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