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 */ 078 private static void extractFullIdent(FullIdent full, DetailAST ast) { 079 if (ast != null) { 080 final DetailAST nextSibling = ast.getNextSibling(); 081 082 // Here we want type declaration, but not initialization 083 final boolean isArrayTypeDeclarationStart = nextSibling != null 084 && (nextSibling.getType() == TokenTypes.ARRAY_DECLARATOR 085 || nextSibling.getType() == TokenTypes.ANNOTATIONS) 086 && isArrayTypeDeclaration(nextSibling); 087 088 final int typeOfAst = ast.getType(); 089 if (typeOfAst == TokenTypes.LITERAL_NEW 090 && ast.hasChildren()) { 091 final DetailAST firstChild = ast.getFirstChild(); 092 extractFullIdent(full, firstChild); 093 } 094 else if (typeOfAst == TokenTypes.DOT) { 095 final DetailAST firstChild = ast.getFirstChild(); 096 extractFullIdent(full, firstChild); 097 full.append("."); 098 extractFullIdent(full, firstChild.getNextSibling()); 099 appendBrackets(full, ast); 100 } 101 else if (isArrayTypeDeclarationStart) { 102 full.append(ast); 103 appendBrackets(full, ast); 104 } 105 else if (typeOfAst != TokenTypes.ANNOTATIONS) { 106 full.append(ast); 107 } 108 } 109 } 110 111 /** 112 * Checks an `ARRAY_DECLARATOR` ast to verify that it is not an 113 * array initialization, i.e. 'new int [2][2]'. We do this by 114 * making sure that no 'EXPR' token exists in this branch. 115 * 116 * @param arrayDeclarator the first ARRAY_DECLARATOR token in the ast 117 * @return true if ast is an array type declaration 118 */ 119 private static boolean isArrayTypeDeclaration(DetailAST arrayDeclarator) { 120 DetailAST expression = arrayDeclarator; 121 while (expression != null) { 122 if (expression.getType() == TokenTypes.EXPR) { 123 break; 124 } 125 expression = expression.getFirstChild(); 126 } 127 return expression == null; 128 } 129 130 /** 131 * Appends the brackets of an array type to a {@code FullIdent}. 132 * 133 * @param full the FullIdent to append brackets to 134 * @param ast the type ast we are building a {@code FullIdent} for 135 */ 136 private static void appendBrackets(FullIdent full, DetailAST ast) { 137 final int bracketCount = 138 ast.getParent().getChildCount(TokenTypes.ARRAY_DECLARATOR); 139 for (int i = 0; i < bracketCount; i++) { 140 full.append("[]"); 141 } 142 } 143 144 /** 145 * Gets the text. 146 * 147 * @return the text 148 */ 149 public String getText() { 150 return String.join("", elements); 151 } 152 153 /** 154 * Gets the topmost leftmost DetailAST for this FullIdent. 155 * 156 * @return the topmost leftmost ast 157 */ 158 public DetailAST getDetailAst() { 159 return detailAst; 160 } 161 162 /** 163 * Gets the line number. 164 * 165 * @return the line number 166 */ 167 public int getLineNo() { 168 return detailAst.getLineNo(); 169 } 170 171 /** 172 * Gets the column number. 173 * 174 * @return the column number 175 */ 176 public int getColumnNo() { 177 return detailAst.getColumnNo(); 178 } 179 180 @Override 181 public String toString() { 182 return String.join("", elements) 183 + "[" + detailAst.getLineNo() + "x" + detailAst.getColumnNo() + "]"; 184 } 185 186 /** 187 * Append the specified text. 188 * 189 * @param text the text to append 190 */ 191 private void append(String text) { 192 elements.add(text); 193 } 194 195 /** 196 * Append the specified token and also recalibrate the first line and 197 * column. 198 * 199 * @param ast the token to append 200 */ 201 private void append(DetailAST ast) { 202 elements.add(ast.getText()); 203 if (detailAst == null) { 204 detailAst = ast; 205 } 206 } 207 208}