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}