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