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
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 * @since 8.16
098 */
099@StatelessCheck
100public class ClassMemberImpliedModifierCheck
101    extends AbstractCheck {
102
103    /**
104     * A key is pointing to the warning message text in "messages.properties" file.
105     */
106    public static final String MSG_KEY = "class.implied.modifier";
107
108    /** Name for 'static' keyword. */
109    private static final String STATIC_KEYWORD = "static";
110
111    /**
112     * Control whether to enforce that {@code static} is explicitly coded
113     * on nested enums in classes.
114     */
115    private boolean violateImpliedStaticOnNestedEnum = true;
116
117    /**
118     * Control whether to enforce that {@code static} is explicitly coded
119     * on nested interfaces in classes.
120     */
121    private boolean violateImpliedStaticOnNestedInterface = true;
122
123    /**
124     * Setter to control whether to enforce that {@code static} is explicitly coded
125     * on nested enums in classes.
126     * @param violateImplied
127     *        True to perform the check, false to turn the check off.
128     */
129    public void setViolateImpliedStaticOnNestedEnum(boolean violateImplied) {
130        violateImpliedStaticOnNestedEnum = violateImplied;
131    }
132
133    /**
134     * Setter to control whether to enforce that {@code static} is explicitly coded
135     * on nested interfaces in classes.
136     * @param violateImplied
137     *        True to perform the check, false to turn the check off.
138     */
139    public void setViolateImpliedStaticOnNestedInterface(boolean violateImplied) {
140        violateImpliedStaticOnNestedInterface = violateImplied;
141    }
142
143    @Override
144    public int[] getDefaultTokens() {
145        return getAcceptableTokens();
146    }
147
148    @Override
149    public int[] getRequiredTokens() {
150        return getAcceptableTokens();
151    }
152
153    @Override
154    public int[] getAcceptableTokens() {
155        return new int[] {
156            TokenTypes.INTERFACE_DEF,
157            TokenTypes.ENUM_DEF,
158        };
159    }
160
161    @Override
162    public void visitToken(DetailAST ast) {
163        if (ScopeUtil.isInClassBlock(ast) || ScopeUtil.isInEnumBlock(ast)) {
164            final DetailAST modifiers = ast.findFirstToken(TokenTypes.MODIFIERS);
165            switch (ast.getType()) {
166                case TokenTypes.ENUM_DEF:
167                    if (violateImpliedStaticOnNestedEnum
168                            && modifiers.findFirstToken(TokenTypes.LITERAL_STATIC) == null) {
169                        log(ast, MSG_KEY, STATIC_KEYWORD);
170                    }
171                    break;
172                case TokenTypes.INTERFACE_DEF:
173                    if (violateImpliedStaticOnNestedInterface
174                            && modifiers.findFirstToken(TokenTypes.LITERAL_STATIC) == null) {
175                        log(ast, MSG_KEY, STATIC_KEYWORD);
176                    }
177                    break;
178                default:
179                    throw new IllegalStateException(ast.toString());
180            }
181        }
182    }
183
184}