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.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          final FullIdent ident = new FullIdent();
70          extractFullIdent(ident, ast);
71          return ident;
72      }
73  
74      /**
75       * Extracts a FullIdent.
76       *
77       * @param full the FullIdent to add to
78       * @param ast the node
79       */
80      private static void extractFullIdent(FullIdent full, DetailAST ast) {
81          final Deque<DetailAST> identStack = new ArrayDeque<>();
82          pushToIdentStack(identStack, ast);
83          boolean bracketsExist = false;
84          int dotCounter = 0;
85          while (!identStack.isEmpty()) {
86              final DetailAST currentAst = identStack.pop();
87  
88              final DetailAST nextSibling = currentAst.getNextSibling();
89  
90              // Here we want type declaration, but not initialization
91              final boolean isArrayTypeDeclarationStart = nextSibling != null
92                      && (nextSibling.getType() == TokenTypes.ARRAY_DECLARATOR
93                          || nextSibling.getType() == TokenTypes.ANNOTATIONS)
94                      && isArrayTypeDeclaration(nextSibling);
95  
96              final int typeOfAst = currentAst.getType();
97              bracketsExist = bracketsExist || isArrayTypeDeclarationStart;
98              final DetailAST firstChild = currentAst.getFirstChild();
99  
100             if (typeOfAst == TokenTypes.LITERAL_NEW && currentAst.hasChildren()) {
101                 pushToIdentStack(identStack, firstChild);
102             }
103             else if (typeOfAst == TokenTypes.DOT) {
104                 pushToIdentStack(identStack, firstChild.getNextSibling());
105                 pushToIdentStack(identStack, firstChild);
106                 dotCounter++;
107             }
108             else {
109                 dotCounter = appendToFull(full, currentAst, dotCounter,
110                         bracketsExist, isArrayTypeDeclarationStart);
111             }
112         }
113     }
114 
115     /**
116      * Populates the FullIdent node.
117      *
118      * @param full the FullIdent to add to
119      * @param ast the node
120      * @param dotCounter no of dots
121      * @param bracketsExist yes if true
122      * @param isArrayTypeDeclarationStart true if array type declaration start
123      * @return updated value of dotCounter
124      */
125     private static int appendToFull(FullIdent full, DetailAST ast,
126                 int dotCounter, boolean bracketsExist, boolean isArrayTypeDeclarationStart) {
127         int result = dotCounter;
128         if (isArrayTypeDeclarationStart) {
129             full.append(ast);
130             appendBrackets(full, ast);
131         }
132         else if (ast.getType() != TokenTypes.ANNOTATIONS) {
133             full.append(ast);
134             if (dotCounter > 0) {
135                 full.append(".");
136                 result--;
137             }
138             if (bracketsExist) {
139                 appendBrackets(full, ast.getParent());
140             }
141         }
142         return result;
143     }
144 
145     /**
146      * Pushes to stack if ast is not null.
147      *
148      * @param stack stack to push into
149      * @param ast node to push into stack
150      */
151     private static void pushToIdentStack(Deque<DetailAST> stack, DetailAST ast) {
152         if (ast != null) {
153             stack.push(ast);
154         }
155     }
156 
157     /**
158      * Checks an `ARRAY_DECLARATOR` ast to verify that it is not an
159      * array initialization, i.e. 'new int [2][2]'. We do this by
160      * making sure that no 'EXPR' token exists in this branch.
161      *
162      * @param arrayDeclarator the first ARRAY_DECLARATOR token in the ast
163      * @return true if ast is an array type declaration
164      */
165     private static boolean isArrayTypeDeclaration(DetailAST arrayDeclarator) {
166         DetailAST expression = arrayDeclarator;
167         while (expression != null) {
168             if (expression.getType() == TokenTypes.EXPR) {
169                 break;
170             }
171             expression = expression.getFirstChild();
172         }
173         return expression == null;
174     }
175 
176     /**
177      * Appends the brackets of an array type to a {@code FullIdent}.
178      *
179      * @param full the FullIdent to append brackets to
180      * @param ast the type ast we are building a {@code FullIdent} for
181      */
182     private static void appendBrackets(FullIdent full, DetailAST ast) {
183         final int bracketCount =
184                 ast.getParent().getChildCount(TokenTypes.ARRAY_DECLARATOR);
185         for (int i = 0; i < bracketCount; i++) {
186             full.append("[]");
187         }
188     }
189 
190     /**
191      * Gets the text.
192      *
193      * @return the text
194      */
195     public String getText() {
196         return String.join("", elements);
197     }
198 
199     /**
200      * Gets the topmost leftmost DetailAST for this FullIdent.
201      *
202      * @return the topmost leftmost ast
203      */
204     public DetailAST getDetailAst() {
205         return detailAst;
206     }
207 
208     /**
209      * Gets the line number.
210      *
211      * @return the line number
212      */
213     public int getLineNo() {
214         return detailAst.getLineNo();
215     }
216 
217     /**
218      * Gets the column number.
219      *
220      * @return the column number
221      */
222     public int getColumnNo() {
223         return detailAst.getColumnNo();
224     }
225 
226     @Override
227     public String toString() {
228         return String.join("", elements)
229             + "[" + detailAst.getLineNo() + "x" + detailAst.getColumnNo() + "]";
230     }
231 
232     /**
233      * Append the specified text.
234      *
235      * @param text the text to append
236      */
237     private void append(String text) {
238         elements.add(text);
239     }
240 
241     /**
242      * Append the specified token and also recalibrate the first line and
243      * column.
244      *
245      * @param ast the token to append
246      */
247     private void append(DetailAST ast) {
248         elements.add(ast.getText());
249         if (detailAst == null) {
250             detailAst = ast;
251         }
252     }
253 
254 }