001///////////////////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
003// Copyright (C) 2001-2025 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.coding;
021
022import java.util.ArrayDeque;
023import java.util.Deque;
024import java.util.HashSet;
025import java.util.Set;
026
027import com.puppycrawl.tools.checkstyle.FileStatefulCheck;
028import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
029import com.puppycrawl.tools.checkstyle.api.DetailAST;
030import com.puppycrawl.tools.checkstyle.api.Scope;
031import com.puppycrawl.tools.checkstyle.api.TokenTypes;
032import com.puppycrawl.tools.checkstyle.utils.ScopeUtil;
033
034/**
035 * <div>
036 * Checks that the parts of a class, record, or interface declaration appear in the order
037 * suggested by the
038 * <a href="https://checkstyle.org/styleguides/sun-code-conventions-19990420/CodeConventions.doc2.html#a1852">
039 * Code Conventions for the Java Programming Language</a>.
040 * </div>
041 *
042 * <p>
043 * According to
044 * <a href="https://checkstyle.org/styleguides/sun-code-conventions-19990420/CodeConventions.doc2.html#a1852">
045 * Code Conventions for the Java Programming Language</a>, the parts of a class
046 * or interface declaration should appear in the following order:
047 * </p>
048 * <ol>
049 * <li>
050 * Class (static) variables. First the public class variables, then
051 * protected, then package level (no access modifier), and then private.
052 * </li>
053 * <li> Instance variables. First the public class variables, then
054 * protected, then package level (no access modifier), and then private.
055 * </li>
056 * <li> Constructors </li>
057 * <li> Methods </li>
058 * </ol>
059 *
060 * <p>
061 * Purpose of <b>ignore*</b> option is to ignore related violations,
062 * however it still impacts on other class members.
063 * </p>
064 *
065 * <p>ATTENTION: the check skips class fields which have
066 * <a href="https://docs.oracle.com/javase/specs/jls/se11/html/jls-8.html#jls-8.3.3">
067 * forward references </a> from validation due to the fact that we have Checkstyle's limitations
068 * to clearly detect user intention of fields location and grouping. For example:
069 * </p>
070 * <div class="wrapper"><pre class="prettyprint"><code class="language-java">
071 * public class A {
072 *   private double x = 1.0;
073 *   private double y = 2.0;
074 *   public double slope = x / y; // will be skipped from validation due to forward reference
075 * }
076 * </code></pre></div>
077 *
078 * @since 3.2
079 */
080@FileStatefulCheck
081public class DeclarationOrderCheck extends AbstractCheck {
082
083    /**
084     * A key is pointing to the warning message text in "messages.properties"
085     * file.
086     */
087    public static final String MSG_CONSTRUCTOR = "declaration.order.constructor";
088
089    /**
090     * A key is pointing to the warning message text in "messages.properties"
091     * file.
092     */
093    public static final String MSG_STATIC = "declaration.order.static";
094
095    /**
096     * A key is pointing to the warning message text in "messages.properties"
097     * file.
098     */
099    public static final String MSG_INSTANCE = "declaration.order.instance";
100
101    /**
102     * A key is pointing to the warning message text in "messages.properties"
103     * file.
104     */
105    public static final String MSG_ACCESS = "declaration.order.access";
106
107    /** State for the VARIABLE_DEF. */
108    private static final int STATE_STATIC_VARIABLE_DEF = 1;
109
110    /** State for the VARIABLE_DEF. */
111    private static final int STATE_INSTANCE_VARIABLE_DEF = 2;
112
113    /** State for the CTOR_DEF. */
114    private static final int STATE_CTOR_DEF = 3;
115
116    /** State for the METHOD_DEF. */
117    private static final int STATE_METHOD_DEF = 4;
118
119    /**
120     * List of Declaration States. This is necessary due to
121     * inner classes that have their own state.
122     */
123    private Deque<ScopeState> scopeStates;
124
125    /** Set of all class field names.*/
126    private Set<String> classFieldNames;
127
128    /** Control whether to ignore constructors. */
129    private boolean ignoreConstructors;
130    /** Control whether to ignore modifiers (fields, ...). */
131    private boolean ignoreModifiers;
132
133    @Override
134    public int[] getDefaultTokens() {
135        return getRequiredTokens();
136    }
137
138    @Override
139    public int[] getAcceptableTokens() {
140        return getRequiredTokens();
141    }
142
143    @Override
144    public int[] getRequiredTokens() {
145        return new int[] {
146            TokenTypes.CTOR_DEF,
147            TokenTypes.METHOD_DEF,
148            TokenTypes.MODIFIERS,
149            TokenTypes.OBJBLOCK,
150            TokenTypes.VARIABLE_DEF,
151            TokenTypes.COMPACT_CTOR_DEF,
152        };
153    }
154
155    @Override
156    public void beginTree(DetailAST rootAST) {
157        scopeStates = new ArrayDeque<>();
158        classFieldNames = new HashSet<>();
159    }
160
161    @Override
162    public void visitToken(DetailAST ast) {
163        final int parentType = ast.getParent().getType();
164
165        switch (ast.getType()) {
166            case TokenTypes.OBJBLOCK -> scopeStates.push(new ScopeState());
167
168            case TokenTypes.MODIFIERS -> {
169                if (parentType == TokenTypes.VARIABLE_DEF
170                    && ast.getParent().getParent().getType() == TokenTypes.OBJBLOCK) {
171                    processModifiers(ast);
172                }
173            }
174
175            case TokenTypes.CTOR_DEF, TokenTypes.COMPACT_CTOR_DEF -> {
176                if (parentType == TokenTypes.OBJBLOCK) {
177                    processConstructor(ast);
178                }
179            }
180
181            case TokenTypes.METHOD_DEF -> {
182                if (parentType == TokenTypes.OBJBLOCK) {
183                    final ScopeState state = scopeStates.peek();
184                    // nothing can be bigger than method's state
185                    state.currentScopeState = STATE_METHOD_DEF;
186                }
187            }
188
189            case TokenTypes.VARIABLE_DEF -> {
190                if (ScopeUtil.isClassFieldDef(ast)) {
191                    final DetailAST fieldDef = ast.findFirstToken(TokenTypes.IDENT);
192                    classFieldNames.add(fieldDef.getText());
193                }
194            }
195
196            default -> {
197                // do nothing
198            }
199        }
200    }
201
202    /**
203     * Processes constructor.
204     *
205     * @param ast constructor AST.
206     */
207    private void processConstructor(DetailAST ast) {
208        final ScopeState state = scopeStates.peek();
209        if (state.currentScopeState > STATE_CTOR_DEF) {
210            if (!ignoreConstructors) {
211                log(ast, MSG_CONSTRUCTOR);
212            }
213        }
214        else {
215            state.currentScopeState = STATE_CTOR_DEF;
216        }
217    }
218
219    /**
220     * Processes modifiers.
221     *
222     * @param ast ast of Modifiers.
223     */
224    private void processModifiers(DetailAST ast) {
225        final ScopeState state = scopeStates.peek();
226        final boolean isStateValid = processModifiersState(ast, state);
227        processModifiersSubState(ast, state, isStateValid);
228    }
229
230    /**
231     * Process if given modifiers are appropriate in given state
232     * ({@code STATE_STATIC_VARIABLE_DEF}, {@code STATE_INSTANCE_VARIABLE_DEF},
233     * ({@code STATE_CTOR_DEF}, {@code STATE_METHOD_DEF}), if it is
234     * it updates states where appropriate or logs violation.
235     *
236     * @param modifierAst modifiers to process
237     * @param state current state
238     * @return true if modifierAst is valid in given state, false otherwise
239     */
240    private boolean processModifiersState(DetailAST modifierAst, ScopeState state) {
241        boolean isStateValid = true;
242        if (modifierAst.findFirstToken(TokenTypes.LITERAL_STATIC) == null) {
243            if (state.currentScopeState > STATE_INSTANCE_VARIABLE_DEF) {
244                isStateValid = false;
245                log(modifierAst, MSG_INSTANCE);
246            }
247            else if (state.currentScopeState == STATE_STATIC_VARIABLE_DEF) {
248                state.declarationAccess = Scope.PUBLIC;
249                state.currentScopeState = STATE_INSTANCE_VARIABLE_DEF;
250            }
251        }
252        else if (state.currentScopeState > STATE_INSTANCE_VARIABLE_DEF
253                || state.currentScopeState > STATE_STATIC_VARIABLE_DEF && !ignoreModifiers) {
254            isStateValid = false;
255            log(modifierAst, MSG_STATIC);
256        }
257        return isStateValid;
258    }
259
260    /**
261     * Checks if given modifiers are valid in substate of given
262     * state({@code Scope}), if it is it updates substate or else it
263     * logs violation.
264     *
265     * @param modifiersAst modifiers to process
266     * @param state current state
267     * @param isStateValid is main state for given modifiers is valid
268     */
269    private void processModifiersSubState(DetailAST modifiersAst, ScopeState state,
270                                          boolean isStateValid) {
271        final Scope access = ScopeUtil.getScopeFromMods(modifiersAst);
272        if (state.declarationAccess.compareTo(access) > 0) {
273            if (isStateValid
274                    && !ignoreModifiers
275                    && !isForwardReference(modifiersAst.getParent())) {
276                log(modifiersAst, MSG_ACCESS);
277            }
278        }
279        else {
280            state.declarationAccess = access;
281        }
282    }
283
284    /**
285     * Checks whether an identifier references a field which has been already defined in class.
286     *
287     * @param fieldDef a field definition.
288     * @return true if an identifier references a field which has been already defined in class.
289     */
290    private boolean isForwardReference(DetailAST fieldDef) {
291        final DetailAST exprStartIdent = fieldDef.findFirstToken(TokenTypes.IDENT);
292        final Set<DetailAST> exprIdents = getAllTokensOfType(exprStartIdent, TokenTypes.IDENT);
293        boolean forwardReference = false;
294        for (DetailAST ident : exprIdents) {
295            if (classFieldNames.contains(ident.getText())) {
296                forwardReference = true;
297                break;
298            }
299        }
300        return forwardReference;
301    }
302
303    /**
304     * Collects all tokens of specific type starting with the current ast node.
305     *
306     * @param ast ast node.
307     * @param tokenType token type.
308     * @return a set of all tokens of specific type starting with the current ast node.
309     */
310    private static Set<DetailAST> getAllTokensOfType(DetailAST ast, int tokenType) {
311        DetailAST vertex = ast;
312        final Set<DetailAST> result = new HashSet<>();
313        final Deque<DetailAST> stack = new ArrayDeque<>();
314        while (vertex != null || !stack.isEmpty()) {
315            if (!stack.isEmpty()) {
316                vertex = stack.pop();
317            }
318            while (vertex != null) {
319                if (vertex.getType() == tokenType && !vertex.equals(ast)) {
320                    result.add(vertex);
321                }
322                if (vertex.getNextSibling() != null) {
323                    stack.push(vertex.getNextSibling());
324                }
325                vertex = vertex.getFirstChild();
326            }
327        }
328        return result;
329    }
330
331    @Override
332    public void leaveToken(DetailAST ast) {
333        if (ast.getType() == TokenTypes.OBJBLOCK) {
334            scopeStates.pop();
335        }
336    }
337
338    /**
339     * Setter to control whether to ignore constructors.
340     *
341     * @param ignoreConstructors whether to ignore constructors.
342     * @since 5.2
343     */
344    public void setIgnoreConstructors(boolean ignoreConstructors) {
345        this.ignoreConstructors = ignoreConstructors;
346    }
347
348    /**
349     * Setter to control whether to ignore modifiers (fields, ...).
350     *
351     * @param ignoreModifiers whether to ignore modifiers.
352     * @since 5.2
353     */
354    public void setIgnoreModifiers(boolean ignoreModifiers) {
355        this.ignoreModifiers = ignoreModifiers;
356    }
357
358    /**
359     * Private class to encapsulate the state.
360     */
361    private static final class ScopeState {
362
363        /** The state the check is in. */
364        private int currentScopeState = STATE_STATIC_VARIABLE_DEF;
365
366        /** The sub-state the check is in. */
367        private Scope declarationAccess = Scope.PUBLIC;
368
369    }
370
371}