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 position information.
029 *
030 * <p>
031 * Identifiers such as {@code java.util.HashMap} are spread across
032 * multiple AST nodes in the syntax tree (three IDENT nodes, two DOT nodes).
033 * A FullIdent represents the whole String (excluding any intermediate
034 * whitespace), which is often easier to work with in Checks.
035 * </p>
036 *
037 * @see TokenTypes#DOT
038 * @see TokenTypes#IDENT
039 */
040public final class FullIdent {
041
042    /** The list holding subsequent elements of identifier. */
043    private final List<String> elements = new ArrayList<>();
044    /** The topmost and leftmost AST of the full identifier. */
045    private DetailAST detailAst;
046
047    /** Hide default constructor. */
048    private FullIdent() {
049    }
050
051    /**
052     * Creates a new FullIdent starting from the child of the specified node.
053     *
054     * @param ast the parent node from where to start from
055     * @return a {@code FullIdent} value
056     */
057    public static FullIdent createFullIdentBelow(DetailAST ast) {
058        return createFullIdent(ast.getFirstChild());
059    }
060
061    /**
062     * Creates a new FullIdent starting from the specified node.
063     *
064     * @param ast the node to start from
065     * @return a {@code FullIdent} value
066     */
067    public static FullIdent createFullIdent(DetailAST ast) {
068        DetailAST node = ast;
069        if (node != null && node.getType() == TokenTypes.SINGLE_LINE_COMMENT) {
070            node = node.getNextSibling();
071        }
072        final FullIdent ident = new FullIdent();
073        extractFullIdent(ident, node);
074        return ident;
075    }
076
077    /**
078     * Extracts a FullIdent.
079     *
080     * @param full the FullIdent to add to
081     * @param ast the node
082     */
083    private static void extractFullIdent(FullIdent full, DetailAST ast) {
084        final Deque<DetailAST> identStack = new ArrayDeque<>();
085        pushToIdentStack(identStack, ast);
086        boolean bracketsExist = false;
087        int dotCounter = 0;
088        while (!identStack.isEmpty()) {
089            final DetailAST currentAst = identStack.pop();
090
091            final DetailAST nextSibling = currentAst.getNextSibling();
092
093            // Here we want type declaration, but not initialization
094            final boolean isArrayTypeDeclarationStart = nextSibling != null
095                    && (nextSibling.getType() == TokenTypes.ARRAY_DECLARATOR
096                        || nextSibling.getType() == TokenTypes.ANNOTATIONS)
097                    && isArrayTypeDeclaration(nextSibling);
098
099            final int typeOfAst = currentAst.getType();
100            bracketsExist = bracketsExist || isArrayTypeDeclarationStart;
101            final DetailAST firstChild = currentAst.getFirstChild();
102
103            if (typeOfAst == TokenTypes.LITERAL_NEW && currentAst.hasChildren()) {
104                pushToIdentStack(identStack, firstChild);
105            }
106            else if (typeOfAst == TokenTypes.DOT) {
107                DetailAST sibling = firstChild.getNextSibling();
108                if (sibling.getType() == TokenTypes.SINGLE_LINE_COMMENT) {
109                    sibling = sibling.getNextSibling();
110                }
111                pushToIdentStack(identStack, sibling);
112                pushToIdentStack(identStack, firstChild);
113                dotCounter++;
114            }
115            else {
116                dotCounter = appendToFull(full, currentAst, dotCounter,
117                        bracketsExist, isArrayTypeDeclarationStart);
118            }
119        }
120    }
121
122    /**
123     * Populates the FullIdent node.
124     *
125     * @param full the FullIdent to add to
126     * @param ast the node
127     * @param dotCounter no of dots
128     * @param bracketsExist yes if true
129     * @param isArrayTypeDeclarationStart true if array type declaration start
130     * @return updated value of dotCounter
131     */
132    private static int appendToFull(FullIdent full, DetailAST ast,
133                int dotCounter, boolean bracketsExist, boolean isArrayTypeDeclarationStart) {
134        int result = dotCounter;
135        if (isArrayTypeDeclarationStart) {
136            full.append(ast);
137            appendBrackets(full, ast);
138        }
139        else if (ast.getType() != TokenTypes.ANNOTATIONS) {
140            full.append(ast);
141            if (dotCounter > 0) {
142                full.append(".");
143                result--;
144            }
145            if (bracketsExist) {
146                appendBrackets(full, ast.getParent());
147            }
148        }
149        return result;
150    }
151
152    /**
153     * Pushes to stack if ast is not null.
154     *
155     * @param stack stack to push into
156     * @param ast node to push into stack
157     */
158    private static void pushToIdentStack(Deque<DetailAST> stack, DetailAST ast) {
159        if (ast != null) {
160            stack.push(ast);
161        }
162    }
163
164    /**
165     * Checks an `ARRAY_DECLARATOR` ast to verify that it is not an
166     * array initialization, i.e. 'new int [2][2]'. We do this by
167     * making sure that no 'EXPR' token exists in this branch.
168     *
169     * @param arrayDeclarator the first ARRAY_DECLARATOR token in the ast
170     * @return true if ast is an array type declaration
171     */
172    private static boolean isArrayTypeDeclaration(DetailAST arrayDeclarator) {
173        DetailAST expression = arrayDeclarator;
174        while (expression != null) {
175            if (expression.getType() == TokenTypes.EXPR) {
176                break;
177            }
178            expression = expression.getFirstChild();
179        }
180        return expression == null;
181    }
182
183    /**
184     * Appends the brackets of an array type to a {@code FullIdent}.
185     *
186     * @param full the FullIdent to append brackets to
187     * @param ast the type ast we are building a {@code FullIdent} for
188     */
189    private static void appendBrackets(FullIdent full, DetailAST ast) {
190        final int bracketCount =
191                ast.getParent().getChildCount(TokenTypes.ARRAY_DECLARATOR);
192        for (int index = 0; index < bracketCount; index++) {
193            full.append("[]");
194        }
195    }
196
197    /**
198     * Gets the text.
199     *
200     * @return the text
201     */
202    public String getText() {
203        return String.join("", elements);
204    }
205
206    /**
207     * Gets the topmost leftmost DetailAST for this FullIdent.
208     *
209     * @return the topmost leftmost ast
210     */
211    public DetailAST getDetailAst() {
212        return detailAst;
213    }
214
215    /**
216     * Gets the line number.
217     *
218     * @return the line number
219     */
220    public int getLineNo() {
221        return detailAst.getLineNo();
222    }
223
224    /**
225     * Gets the column number.
226     *
227     * @return the column number
228     */
229    public int getColumnNo() {
230        return detailAst.getColumnNo();
231    }
232
233    @Override
234    public String toString() {
235        return String.join("", elements)
236            + "[" + detailAst.getLineNo() + "x" + detailAst.getColumnNo() + "]";
237    }
238
239    /**
240     * Append the specified text.
241     *
242     * @param text the text to append
243     */
244    private void append(String text) {
245        elements.add(text);
246    }
247
248    /**
249     * Append the specified token and also recalibrate the first line and column.
250     *
251     * @param ast the token to append
252     */
253    private void append(DetailAST ast) {
254        elements.add(ast.getText());
255        if (detailAst == null) {
256            detailAst = ast;
257        }
258    }
259
260}