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