View Javadoc
1   ///////////////////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3   // Copyright (C) 2001-2024 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.ArrayList;
23  import java.util.List;
24  
25  /**
26   * Represents a full identifier, including dots, with associated
27   * position information.
28   *
29   * <p>
30   * Identifiers such as {@code java.util.HashMap} are spread across
31   * multiple AST nodes in the syntax tree (three IDENT nodes, two DOT nodes).
32   * A FullIdent represents the whole String (excluding any intermediate
33   * whitespace), which is often easier to work with in Checks.
34   * </p>
35   *
36   * @see TokenTypes#DOT
37   * @see TokenTypes#IDENT
38   **/
39  public final class FullIdent {
40  
41      /** The list holding subsequent elements of identifier. **/
42      private final List<String> elements = new ArrayList<>();
43      /** The topmost and leftmost AST of the full identifier. */
44      private DetailAST detailAst;
45  
46      /** Hide default constructor. */
47      private FullIdent() {
48      }
49  
50      /**
51       * Creates a new FullIdent starting from the child of the specified node.
52       *
53       * @param ast the parent node from where to start from
54       * @return a {@code FullIdent} value
55       */
56      public static FullIdent createFullIdentBelow(DetailAST ast) {
57          return createFullIdent(ast.getFirstChild());
58      }
59  
60      /**
61       * Creates a new FullIdent starting from the specified node.
62       *
63       * @param ast the node to start from
64       * @return a {@code FullIdent} value
65       */
66      public static FullIdent createFullIdent(DetailAST ast) {
67          final FullIdent ident = new FullIdent();
68          extractFullIdent(ident, ast);
69          return ident;
70      }
71  
72      /**
73       * Recursively extract a FullIdent.
74       *
75       * @param full the FullIdent to add to
76       * @param ast the node to recurse from
77       * @noinspection TailRecursion
78       * @noinspectionreason TailRecursion - until issue #14814
79       */
80      private static void extractFullIdent(FullIdent full, DetailAST ast) {
81          if (ast != null) {
82              final DetailAST nextSibling = ast.getNextSibling();
83  
84              // Here we want type declaration, but not initialization
85              final boolean isArrayTypeDeclarationStart = nextSibling != null
86                      && (nextSibling.getType() == TokenTypes.ARRAY_DECLARATOR
87                          || nextSibling.getType() == TokenTypes.ANNOTATIONS)
88                      && isArrayTypeDeclaration(nextSibling);
89  
90              final int typeOfAst = ast.getType();
91              if (typeOfAst == TokenTypes.LITERAL_NEW
92                      && ast.hasChildren()) {
93                  final DetailAST firstChild = ast.getFirstChild();
94                  extractFullIdent(full, firstChild);
95              }
96              else if (typeOfAst == TokenTypes.DOT) {
97                  final DetailAST firstChild = ast.getFirstChild();
98                  extractFullIdent(full, firstChild);
99                  full.append(".");
100                 extractFullIdent(full, firstChild.getNextSibling());
101                 appendBrackets(full, ast);
102             }
103             else if (isArrayTypeDeclarationStart) {
104                 full.append(ast);
105                 appendBrackets(full, ast);
106             }
107             else if (typeOfAst != TokenTypes.ANNOTATIONS) {
108                 full.append(ast);
109             }
110         }
111     }
112 
113     /**
114      * Checks an `ARRAY_DECLARATOR` ast to verify that it is not an
115      * array initialization, i.e. 'new int [2][2]'. We do this by
116      * making sure that no 'EXPR' token exists in this branch.
117      *
118      * @param arrayDeclarator the first ARRAY_DECLARATOR token in the ast
119      * @return true if ast is an array type declaration
120      */
121     private static boolean isArrayTypeDeclaration(DetailAST arrayDeclarator) {
122         DetailAST expression = arrayDeclarator;
123         while (expression != null) {
124             if (expression.getType() == TokenTypes.EXPR) {
125                 break;
126             }
127             expression = expression.getFirstChild();
128         }
129         return expression == null;
130     }
131 
132     /**
133      * Appends the brackets of an array type to a {@code FullIdent}.
134      *
135      * @param full the FullIdent to append brackets to
136      * @param ast the type ast we are building a {@code FullIdent} for
137      */
138     private static void appendBrackets(FullIdent full, DetailAST ast) {
139         final int bracketCount =
140                 ast.getParent().getChildCount(TokenTypes.ARRAY_DECLARATOR);
141         for (int i = 0; i < bracketCount; i++) {
142             full.append("[]");
143         }
144     }
145 
146     /**
147      * Gets the text.
148      *
149      * @return the text
150      */
151     public String getText() {
152         return String.join("", elements);
153     }
154 
155     /**
156      * Gets the topmost leftmost DetailAST for this FullIdent.
157      *
158      * @return the topmost leftmost ast
159      */
160     public DetailAST getDetailAst() {
161         return detailAst;
162     }
163 
164     /**
165      * Gets the line number.
166      *
167      * @return the line number
168      */
169     public int getLineNo() {
170         return detailAst.getLineNo();
171     }
172 
173     /**
174      * Gets the column number.
175      *
176      * @return the column number
177      */
178     public int getColumnNo() {
179         return detailAst.getColumnNo();
180     }
181 
182     @Override
183     public String toString() {
184         return String.join("", elements)
185             + "[" + detailAst.getLineNo() + "x" + detailAst.getColumnNo() + "]";
186     }
187 
188     /**
189      * Append the specified text.
190      *
191      * @param text the text to append
192      */
193     private void append(String text) {
194         elements.add(text);
195     }
196 
197     /**
198      * Append the specified token and also recalibrate the first line and
199      * column.
200      *
201      * @param ast the token to append
202      */
203     private void append(DetailAST ast) {
204         elements.add(ast.getText());
205         if (detailAst == null) {
206             detailAst = ast;
207         }
208     }
209 
210 }