1   ////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code for adherence to a set of rules.
3   // Copyright (C) 2001-2019 the original author or authors.
4   //
5   // This library is free software; you can redistribute it and/or
6   // modify it under the terms of the GNU Lesser General Public
7   // License as published by the Free Software Foundation; either
8   // version 2.1 of the License, or (at your option) any later version.
9   //
10  // This library is distributed in the hope that it will be useful,
11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  // Lesser General Public License for more details.
14  //
15  // You should have received a copy of the GNU Lesser General Public
16  // License along with this library; if not, write to the Free Software
17  // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18  ////////////////////////////////////////////////////////////////////////////////
19  
20  package com.puppycrawl.tools.checkstyle.api;
21  
22  import java.util.BitSet;
23  
24  import antlr.CommonASTWithHiddenTokens;
25  import antlr.Token;
26  import antlr.collections.AST;
27  import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
28  
29  /**
30   * An extension of the CommonAST that records the line and column number.
31   *
32   * @see <a href="https://www.antlr.org/">ANTLR Website</a>
33   * @noinspection FieldNotUsedInToString, SerializableHasSerializationMethods
34   */
35  public final class DetailAST extends CommonASTWithHiddenTokens {
36  
37      private static final long serialVersionUID = -2580884815577559874L;
38  
39      /** Constant to indicate if not calculated the child count. */
40      private static final int NOT_INITIALIZED = Integer.MIN_VALUE;
41  
42      /** The line number. **/
43      private int lineNo = NOT_INITIALIZED;
44      /** The column number. **/
45      private int columnNo = NOT_INITIALIZED;
46  
47      /** Number of children. */
48      private int childCount = NOT_INITIALIZED;
49      /** The parent token. */
50      private DetailAST parent;
51      /** Previous sibling. */
52      private DetailAST previousSibling;
53  
54      /**
55       * All token types in this branch.
56       * Token 'x' (where x is an int) is in this branch
57       * if branchTokenTypes.get(x) is true.
58       */
59      private BitSet branchTokenTypes;
60  
61      @Override
62      public void initialize(Token tok) {
63          super.initialize(tok);
64          lineNo = tok.getLine();
65  
66          // expect columns to start @ 0
67          columnNo = tok.getColumn() - 1;
68      }
69  
70      @Override
71      public void initialize(AST ast) {
72          final DetailAST detailAst = (DetailAST) ast;
73          setText(detailAst.getText());
74          setType(detailAst.getType());
75          lineNo = detailAst.getLineNo();
76          columnNo = detailAst.getColumnNo();
77          hiddenAfter = detailAst.getHiddenAfter();
78          hiddenBefore = detailAst.getHiddenBefore();
79      }
80  
81      @Override
82      public void setFirstChild(AST ast) {
83          clearBranchTokenTypes();
84          clearChildCountCache(this);
85          super.setFirstChild(ast);
86          if (ast != null) {
87              ((DetailAST) ast).setParent(this);
88          }
89      }
90  
91      @Override
92      public void setNextSibling(AST ast) {
93          clearBranchTokenTypes();
94          clearChildCountCache(parent);
95          super.setNextSibling(ast);
96          if (ast != null && parent != null) {
97              ((DetailAST) ast).setParent(parent);
98          }
99          if (ast != null) {
100             ((DetailAST) ast).previousSibling = this;
101         }
102     }
103 
104     /**
105      * Add previous sibling.
106      * @param ast
107      *        DetailAST object.
108      */
109     public void addPreviousSibling(DetailAST ast) {
110         clearBranchTokenTypes();
111         clearChildCountCache(parent);
112         if (ast != null) {
113             //parent is set in setNextSibling or parent.setFirstChild
114             final DetailAST previousSiblingNode = previousSibling;
115 
116             if (previousSiblingNode != null) {
117                 ast.previousSibling = previousSiblingNode;
118                 previousSiblingNode.setNextSibling(ast);
119             }
120             else if (parent != null) {
121                 parent.setFirstChild(ast);
122             }
123 
124             ast.setNextSibling(this);
125             previousSibling = ast;
126         }
127     }
128 
129     /**
130      * Add next sibling.
131      * @param ast
132      *        DetailAST object.
133      */
134     public void addNextSibling(DetailAST ast) {
135         clearBranchTokenTypes();
136         clearChildCountCache(parent);
137         if (ast != null) {
138             //parent is set in setNextSibling
139             final DetailAST nextSibling = getNextSibling();
140 
141             if (nextSibling != null) {
142                 ast.setNextSibling(nextSibling);
143                 nextSibling.previousSibling = ast;
144             }
145 
146             ast.previousSibling = this;
147             setNextSibling(ast);
148         }
149     }
150 
151     @Override
152     public void addChild(AST ast) {
153         clearBranchTokenTypes();
154         clearChildCountCache(this);
155         if (ast != null) {
156             ((DetailAST) ast).setParent(this);
157             ((DetailAST) ast).previousSibling = getLastChild();
158         }
159         super.addChild(ast);
160     }
161 
162     /**
163      * Returns the number of child nodes one level below this node. That is is
164      * does not recurse down the tree.
165      * @return the number of child nodes
166      */
167     public int getChildCount() {
168         // lazy init
169         if (childCount == NOT_INITIALIZED) {
170             childCount = 0;
171             AST child = getFirstChild();
172 
173             while (child != null) {
174                 childCount += 1;
175                 child = child.getNextSibling();
176             }
177         }
178         return childCount;
179     }
180 
181     /**
182      * Returns the number of direct child tokens that have the specified type.
183      * @param type the token type to match
184      * @return the number of matching token
185      */
186     public int getChildCount(int type) {
187         int count = 0;
188         for (AST ast = getFirstChild(); ast != null; ast = ast.getNextSibling()) {
189             if (ast.getType() == type) {
190                 count++;
191             }
192         }
193         return count;
194     }
195 
196     /**
197      * Set the parent token.
198      * @param parent the parent token
199      */
200     private void setParent(DetailAST parent) {
201         DetailAST instance = this;
202         do {
203             instance.clearBranchTokenTypes();
204             instance.parent = parent;
205             instance = instance.getNextSibling();
206         } while (instance != null);
207     }
208 
209     /**
210      * Returns the parent token.
211      * @return the parent token
212      */
213     public DetailAST getParent() {
214         return parent;
215     }
216 
217     /**
218      * Gets line number.
219      * @return the line number
220      */
221     public int getLineNo() {
222         int resultNo = -1;
223 
224         if (lineNo == NOT_INITIALIZED) {
225             // an inner AST that has been initialized
226             // with initialize(String text)
227             resultNo = findLineNo(getFirstChild());
228 
229             if (resultNo == -1) {
230                 resultNo = findLineNo(getNextSibling());
231             }
232         }
233         if (resultNo == -1) {
234             resultNo = lineNo;
235         }
236         return resultNo;
237     }
238 
239     /**
240      * Set line number.
241      * @param lineNo
242      *        line number.
243      */
244     public void setLineNo(int lineNo) {
245         this.lineNo = lineNo;
246     }
247 
248     /**
249      * Gets column number.
250      * @return the column number
251      */
252     public int getColumnNo() {
253         int resultNo = -1;
254 
255         if (columnNo == NOT_INITIALIZED) {
256             // an inner AST that has been initialized
257             // with initialize(String text)
258             resultNo = findColumnNo(getFirstChild());
259 
260             if (resultNo == -1) {
261                 resultNo = findColumnNo(getNextSibling());
262             }
263         }
264         if (resultNo == -1) {
265             resultNo = columnNo;
266         }
267         return resultNo;
268     }
269 
270     /**
271      * Set column number.
272      * @param columnNo
273      *        column number.
274      */
275     public void setColumnNo(int columnNo) {
276         this.columnNo = columnNo;
277     }
278 
279     /**
280      * Gets the last child node.
281      * @return the last child node
282      */
283     public DetailAST getLastChild() {
284         DetailAST ast = getFirstChild();
285         while (ast != null && ast.getNextSibling() != null) {
286             ast = ast.getNextSibling();
287         }
288         return ast;
289     }
290 
291     /**
292      * Finds column number in the first non-comment node.
293      *
294      * @param ast DetailAST node.
295      * @return Column number if non-comment node exists, -1 otherwise.
296      */
297     private static int findColumnNo(DetailAST ast) {
298         int resultNo = -1;
299         DetailAST node = ast;
300         while (node != null) {
301             // comment node can't be start of any java statement/definition
302             if (TokenUtil.isCommentType(node.getType())) {
303                 node = node.getNextSibling();
304             }
305             else {
306                 resultNo = node.getColumnNo();
307                 break;
308             }
309         }
310         return resultNo;
311     }
312 
313     /**
314      * Finds line number in the first non-comment node.
315      *
316      * @param ast DetailAST node.
317      * @return Line number if non-comment node exists, -1 otherwise.
318      */
319     private static int findLineNo(DetailAST ast) {
320         int resultNo = -1;
321         DetailAST node = ast;
322         while (node != null) {
323             // comment node can't be start of any java statement/definition
324             if (TokenUtil.isCommentType(node.getType())) {
325                 node = node.getNextSibling();
326             }
327             else {
328                 resultNo = node.getLineNo();
329                 break;
330             }
331         }
332         return resultNo;
333     }
334 
335     /**
336      * Returns token type with branch.
337      * @return the token types that occur in the branch as a sorted set.
338      */
339     private BitSet getBranchTokenTypes() {
340         // lazy init
341         if (branchTokenTypes == null) {
342             branchTokenTypes = new BitSet();
343             branchTokenTypes.set(getType());
344 
345             // add union of all children
346             DetailAST child = getFirstChild();
347             while (child != null) {
348                 final BitSet childTypes = child.getBranchTokenTypes();
349                 branchTokenTypes.or(childTypes);
350 
351                 child = child.getNextSibling();
352             }
353         }
354         return branchTokenTypes;
355     }
356 
357     /**
358      * Checks if this branch of the parse tree contains a token
359      * of the provided type.
360      * @param type a TokenType
361      * @return true if and only if this branch (including this node)
362      *     contains a token of type {@code type}.
363      */
364     public boolean branchContains(int type) {
365         return getBranchTokenTypes().get(type);
366     }
367 
368     /**
369      * Returns the previous sibling or null if no such sibling exists.
370      * @return the previous sibling or null if no such sibling exists.
371      */
372     public DetailAST getPreviousSibling() {
373         return previousSibling;
374     }
375 
376     /**
377      * Returns the first child token that makes a specified type.
378      * @param type the token type to match
379      * @return the matching token, or null if no match
380      */
381     public DetailAST findFirstToken(int type) {
382         DetailAST returnValue = null;
383         for (DetailAST ast = getFirstChild(); ast != null; ast = ast.getNextSibling()) {
384             if (ast.getType() == type) {
385                 returnValue = ast;
386                 break;
387             }
388         }
389         return returnValue;
390     }
391 
392     @Override
393     public String toString() {
394         return super.toString() + "[" + getLineNo() + "x" + getColumnNo() + "]";
395     }
396 
397     @Override
398     public DetailAST getNextSibling() {
399         return (DetailAST) super.getNextSibling();
400     }
401 
402     @Override
403     public DetailAST getFirstChild() {
404         return (DetailAST) super.getFirstChild();
405     }
406 
407     /**
408      * Clears the child count for the ast instance.
409      * @param ast The ast to clear.
410      */
411     private static void clearChildCountCache(DetailAST ast) {
412         if (ast != null) {
413             ast.childCount = NOT_INITIALIZED;
414         }
415     }
416 
417     /**
418      * Clears branchTokenTypes cache for all parents of the current DetailAST instance, and the
419      * child count for the current DetailAST instance.
420      */
421     private void clearBranchTokenTypes() {
422         DetailAST prevParent = parent;
423         while (prevParent != null) {
424             prevParent.branchTokenTypes = null;
425             prevParent = prevParent.parent;
426         }
427     }
428 
429 }