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.design;
021
022import java.util.Arrays;
023import java.util.List;
024
025import com.puppycrawl.tools.checkstyle.FileStatefulCheck;
026import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
027import com.puppycrawl.tools.checkstyle.api.DetailAST;
028import com.puppycrawl.tools.checkstyle.api.TokenTypes;
029import com.puppycrawl.tools.checkstyle.utils.ScopeUtil;
030
031/**
032 * <p>
033 * Checks nested (internal) classes/interfaces are declared at the bottom of the
034 * primary (top-level) class after all init and static init blocks,
035 * method, constructor and field declarations.
036 * </p>
037 * <p>
038 * To configure the check:
039 * </p>
040 * <pre>
041 * &lt;module name=&quot;InnerTypeLast&quot;/&gt;
042 * </pre>
043 * <p>Example:</p>
044 * <pre>
045 * class Test {
046 *     private String s; // OK
047 *     class InnerTest1 {}
048 *     public void test() {} // violation, method should be declared before inner types.
049 * }
050 *
051 * class Test2 {
052 *     static {}; // OK
053 *     class InnerTest1 {}
054 *     public Test2() {} // violation, constructor should be declared before inner types.
055 * }
056 *
057 * class Test3 {
058 *     private String s; // OK
059 *     public void test() {} // OK
060 *     class InnerTest1 {}
061 * }
062 * </pre>
063 * <p>
064 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
065 * </p>
066 * <p>
067 * Violation Message Keys:
068 * </p>
069 * <ul>
070 * <li>
071 * {@code arrangement.members.before.inner}
072 * </li>
073 * </ul>
074 *
075 * @since 5.2
076 */
077@FileStatefulCheck
078public class InnerTypeLastCheck extends AbstractCheck {
079
080    /**
081     * A key is pointing to the warning message text in "messages.properties"
082     * file.
083     */
084    public static final String MSG_KEY = "arrangement.members.before.inner";
085
086    /** List of class member tokens. */
087    private static final List<Integer> CLASS_MEMBER_TOKENS = Arrays.asList(
088            TokenTypes.VARIABLE_DEF,
089            TokenTypes.METHOD_DEF,
090            TokenTypes.CTOR_DEF,
091            TokenTypes.INSTANCE_INIT,
092            TokenTypes.STATIC_INIT,
093            TokenTypes.COMPACT_CTOR_DEF
094    );
095
096    /** Meet a root class. */
097    private boolean rootClass = true;
098
099    @Override
100    public int[] getDefaultTokens() {
101        return getRequiredTokens();
102    }
103
104    @Override
105    public int[] getAcceptableTokens() {
106        return getRequiredTokens();
107    }
108
109    @Override
110    public int[] getRequiredTokens() {
111        return new int[] {
112            TokenTypes.CLASS_DEF,
113            TokenTypes.INTERFACE_DEF,
114            TokenTypes.RECORD_DEF,
115        };
116    }
117
118    @Override
119    public void beginTree(DetailAST rootAST) {
120        rootClass = true;
121    }
122
123    @Override
124    public void visitToken(DetailAST ast) {
125        // First root class
126        if (rootClass) {
127            rootClass = false;
128        }
129        else {
130            DetailAST nextSibling = ast.getNextSibling();
131            while (nextSibling != null) {
132                if (!ScopeUtil.isInCodeBlock(ast)
133                    && CLASS_MEMBER_TOKENS.contains(nextSibling.getType())) {
134                    log(nextSibling, MSG_KEY);
135                }
136                nextSibling = nextSibling.getNextSibling();
137            }
138        }
139    }
140
141    @Override
142    public void leaveToken(DetailAST ast) {
143        // Is this a root class
144        if (ast.getParent() == null) {
145            rootClass = true;
146        }
147    }
148
149}