1
2
3
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("<");
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
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 }