001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2020 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.checks.modifier;
021
022import com.puppycrawl.tools.checkstyle.StatelessCheck;
023import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
024import com.puppycrawl.tools.checkstyle.api.DetailAST;
025import com.puppycrawl.tools.checkstyle.api.TokenTypes;
026import com.puppycrawl.tools.checkstyle.utils.ScopeUtil;
027
028/**
029 * <p>
030 * Checks for implicit modifiers on nested types in classes.
031 * </p>
032 * <p>
033 * This check is effectively the opposite of
034 * <a href="https://checkstyle.org/config_modifier.html#RedundantModifier">RedundantModifier</a>.
035 * It checks the modifiers on nested types in classes, ensuring that certain modifiers are
036 * explicitly specified even though they are actually redundant.
037 * </p>
038 * <p>
039 * Nested enums and interfaces within a class are always {@code static} and as such the compiler
040 * does not require the {@code static} modifier. This check provides the ability to enforce that
041 * the {@code static} modifier is explicitly coded and not implicitly added by the compiler.
042 * </p>
043 * <pre>
044 * public final class Person {
045 *   enum Age {  // violation
046 *     CHILD, ADULT
047 *   }
048 * }
049 * </pre>
050 * <p>
051 * Rationale for this check: Nested enums and interfaces are treated differently from nested
052 * classes as they are only allowed to be {@code static}. Developers should not need to remember
053 * this rule, and this check provides the means to enforce that the modifier is coded explicitly.
054 * </p>
055 * <ul>
056 * <li>
057 * Property {@code violateImpliedStaticOnNestedEnum} - Control whether to enforce that
058 * {@code static} is explicitly coded on nested enums in classes.
059 * Default value is {@code true}.
060 * </li>
061 * <li>
062 * Property {@code violateImpliedStaticOnNestedInterface} - Control whether to enforce that
063 * {@code static} is explicitly coded on nested interfaces in classes.
064 * Default value is {@code true}.
065 * </li>
066 * </ul>
067 * <p>
068 * This example checks that all implicit modifiers on nested interfaces and enums are
069 * explicitly specified in classes.
070 * </p>
071 * <p>
072 * Configuration:
073 * </p>
074 * <pre>
075 * &lt;module name="ClassMemberImpliedModifier" /&gt;
076 * </pre>
077 * <p>
078 * Code:
079 * </p>
080 * <pre>
081 * public final class Person {
082 *   static interface Address1 {  // valid
083 *   }
084 *
085 *   interface Address2 {  // violation
086 *   }
087 *
088 *   static enum Age1 {  // valid
089 *     CHILD, ADULT
090 *   }
091 *
092 *   enum Age2 {  // violation
093 *     CHILD, ADULT
094 *   }
095 * }
096 * </pre>
097 *
098 * @since 8.16
099 */
100@StatelessCheck
101public class ClassMemberImpliedModifierCheck
102    extends AbstractCheck {
103
104    /**
105     * A key is pointing to the warning message text in "messages.properties" file.
106     */
107    public static final String MSG_KEY = "class.implied.modifier";
108
109    /** Name for 'static' keyword. */
110    private static final String STATIC_KEYWORD = "static";
111
112    /**
113     * Control whether to enforce that {@code static} is explicitly coded
114     * on nested enums in classes.
115     */
116    private boolean violateImpliedStaticOnNestedEnum = true;
117
118    /**
119     * Control whether to enforce that {@code static} is explicitly coded
120     * on nested interfaces in classes.
121     */
122    private boolean violateImpliedStaticOnNestedInterface = true;
123
124    /**
125     * Setter to control whether to enforce that {@code static} is explicitly coded
126     * on nested enums in classes.
127     *
128     * @param violateImplied
129     *        True to perform the check, false to turn the check off.
130     */
131    public void setViolateImpliedStaticOnNestedEnum(boolean violateImplied) {
132        violateImpliedStaticOnNestedEnum = violateImplied;
133    }
134
135    /**
136     * Setter to control whether to enforce that {@code static} is explicitly coded
137     * on nested interfaces in classes.
138     *
139     * @param violateImplied
140     *        True to perform the check, false to turn the check off.
141     */
142    public void setViolateImpliedStaticOnNestedInterface(boolean violateImplied) {
143        violateImpliedStaticOnNestedInterface = violateImplied;
144    }
145
146    @Override
147    public int[] getDefaultTokens() {
148        return getAcceptableTokens();
149    }
150
151    @Override
152    public int[] getRequiredTokens() {
153        return getAcceptableTokens();
154    }
155
156    @Override
157    public int[] getAcceptableTokens() {
158        return new int[] {
159            TokenTypes.INTERFACE_DEF,
160            TokenTypes.ENUM_DEF,
161        };
162    }
163
164    @Override
165    public void visitToken(DetailAST ast) {
166        if (ScopeUtil.isInClassBlock(ast) || ScopeUtil.isInEnumBlock(ast)) {
167            final DetailAST modifiers = ast.findFirstToken(TokenTypes.MODIFIERS);
168            switch (ast.getType()) {
169                case TokenTypes.ENUM_DEF:
170                    if (violateImpliedStaticOnNestedEnum
171                            && modifiers.findFirstToken(TokenTypes.LITERAL_STATIC) == null) {
172                        log(ast, MSG_KEY, STATIC_KEYWORD);
173                    }
174                    break;
175                case TokenTypes.INTERFACE_DEF:
176                    if (violateImpliedStaticOnNestedInterface
177                            && modifiers.findFirstToken(TokenTypes.LITERAL_STATIC) == null) {
178                        log(ast, MSG_KEY, STATIC_KEYWORD);
179                    }
180                    break;
181                default:
182                    throw new IllegalStateException(ast.toString());
183            }
184        }
185    }
186
187}