001///////////////////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
003// Copyright (C) 2001-2024 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.ArrayList;
023import java.util.List;
024
025/**
026 * Represents a full identifier, including dots, with associated
027 * position information.
028 *
029 * <p>
030 * Identifiers such as {@code java.util.HashMap} are spread across
031 * multiple AST nodes in the syntax tree (three IDENT nodes, two DOT nodes).
032 * A FullIdent represents the whole String (excluding any intermediate
033 * whitespace), which is often easier to work with in Checks.
034 * </p>
035 *
036 * @see TokenTypes#DOT
037 * @see TokenTypes#IDENT
038 **/
039public final class FullIdent {
040
041    /** The list holding subsequent elements of identifier. **/
042    private final List<String> elements = new ArrayList<>();
043    /** The topmost and leftmost AST of the full identifier. */
044    private DetailAST detailAst;
045
046    /** Hide default constructor. */
047    private FullIdent() {
048    }
049
050    /**
051     * Creates a new FullIdent starting from the child of the specified node.
052     *
053     * @param ast the parent node from where to start from
054     * @return a {@code FullIdent} value
055     */
056    public static FullIdent createFullIdentBelow(DetailAST ast) {
057        return createFullIdent(ast.getFirstChild());
058    }
059
060    /**
061     * Creates a new FullIdent starting from the specified node.
062     *
063     * @param ast the node to start from
064     * @return a {@code FullIdent} value
065     */
066    public static FullIdent createFullIdent(DetailAST ast) {
067        final FullIdent ident = new FullIdent();
068        extractFullIdent(ident, ast);
069        return ident;
070    }
071
072    /**
073     * Recursively extract a FullIdent.
074     *
075     * @param full the FullIdent to add to
076     * @param ast the node to recurse from
077     * @noinspection TailRecursion
078     * @noinspectionreason TailRecursion - until issue #14814
079     */
080    private static void extractFullIdent(FullIdent full, DetailAST ast) {
081        if (ast != null) {
082            final DetailAST nextSibling = ast.getNextSibling();
083
084            // Here we want type declaration, but not initialization
085            final boolean isArrayTypeDeclarationStart = nextSibling != null
086                    && (nextSibling.getType() == TokenTypes.ARRAY_DECLARATOR
087                        || nextSibling.getType() == TokenTypes.ANNOTATIONS)
088                    && isArrayTypeDeclaration(nextSibling);
089
090            final int typeOfAst = ast.getType();
091            if (typeOfAst == TokenTypes.LITERAL_NEW
092                    && ast.hasChildren()) {
093                final DetailAST firstChild = ast.getFirstChild();
094                extractFullIdent(full, firstChild);
095            }
096            else if (typeOfAst == TokenTypes.DOT) {
097                final DetailAST firstChild = ast.getFirstChild();
098                extractFullIdent(full, firstChild);
099                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}