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}