All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.thomasjensen.checkstyle.addons.checks.AbstractAddonsCheck Maven / Gradle / Ivy

The newest version!
package com.thomasjensen.checkstyle.addons.checks;
/*
 * Checkstyle-Addons - Additional Checkstyle checks
 * Copyright (c) 2015-2022, the Checkstyle Addons contributors
 *
 * This program is free software: you can redistribute it and/or modify it under the
 * terms of the GNU General Public License, version 3, as published by the Free
 * Software Foundation.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
 * PARTICULAR PURPOSE. See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along with this
 * program.  If not, see .
 */

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Deque;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;

import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
import com.puppycrawl.tools.checkstyle.api.DetailAST;
import com.puppycrawl.tools.checkstyle.api.TokenTypes;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;

import com.thomasjensen.checkstyle.addons.util.CheckstyleApiFixer;
import com.thomasjensen.checkstyle.addons.util.Util;


/**
 * Base class of all Checkstyle Addons checks.
 */
@SuppressFBWarnings("ACEM_ABSTRACT_CLASS_EMPTY_METHODS")
public abstract class AbstractAddonsCheck
    extends AbstractCheck
{
    private static final Set TOKENS = Collections.unmodifiableSet(new HashSet<>(Arrays
        .asList(TokenTypes.PACKAGE_DEF, TokenTypes.ENUM_DEF, TokenTypes.CLASS_DEF, TokenTypes.INTERFACE_DEF,
            TokenTypes.ANNOTATION_DEF)));

    /** binary name of the outer (or only) class */
    private BinaryName iOuterClassName = null;

    /** Map from binary class names to ASTs */
    private final Map iClassDeclarationPositions = new HashMap<>();

    /** CLASS_DEF/IDENTs as encountered */
    private final Deque iClassDefStack = new LinkedList<>();

    /** Encountered binary class names in the current Java file */
    private final Deque iBinaryNameStack = new LinkedList<>();

    /** Package that the currently checked class resides in */
    private String iMyPackage = null;

    private final CheckstyleApiFixer apiFixer;



    protected AbstractAddonsCheck()
    {
        this(null);
    }



    protected AbstractAddonsCheck(@Nullable final String pMockfile)
    {
        super();
        apiFixer = new CheckstyleApiFixer(this, pMockfile);
    }



    /**
     * The tokens which this check is interested in. Will be added to the tokens of the base class.
     *
     * @return the relevant tokens
     */
    public abstract Set getRelevantTokens();



    @Override
    public final int[] getRequiredTokens()
    {
        final Set tokens = new TreeSet<>();
        tokens.addAll(TOKENS);
        tokens.addAll(getRelevantTokens());

        final int[] result = new int[tokens.size()];
        int i = 0;
        for (Integer token : tokens) {
            result[i++] = token.intValue();
        }
        return result;
    }



    @Override
    public final int[] getAcceptableTokens()
    {
        return getRequiredTokens();
    }



    @Override
    public final int[] getDefaultTokens()
    {
        return getRequiredTokens();
    }



    @Override
    public void beginTree(final DetailAST pRootAst)
    {
        super.beginTree(pRootAst);

        iClassDeclarationPositions.clear();
        iBinaryNameStack.clear();
        iOuterClassName = null;
        iMyPackage = null;
        iClassDefStack.clear();
    }



    @Override
    public final void finishTree(final DetailAST pRootAst)
    {
        super.finishTree(pRootAst);
        finishTree(iOuterClassName, pRootAst);
    }



    /**
     * Called after a tree is fully processed. Ideal place to report on information collected whilst processing a tree.
     *
     * @param pOuterClassName the fully qualified class name of the outer class
     * @param pRootAst the root of the tree
     */
    @SuppressWarnings({"unused", "EmptyMethod"})
    protected void finishTree(@Nonnull final BinaryName pOuterClassName, @Nonnull final DetailAST pRootAst)
    {
        // optionally filled in by subclass
    }



    /**
     * Called to process a token.
     *
     * @param pBinaryClassName the currently active binary class name
     * @param pAst the token to process
     */
    @SuppressWarnings("unused")
    protected void visitToken(@Nullable final BinaryName pBinaryClassName, @Nonnull final DetailAST pAst)
    {
        // optionally filled in by subclass
    }



    /**
     * Called after all the child nodes have been processed.
     *
     * @param pBinaryClassName the currently active binary class name
     * @param pAst the token being completed
     */
    @SuppressWarnings("unused")
    protected void leaveToken(@Nullable final BinaryName pBinaryClassName, @Nonnull final DetailAST pAst)
    {
        // optionally filled in by subclass
    }



    /**
     * Called after visiting a CLASS_DEF, INTERFACE_DEF, ANNOTATION_DEF, or ENUM_DEF token and successfully determining
     * the type's binary class name. This is useful in a Java source file with nested inner classes. If the implementing
     * check registers for any of the above tokens, the regular call to {@link #visitToken(BinaryName, DetailAST)} will
     * be performed in addition to (after) this one.
     *
     * @param pBinaryClassName the binary class name of the visited type
     * @param pAst the token to process
     */
    @SuppressWarnings("unused")
    protected void visitKnownType(@Nonnull final BinaryName pBinaryClassName, @Nonnull final DetailAST pAst)
    {
        // optionally filled in by subclass
    }



    /**
     * Called after leaving a CLASS_DEF, INTERFACE_DEF, ANNOTATION_DEF, or ENUM_DEF token and the type's binary class
     * name is known. This is useful in a Java source file with nested inner classes. If the implementing check
     * registers for any of the above tokens, the regular call to {@link #leaveToken(BinaryName, DetailAST)} will be
     * performed in addition to (before) this one.
     *
     * @param pBinaryClassName the binary class name of the visited type
     * @param pAst the token being completed
     */
    @SuppressWarnings("unused")
    protected void leaveKnownType(@Nonnull final BinaryName pBinaryClassName, @Nonnull final DetailAST pAst)
    {
        // optionally filled in by subclass
    }



    @Override
    public final void visitToken(final DetailAST pAst)
    {
        super.visitToken(pAst);

        switch (pAst.getType()) {
            case TokenTypes.PACKAGE_DEF:
                final String pkg = Util.getFullIdent(pAst);
                iMyPackage = pkg != null ? pkg : "";
                break;
            case TokenTypes.CLASS_DEF: // fall through
            case TokenTypes.INTERFACE_DEF: // fall through
            case TokenTypes.ANNOTATION_DEF: // fall through
            case TokenTypes.ENUM_DEF:
                visitClassDef(pAst);
                visitKnownType(getCurrentBinaryName(), pAst);
                break;
            default:
                // some other token defined by the subclass
                break;
        }

        if (getRelevantTokens().contains(Integer.valueOf(pAst.getType()))) {
            visitToken(getCurrentBinaryName(), pAst);
        }
    }



    private void visitClassDef(final DetailAST pAst)
    {
        final String simpleName = Util.getFirstIdent(pAst);
        iClassDefStack.push(simpleName);

        final List names = new ArrayList<>(iClassDefStack);
        Collections.reverse(names);
        final BinaryName binaryName = new BinaryName(iMyPackage, names);

        iBinaryNameStack.push(binaryName);
        iClassDeclarationPositions.put(binaryName, pAst);
        if (iOuterClassName == null) {
            iOuterClassName = binaryName;
        }
    }



    @CheckForNull
    @SuppressWarnings("unused")
    protected DetailAST getClassDeclarationPosition(@Nonnull final BinaryName pBinaryName)
    {
        return iClassDeclarationPositions.get(pBinaryName);
    }



    @Override
    public final void leaveToken(final DetailAST pAst)
    {
        super.leaveToken(pAst);

        if (getRelevantTokens().contains(Integer.valueOf(pAst.getType()))) {
            leaveToken(getCurrentBinaryName(), pAst);
        }

        switch (pAst.getType()) {
            case TokenTypes.PACKAGE_DEF:
                // ignore
                break;
            case TokenTypes.CLASS_DEF: // fall through
            case TokenTypes.INTERFACE_DEF: // fall through
            case TokenTypes.ANNOTATION_DEF: // fall through
            case TokenTypes.ENUM_DEF:
                leaveKnownType(getCurrentBinaryName(), pAst);
                iClassDefStack.pop();
                iBinaryNameStack.pop();
                break;
            default:
                // some other token defined by subclass
                break;
        }
    }



    /**
     * Gets the binary name of the class currently being traversed on the AST. Normally, this is the single class in the
     * .java file, but it could also be a (nested) named inner class. Whenever a new CLASS_DEF or similar token is
     * visited, the result of this method would change.
     *
     * @return current binary name, or null if no CLASS_DEF or similar token has been encountered yet (e.g.
     * while we are still going through the import statements)
     */
    @CheckForNull
    protected BinaryName getCurrentBinaryName()
    {
        return iBinaryNameStack.peek();
    }



    /**
     * Gets the simple name of the current class, interface, annotation, or enum.
     *
     * @return the simple class name, or null if we are outside of a type definition
     */
    @CheckForNull
    protected String getCurrentSimpleName()
    {
        return iClassDefStack.peek();
    }



    @SuppressWarnings("unused")
    protected String getMyPackage()
    {
        return iMyPackage;
    }



    @Nonnull
    protected CheckstyleApiFixer getApiFixer()
    {
        return apiFixer;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy