View Javadoc
1   /*
2    * MIT License
3    * Copyright (c) 2006-2025 JMockit developers
4    * See LICENSE file for full license text.
5    */
6   package mockit.coverage.reporting.parsing;
7   
8   import edu.umd.cs.findbugs.annotations.NonNull;
9   import edu.umd.cs.findbugs.annotations.Nullable;
10  
11  import mockit.coverage.reporting.parsing.LineElement.ElementType;
12  
13  import org.checkerframework.checker.index.qual.NonNegative;
14  
15  /**
16   * Parses a source line into one or more consecutive segments, identifying which ones contain Java code and which ones
17   * contain only comments. Block comments initiated in a previous line are kept track of until the end of the block is
18   * reached.
19   */
20  public final class LineParser {
21      private static final String SEPARATORS = ".,;()";
22  
23      @NonNegative
24      private int lineNum;
25      private String line;
26      @Nullable
27      private LineElement initialElement;
28      private boolean inComments;
29  
30      // Helper fields:
31      @Nullable
32      private LineElement currentElement;
33      @NonNegative
34      private int lineLength;
35      private int startPos;
36      private boolean inCodeElement;
37      @NonNegative
38      private int pos;
39      private int currChar;
40  
41      @NonNegative
42      public int getNumber() {
43          return lineNum;
44      }
45  
46      public boolean isInComments() {
47          return inComments;
48      }
49  
50      public boolean isBlankLine() {
51          int n = line.length();
52  
53          for (int i = 0; i < n; i++) {
54              char c = line.charAt(i);
55  
56              if (!Character.isWhitespace(c)) {
57                  return false;
58              }
59          }
60  
61          return true;
62      }
63  
64      @NonNull
65      public LineElement getInitialElement() {
66          assert initialElement != null;
67          return initialElement;
68      }
69  
70      boolean parse(@NonNull String lineToParse) {
71          lineNum++;
72          initialElement = null;
73          currentElement = null;
74          line = lineToParse;
75          lineLength = lineToParse.length();
76          startPos = inComments ? 0 : -1;
77          inCodeElement = false;
78  
79          for (pos = 0; pos < lineLength; pos++) {
80              currChar = lineToParse.codePointAt(pos);
81  
82              if (parseComment()) {
83                  break;
84              }
85  
86              parseSeparatorsAndCode();
87          }
88  
89          if (startPos >= 0) {
90              addFinalElement();
91          } else if (initialElement == null) {
92              initialElement = new LineElement(ElementType.SEPARATOR, "");
93              return false;
94          }
95  
96          return !inComments && !isBlankLine();
97      }
98  
99      private void parseSeparatorsAndCode() {
100         boolean separator = isSeparator();
101 
102         if (separator && !inCodeElement) {
103             startNewElementIfNotYetStarted();
104         } else if (!separator && !inCodeElement) {
105             if (startPos >= 0) {
106                 addElement();
107             }
108 
109             inCodeElement = true;
110             startPos = pos;
111         } else if (separator) {
112             addElement();
113             inCodeElement = false;
114             startPos = pos;
115         }
116     }
117 
118     private boolean isSeparator() {
119         return Character.isWhitespace(currChar) || SEPARATORS.indexOf(currChar) >= 0;
120     }
121 
122     private void startNewElementIfNotYetStarted() {
123         if (startPos < 0) {
124             startPos = pos;
125         }
126     }
127 
128     private boolean parseComment() {
129         if (inComments && parseUntilEndOfLineOrEndOfComment()) {
130             return true;
131         }
132 
133         while (currChar == '/' && pos < lineLength - 1) {
134             int c2 = line.codePointAt(pos + 1);
135 
136             if (c2 == '/') {
137                 endCodeElementIfPending();
138                 startNewElementIfNotYetStarted();
139                 inComments = true;
140                 addFinalElement();
141                 inComments = false;
142                 startPos = -1;
143                 return true;
144             }
145             if (c2 != '*') {
146                 break;
147             }
148             endCodeElementIfPending();
149             startNewElementIfNotYetStarted();
150             inComments = true;
151             pos += 2;
152 
153             if (parseUntilEndOfLineOrEndOfComment()) {
154                 return true;
155             }
156         }
157 
158         return false;
159     }
160 
161     private void endCodeElementIfPending() {
162         if (inCodeElement) {
163             addElement();
164             startPos = pos;
165             inCodeElement = false;
166         }
167     }
168 
169     private boolean parseUntilEndOfLineOrEndOfComment() {
170         while (pos < lineLength) {
171             currChar = line.codePointAt(pos);
172 
173             if (currChar == '*' && pos < lineLength - 1 && line.codePointAt(pos + 1) == '/') {
174                 pos += 2;
175                 addElement();
176                 startPos = -1;
177                 inComments = false;
178                 break;
179             }
180 
181             pos++;
182         }
183 
184         if (pos < lineLength) {
185             currChar = line.codePointAt(pos);
186             return false;
187         }
188 
189         return true;
190     }
191 
192     private void addFinalElement() {
193         String text = line.substring(startPos);
194         addElement(text);
195     }
196 
197     private void addElement() {
198         String text = pos > 0 ? line.substring(startPos, pos) : line.substring(startPos);
199         addElement(text);
200     }
201 
202     private void addElement(@NonNull String text) {
203         ElementType type;
204 
205         if (inComments) {
206             type = ElementType.COMMENT;
207         } else if (inCodeElement) {
208             type = ElementType.CODE;
209         } else {
210             type = ElementType.SEPARATOR;
211         }
212 
213         LineElement newElement = new LineElement(type, text);
214 
215         if (initialElement == null) {
216             initialElement = newElement;
217         } else {
218             assert currentElement != null;
219             currentElement.setNext(newElement);
220         }
221 
222         currentElement = newElement;
223     }
224 }