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