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}