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.site; 021 022import java.beans.Introspector; 023import java.util.Collections; 024import java.util.LinkedHashMap; 025import java.util.Map; 026import java.util.regex.Pattern; 027 028import com.puppycrawl.tools.checkstyle.FileStatefulCheck; 029import com.puppycrawl.tools.checkstyle.api.DetailAST; 030import com.puppycrawl.tools.checkstyle.api.DetailNode; 031import com.puppycrawl.tools.checkstyle.api.JavadocTokenTypes; 032import com.puppycrawl.tools.checkstyle.api.TokenTypes; 033import com.puppycrawl.tools.checkstyle.checks.javadoc.AbstractJavadocCheck; 034import com.puppycrawl.tools.checkstyle.utils.BlockCommentPosition; 035 036/** 037 * Class for scraping class javadoc and all property setter javadocs from the 038 * given checkstyle module. 039 */ 040@FileStatefulCheck 041public class ClassAndPropertiesSettersJavadocScraper extends AbstractJavadocCheck { 042 043 /** 044 * Map of scraped javadocs - name of property, javadoc detail node. 045 * The class javadoc is stored too, with the key being the module name. 046 */ 047 private static final Map<String, DetailNode> JAVADOC_FOR_MODULE_OR_PROPERTY = 048 new LinkedHashMap<>(); 049 050 /** Name of the module being scraped. */ 051 private static String moduleName = ""; 052 053 /** 054 * Initialize the scraper. Clears static context and sets the module name. 055 * 056 * @param newModuleName the module name. 057 */ 058 public static void initialize(String newModuleName) { 059 JAVADOC_FOR_MODULE_OR_PROPERTY.clear(); 060 moduleName = newModuleName; 061 } 062 063 /** 064 * Get the module or property javadocs map. 065 * 066 * @return the javadocs. 067 */ 068 public static Map<String, DetailNode> getJavadocsForModuleOrProperty() { 069 return Collections.unmodifiableMap(JAVADOC_FOR_MODULE_OR_PROPERTY); 070 } 071 072 @Override 073 public int[] getDefaultJavadocTokens() { 074 return new int[] { 075 JavadocTokenTypes.JAVADOC, 076 }; 077 } 078 079 @Override 080 public void visitJavadocToken(DetailNode ast) { 081 final DetailAST blockCommentAst = getBlockCommentAst(); 082 if (BlockCommentPosition.isOnMethod(blockCommentAst)) { 083 final DetailAST methodDef = getParentAst(blockCommentAst, TokenTypes.METHOD_DEF); 084 if (methodDef != null 085 && isSetterMethod(methodDef) 086 && isMethodOfScrapedModule(methodDef)) { 087 final String methodName = methodDef.findFirstToken(TokenTypes.IDENT).getText(); 088 final String propertyName = getPropertyName(methodName); 089 JAVADOC_FOR_MODULE_OR_PROPERTY.put(propertyName, ast); 090 } 091 092 } 093 else if (BlockCommentPosition.isOnClass(blockCommentAst)) { 094 final DetailAST classDef = getParentAst(blockCommentAst, TokenTypes.CLASS_DEF); 095 if (classDef != null) { 096 final String className = classDef.findFirstToken(TokenTypes.IDENT).getText(); 097 if (className.equals(moduleName)) { 098 JAVADOC_FOR_MODULE_OR_PROPERTY.put(moduleName, ast); 099 } 100 } 101 } 102 } 103 104 /** 105 * Checks if the given method is a method of the module being scraped. Traverses 106 * parent nodes until it finds the class definition and checks if the class name 107 * is the same as the module name. We want to avoid scraping javadocs from 108 * inner classes. 109 * 110 * @param methodDef the method definition. 111 * @return true if the method is a method of the given module, false otherwise. 112 */ 113 private static boolean isMethodOfScrapedModule(DetailAST methodDef) { 114 final DetailAST classDef = getParentAst(methodDef, TokenTypes.CLASS_DEF); 115 116 boolean isMethodOfModule = false; 117 if (classDef != null) { 118 final String className = classDef.findFirstToken(TokenTypes.IDENT).getText(); 119 isMethodOfModule = className.equals(moduleName); 120 } 121 122 return isMethodOfModule; 123 } 124 125 /** 126 * Get the parent node of the given type. Traverses up the tree until it finds 127 * the given type. 128 * 129 * @param ast the node to start traversing from. 130 * @param type the type of the parent node to find. 131 * @return the parent node of the given type, or null if not found. 132 */ 133 private static DetailAST getParentAst(DetailAST ast, int type) { 134 DetailAST node = ast.getParent(); 135 136 while (node != null && node.getType() != type) { 137 node = node.getParent(); 138 } 139 140 return node; 141 } 142 143 /** 144 * Get the property name from the setter method name. For example, getPropertyName("setFoo") 145 * returns "foo". This method removes the "set" prefix and decapitalizes the first letter 146 * of the property name. 147 * 148 * @param setterName the setter method name. 149 * @return the property name. 150 */ 151 private static String getPropertyName(String setterName) { 152 return Introspector.decapitalize(setterName.substring("set".length())); 153 } 154 155 /** 156 * Returns whether an AST represents a setter method. 157 * 158 * @param ast the AST to check with 159 * @return whether the AST represents a setter method 160 */ 161 private static boolean isSetterMethod(DetailAST ast) { 162 boolean setterMethod = false; 163 164 if (ast.getType() == TokenTypes.METHOD_DEF) { 165 final DetailAST type = ast.findFirstToken(TokenTypes.TYPE); 166 final String name = type.getNextSibling().getText(); 167 final Pattern setterPattern = Pattern.compile("^set[A-Z].*"); 168 169 setterMethod = setterPattern.matcher(name).matches(); 170 } 171 return setterMethod; 172 } 173}