1 /////////////////////////////////////////////////////////////////////////////////////////////// 2 // checkstyle: Checks Java source code and other text files for adherence to a set of rules. 3 // Copyright (C) 2001-2024 the original author or authors. 4 // 5 // This library is free software; you can redistribute it and/or 6 // modify it under the terms of the GNU Lesser General Public 7 // License as published by the Free Software Foundation; either 8 // version 2.1 of the License, or (at your option) any later version. 9 // 10 // This library is distributed in the hope that it will be useful, 11 // but WITHOUT ANY WARRANTY; without even the implied warranty of 12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 13 // Lesser General Public License for more details. 14 // 15 // You should have received a copy of the GNU Lesser General Public 16 // License along with this library; if not, write to the Free Software 17 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 18 /////////////////////////////////////////////////////////////////////////////////////////////// 19 20 package com.puppycrawl.tools.checkstyle.api; 21 22 import java.util.ArrayList; 23 import java.util.List; 24 25 /** 26 * Represents a full identifier, including dots, with associated 27 * position information. 28 * 29 * <p> 30 * Identifiers such as {@code java.util.HashMap} are spread across 31 * multiple AST nodes in the syntax tree (three IDENT nodes, two DOT nodes). 32 * A FullIdent represents the whole String (excluding any intermediate 33 * whitespace), which is often easier to work with in Checks. 34 * </p> 35 * 36 * @see TokenTypes#DOT 37 * @see TokenTypes#IDENT 38 **/ 39 public final class FullIdent { 40 41 /** The list holding subsequent elements of identifier. **/ 42 private final List<String> elements = new ArrayList<>(); 43 /** The topmost and leftmost AST of the full identifier. */ 44 private DetailAST detailAst; 45 46 /** Hide default constructor. */ 47 private FullIdent() { 48 } 49 50 /** 51 * Creates a new FullIdent starting from the child of the specified node. 52 * 53 * @param ast the parent node from where to start from 54 * @return a {@code FullIdent} value 55 */ 56 public static FullIdent createFullIdentBelow(DetailAST ast) { 57 return createFullIdent(ast.getFirstChild()); 58 } 59 60 /** 61 * Creates a new FullIdent starting from the specified node. 62 * 63 * @param ast the node to start from 64 * @return a {@code FullIdent} value 65 */ 66 public static FullIdent createFullIdent(DetailAST ast) { 67 final FullIdent ident = new FullIdent(); 68 extractFullIdent(ident, ast); 69 return ident; 70 } 71 72 /** 73 * Recursively extract a FullIdent. 74 * 75 * @param full the FullIdent to add to 76 * @param ast the node to recurse from 77 * @noinspection TailRecursion 78 * @noinspectionreason TailRecursion - until issue #14814 79 */ 80 private static void extractFullIdent(FullIdent full, DetailAST ast) { 81 if (ast != null) { 82 final DetailAST nextSibling = ast.getNextSibling(); 83 84 // Here we want type declaration, but not initialization 85 final boolean isArrayTypeDeclarationStart = nextSibling != null 86 && (nextSibling.getType() == TokenTypes.ARRAY_DECLARATOR 87 || nextSibling.getType() == TokenTypes.ANNOTATIONS) 88 && isArrayTypeDeclaration(nextSibling); 89 90 final int typeOfAst = ast.getType(); 91 if (typeOfAst == TokenTypes.LITERAL_NEW 92 && ast.hasChildren()) { 93 final DetailAST firstChild = ast.getFirstChild(); 94 extractFullIdent(full, firstChild); 95 } 96 else if (typeOfAst == TokenTypes.DOT) { 97 final DetailAST firstChild = ast.getFirstChild(); 98 extractFullIdent(full, firstChild); 99 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 }