View Javadoc
1   /*
2    * Copyright (c) 2006 JMockit developers
3    * This file is subject to the terms of the MIT license (see LICENSE.txt).
4    */
5   package mockit.coverage.reporting.parsing;
6   
7   import static java.util.Arrays.asList;
8   
9   import edu.umd.cs.findbugs.annotations.NonNull;
10  import edu.umd.cs.findbugs.annotations.Nullable;
11  
12  import java.util.Iterator;
13  import java.util.List;
14  import java.util.NoSuchElementException;
15  import java.util.regex.Pattern;
16  
17  public final class LineElement implements Iterable<LineElement> {
18      private static final List<String> CONDITIONAL_OPERATORS = asList("||", "&&", ":", "else", "?");
19      private static final Pattern OPEN_TAG = Pattern.compile("<");
20      private static final NoSuchElementException LAST_ELEMENT_REACHED = new NoSuchElementException();
21  
22      enum ElementType {
23          CODE, COMMENT, SEPARATOR
24      }
25  
26      enum ConditionalStatement {
27          IF("if"), FOR("for"), WHILE("while");
28  
29          @NonNull
30          private final String keyword;
31  
32          ConditionalStatement(@NonNull String keyword) {
33              this.keyword = keyword;
34          }
35  
36          @Nullable
37          static ConditionalStatement find(@NonNull String keyword) {
38              for (ConditionalStatement statement : values()) {
39                  if (statement.keyword.equals(keyword)) {
40                      return statement;
41                  }
42              }
43  
44              return null;
45          }
46      }
47  
48      @NonNull
49      private final ElementType type;
50      @NonNull
51      private final String text;
52      @Nullable
53      private String openingTag;
54      @Nullable
55      private String closingTag;
56      @Nullable
57      private LineElement next;
58  
59      @Nullable
60      private ConditionalStatement conditionalStatement;
61      private int parenthesesBalance;
62  
63      LineElement(@NonNull ElementType type, @NonNull String text) {
64          this.type = type;
65          this.text = OPEN_TAG.matcher(text).replaceAll("&lt;");
66      }
67  
68      public boolean isCode() {
69          return type == ElementType.CODE;
70      }
71  
72      public boolean isComment() {
73          return type == ElementType.COMMENT;
74      }
75  
76      public boolean isKeyword(@NonNull String keyword) {
77          return isCode() && text.equals(keyword);
78      }
79  
80      boolean isDotSeparator() {
81          return type == ElementType.SEPARATOR && text.charAt(0) == '.';
82      }
83  
84      @NonNull
85      public String getText() {
86          return text;
87      }
88  
89      @Nullable
90      public LineElement getNext() {
91          return next;
92      }
93  
94      void setNext(@Nullable LineElement next) {
95          this.next = next;
96      }
97  
98      @Nullable
99      public LineElement getNextCodeElement() {
100         if (next != null) {
101             for (LineElement element : next) {
102                 if (element.isCode()) {
103                     return element;
104                 }
105             }
106         }
107 
108         return null;
109     }
110 
111     public void wrapText(@NonNull String desiredOpeningTag, @NonNull String desiredClosingTag) {
112         openingTag = desiredOpeningTag;
113         closingTag = desiredClosingTag;
114     }
115 
116     @Nullable
117     public LineElement appendUntilNextCodeElement(@NonNull StringBuilder line) {
118         LineElement element = this;
119 
120         while (!element.isCode()) {
121             element.appendText(line);
122             element = element.next;
123 
124             if (element == null) {
125                 break;
126             }
127 
128             copyConditionalTrackingState(element);
129         }
130 
131         return element;
132     }
133 
134     private void copyConditionalTrackingState(@NonNull LineElement destination) {
135         destination.conditionalStatement = conditionalStatement;
136         destination.parenthesesBalance = parenthesesBalance;
137     }
138 
139     private void appendText(@NonNull StringBuilder line) {
140         if (openingTag == null) {
141             line.append(text);
142         } else {
143             line.append(openingTag).append(text).append(closingTag);
144         }
145     }
146 
147     @Nullable
148     public LineElement findNextBranchingPoint() {
149         if (conditionalStatement == null) {
150             conditionalStatement = ConditionalStatement.find(text);
151         }
152 
153         if (isBranchingElement()) {
154             if (next != null) {
155                 copyConditionalTrackingState(next);
156             }
157 
158             return this;
159         }
160 
161         if (conditionalStatement != null) {
162             int balance = getParenthesisBalance();
163             parenthesesBalance += balance;
164 
165             if (balance != 0 && parenthesesBalance == 0) {
166                 return next;
167             }
168         }
169 
170         if (next == null) {
171             return null;
172         }
173 
174         copyConditionalTrackingState(next);
175 
176         // noinspection TailRecursion
177         return next.findNextBranchingPoint();
178     }
179 
180     public boolean isBranchingElement() {
181         if (conditionalStatement == ConditionalStatement.FOR) {
182             int p = text.indexOf(':');
183 
184             if (p < 0) {
185                 p = text.indexOf(';');
186             }
187 
188             return p >= 0 && text.trim().length() == 1;
189         }
190 
191         return CONDITIONAL_OPERATORS.contains(text);
192     }
193 
194     private int getParenthesisBalance() {
195         int balance = 0;
196         int p = text.indexOf('(');
197 
198         while (p >= 0) {
199             balance++;
200             p = text.indexOf('(', p + 1);
201         }
202 
203         int q = text.indexOf(')');
204 
205         while (q >= 0) {
206             balance--;
207             q = text.indexOf(')', q + 1);
208         }
209 
210         return balance;
211     }
212 
213     @Nullable
214     public LineElement findWord(@NonNull String word) {
215         for (LineElement element : this) {
216             if (element.isCode() && word.equals(element.text)) {
217                 return element;
218             }
219         }
220 
221         return null;
222     }
223 
224     int getBraceBalanceUntilEndOfLine() {
225         int balance = 0;
226 
227         for (LineElement element : this) {
228             balance += element.getBraceBalance();
229         }
230 
231         return balance;
232     }
233 
234     private int getBraceBalance() {
235         if (isCode() && text.length() == 1) {
236             char c = text.charAt(0);
237 
238             if (c == '{') {
239                 return 1;
240             }
241             if (c == '}') {
242                 return -1;
243             }
244         }
245 
246         return 0;
247     }
248 
249     public void appendAllBefore(@NonNull StringBuilder line, @Nullable LineElement elementToStopBefore) {
250         LineElement elementToPrint = this;
251 
252         do {
253             elementToPrint.appendText(line);
254             elementToPrint = elementToPrint.next;
255         } while (elementToPrint != null && elementToPrint != elementToStopBefore);
256     }
257 
258     @NonNull
259     @Override
260     public Iterator<LineElement> iterator() {
261         return new Iterator<>() {
262             @Nullable
263             private LineElement current = LineElement.this;
264 
265             @Override
266             public boolean hasNext() {
267                 return current != null;
268             }
269 
270             @NonNull
271             @Override
272             public LineElement next() {
273                 if (current == null) {
274                     throw LAST_ELEMENT_REACHED;
275                 }
276 
277                 LineElement nextElement = current;
278                 current = current.next;
279 
280                 return nextElement;
281             }
282 
283             @Override
284             public void remove() {
285             }
286         };
287     }
288 
289     @Override
290     public String toString() {
291         StringBuilder line = new StringBuilder(200);
292 
293         for (LineElement element : this) {
294             element.appendText(line);
295         }
296 
297         return line.toString();
298     }
299 }