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