001///////////////////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
003// Copyright (C) 2001-2026 the original author or authors.
004//
005// This library is free software; you can redistribute it and/or
006// modify it under the terms of the GNU Lesser General Public
007// License as published by the Free Software Foundation; either
008// version 2.1 of the License, or (at your option) any later version.
009//
010// This library is distributed in the hope that it will be useful,
011// but WITHOUT ANY WARRANTY; without even the implied warranty of
012// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
013// Lesser General Public License for more details.
014//
015// You should have received a copy of the GNU Lesser General Public
016// License along with this library; if not, write to the Free Software
017// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
018///////////////////////////////////////////////////////////////////////////////////////////////
019
020package com.puppycrawl.tools.checkstyle.api;
021
022import java.util.ArrayDeque;
023import java.util.ArrayList;
024import java.util.Deque;
025import java.util.List;
026
027/**
028 * Represents a full identifier, including dots, with associated
029 * position information.
030 *
031 * <p>
032 * Identifiers such as {@code java.util.HashMap} are spread across
033 * multiple AST nodes in the syntax tree (three IDENT nodes, two DOT nodes).
034 * A FullIdent represents the whole String (excluding any intermediate
035 * whitespace), which is often easier to work with in Checks.
036 * </p>
037 *
038 * @see TokenTypes#DOT
039 * @see TokenTypes#IDENT
040 **/
041public final class FullIdent {
042
043    /** The list holding subsequent elements of identifier. **/
044    private final List<String> elements = new ArrayList<>();
045    /** The topmost and leftmost AST of the full identifier. */
046    private DetailAST detailAst;
047
048    /** Hide default constructor. */
049    private FullIdent() {
050    }
051
052    /**
053     * Creates a new FullIdent starting from the child of the specified node.
054     *
055     * @param ast the parent node from where to start from
056     * @return a {@code FullIdent} value
057     */
058    public static FullIdent createFullIdentBelow(DetailAST ast) {
059        return createFullIdent(ast.getFirstChild());
060    }
061
062    /**
063     * Creates a new FullIdent starting from the specified node.
064     *
065     * @param ast the node to start from
066     * @return a {@code FullIdent} value
067     */
068    public static FullIdent createFullIdent(DetailAST ast) {
069        DetailAST node = ast;
070        if (node != null && node.getType() == TokenTypes.SINGLE_LINE_COMMENT) {
071            node = node.getNextSibling();
072        }
073        final FullIdent ident = new FullIdent();
074        extractFullIdent(ident, node);
075        return ident;
076    }
077
078    /**
079     * Extracts a FullIdent.
080     *
081     * @param full the FullIdent to add to
082     * @param ast the node
083     */
084    private static void extractFullIdent(FullIdent full, DetailAST ast) {
085        final Deque<DetailAST> identStack = new ArrayDeque<>();
086        pushToIdentStack(identStack, ast);
087        boolean bracketsExist = false;
088        int dotCounter = 0;
089        while (!identStack.isEmpty()) {
090            final DetailAST currentAst = identStack.pop();
091
092            final DetailAST nextSibling = currentAst.getNextSibling();
093
094            // Here we want type declaration, but not initialization
095            final boolean isArrayTypeDeclarationStart = nextSibling != null
096                    && (nextSibling.getType() == TokenTypes.ARRAY_DECLARATOR
097                        || nextSibling.getType() == TokenTypes.ANNOTATIONS)
098                    && isArrayTypeDeclaration(nextSibling);
099
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}