001///////////////////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
003// Copyright (C) 2001-2026 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 * {@snippet lang="text" :
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 * }
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" file.
085     */
086    public static final String MSG_CONSTRUCTOR = "declaration.order.constructor";
087
088    /**
089     * A key is pointing to the warning message text in "messages.properties" file.
090     */
091    public static final String MSG_STATIC = "declaration.order.static";
092
093    /**
094     * A key is pointing to the warning message text in "messages.properties" file.
095     */
096    public static final String MSG_INSTANCE = "declaration.order.instance";
097
098    /**
099     * A key is pointing to the warning message text in "messages.properties" file.
100     */
101    public static final String MSG_ACCESS = "declaration.order.access";
102
103    /** State for the VARIABLE_DEF. */
104    private static final int STATE_STATIC_VARIABLE_DEF = 1;
105
106    /** State for the VARIABLE_DEF. */
107    private static final int STATE_INSTANCE_VARIABLE_DEF = 2;
108
109    /** State for the CTOR_DEF. */
110    private static final int STATE_CTOR_DEF = 3;
111
112    /** State for the METHOD_DEF. */
113    private static final int STATE_METHOD_DEF = 4;
114
115    /**
116     * List of Declaration States. This is necessary due to inner classes that have their own state.
117     */
118    private Deque<ScopeState> scopeStates;
119
120    /** Set of all class field names.*/
121    private Set<String> classFieldNames;
122
123    /** Control whether to ignore constructors. */
124    private boolean ignoreConstructors;
125    /** Control whether to ignore modifiers (fields, ...). */
126    private boolean ignoreModifiers;
127
128    /**
129     * Creates a new {@code DeclarationOrderCheck} instance.
130     */
131    public DeclarationOrderCheck() {
132        // no code by default
133    }
134
135    @Override
136    public int[] getDefaultTokens() {
137        return getRequiredTokens();
138    }
139
140    @Override
141    public int[] getAcceptableTokens() {
142        return getRequiredTokens();
143    }
144
145    @Override
146    public int[] getRequiredTokens() {
147        return new int[] {
148            TokenTypes.CTOR_DEF,
149            TokenTypes.METHOD_DEF,
150            TokenTypes.MODIFIERS,
151            TokenTypes.OBJBLOCK,
152            TokenTypes.VARIABLE_DEF,
153            TokenTypes.COMPACT_CTOR_DEF,
154        };
155    }
156
157    @Override
158    public void beginTree(DetailAST rootAST) {
159        scopeStates = new ArrayDeque<>();
160        classFieldNames = new HashSet<>();
161        scopeStates.push(new ScopeState());
162    }
163
164    @Override
165    public void visitToken(DetailAST ast) {
166        final int parentType = ast.getParent().getType();
167
168        switch (ast.getType()) {
169            case TokenTypes.OBJBLOCK -> scopeStates.push(new ScopeState());
170
171            case TokenTypes.MODIFIERS -> {
172                if (parentType == TokenTypes.VARIABLE_DEF
173                    && isTypeMemberContainer(ast.getParent().getParent().getType())) {
174                    processModifiers(ast);
175                }
176            }
177
178            case TokenTypes.CTOR_DEF, TokenTypes.COMPACT_CTOR_DEF -> {
179                if (parentType == TokenTypes.OBJBLOCK) {
180                    processConstructor(ast);
181                }
182            }
183
184            case TokenTypes.METHOD_DEF -> {
185                if (isTypeMemberContainer(parentType)) {
186                    final ScopeState state = scopeStates.peek();
187                    // nothing can be bigger than method's state
188                    state.currentScopeState = STATE_METHOD_DEF;
189                }
190            }
191
192            case TokenTypes.VARIABLE_DEF -> {
193                if (ScopeUtil.isClassFieldDef(ast)) {
194                    final DetailAST fieldDef = ast.findFirstToken(TokenTypes.IDENT);
195                    classFieldNames.add(fieldDef.getText());
196                }
197            }
198
199            default -> {
200                // do nothing
201            }
202        }
203    }
204
205    /**
206     * Checks whether the given token type is a container of class-level
207     * members: an object block, or the implicit class of a JEP 512 compact source file.
208     *
209     * @param type the token type to check
210     * @return true if the type holds class-level members
211     */
212    private static boolean isTypeMemberContainer(int type) {
213        return type == TokenTypes.OBJBLOCK
214                || type == TokenTypes.COMPACT_COMPILATION_UNIT;
215    }
216
217    /**
218     * Processes constructor.
219     *
220     * @param ast constructor AST.
221     */
222    private void processConstructor(DetailAST ast) {
223        final ScopeState state = scopeStates.peek();
224        if (state.currentScopeState > STATE_CTOR_DEF) {
225            if (!ignoreConstructors) {
226                log(ast, MSG_CONSTRUCTOR);
227            }
228        }
229        else {
230            state.currentScopeState = STATE_CTOR_DEF;
231        }
232    }
233
234    /**
235     * Processes modifiers.
236     *
237     * @param ast ast of Modifiers.
238     */
239    private void processModifiers(DetailAST ast) {
240        final ScopeState state = scopeStates.peek();
241        final boolean isStateValid = processModifiersState(ast, state);
242        processModifiersSubState(ast, state, isStateValid);
243    }
244
245    /**
246     * Process if given modifiers are appropriate in given state
247     * ({@code STATE_STATIC_VARIABLE_DEF}, {@code STATE_INSTANCE_VARIABLE_DEF},
248     * ({@code STATE_CTOR_DEF}, {@code STATE_METHOD_DEF}), if it is
249     * it updates states where appropriate or logs violation.
250     *
251     * @param modifierAst modifiers to process
252     * @param state current state
253     * @return true if modifierAst is valid in given state, false otherwise
254     */
255    private boolean processModifiersState(DetailAST modifierAst, ScopeState state) {
256        boolean isStateValid = true;
257        if (modifierAst.findFirstToken(TokenTypes.LITERAL_STATIC) == null) {
258            if (state.currentScopeState > STATE_INSTANCE_VARIABLE_DEF) {
259                isStateValid = false;
260                log(modifierAst, MSG_INSTANCE);
261            }
262            else if (state.currentScopeState == STATE_STATIC_VARIABLE_DEF) {
263                state.declarationAccess = Scope.PUBLIC;
264                state.currentScopeState = STATE_INSTANCE_VARIABLE_DEF;
265            }
266        }
267        else if (state.currentScopeState > STATE_INSTANCE_VARIABLE_DEF
268                || state.currentScopeState > STATE_STATIC_VARIABLE_DEF && !ignoreModifiers) {
269            isStateValid = false;
270            log(modifierAst, MSG_STATIC);
271        }
272        return isStateValid;
273    }
274
275    /**
276     * Checks if given modifiers are valid in substate of given
277     * state({@code Scope}), if it is it updates substate or else it logs violation.
278     *
279     * @param modifiersAst modifiers to process
280     * @param state current state
281     * @param isStateValid is main state for given modifiers is valid
282     */
283    private void processModifiersSubState(DetailAST modifiersAst, ScopeState state,
284                                          boolean isStateValid) {
285        final Scope access = ScopeUtil.getScopeFromMods(modifiersAst);
286        if (state.declarationAccess.compareTo(access) > 0) {
287            if (isStateValid
288                    && !ignoreModifiers
289                    && !isForwardReference(modifiersAst.getParent())) {
290                log(modifiersAst, MSG_ACCESS);
291            }
292        }
293        else {
294            state.declarationAccess = access;
295        }
296    }
297
298    /**
299     * Checks whether an identifier references a field which has been already defined in class.
300     *
301     * @param fieldDef a field definition.
302     * @return true if an identifier references a field which has been already defined in class.
303     */
304    private boolean isForwardReference(DetailAST fieldDef) {
305        final DetailAST exprStartIdent = fieldDef.findFirstToken(TokenTypes.IDENT);
306        final Set<DetailAST> exprIdents = getAllTokensOfType(exprStartIdent, TokenTypes.IDENT);
307        boolean forwardReference = false;
308        for (DetailAST ident : exprIdents) {
309            if (classFieldNames.contains(ident.getText())) {
310                forwardReference = true;
311                break;
312            }
313        }
314        return forwardReference;
315    }
316
317    /**
318     * Collects all tokens of specific type starting with the current ast node.
319     *
320     * @param ast ast node.
321     * @param tokenType token type.
322     * @return a set of all tokens of specific type starting with the current ast node.
323     */
324    private static Set<DetailAST> getAllTokensOfType(DetailAST ast, int tokenType) {
325        final Deque<DetailAST> stack = new ArrayDeque<>();
326        stack.push(ast);
327
328        final Set<DetailAST> result = new HashSet<>();
329
330        while (!stack.isEmpty()) {
331            final DetailAST current = stack.pop();
332            if (current.getType() == tokenType && !current.equals(ast)) {
333                result.add(current);
334            }
335
336            final DetailAST sibling = current.getNextSibling();
337            if (sibling != null) {
338                stack.push(sibling);
339            }
340
341            final DetailAST child = current.getFirstChild();
342            if (child != null) {
343                stack.push(child);
344            }
345        }
346        return result;
347    }
348
349    @Override
350    public void leaveToken(DetailAST ast) {
351        if (ast.getType() == TokenTypes.OBJBLOCK) {
352            scopeStates.pop();
353        }
354    }
355
356    /**
357     * Setter to control whether to ignore constructors.
358     *
359     * @param ignoreConstructors whether to ignore constructors.
360     * @since 5.2
361     */
362    public void setIgnoreConstructors(boolean ignoreConstructors) {
363        this.ignoreConstructors = ignoreConstructors;
364    }
365
366    /**
367     * Setter to control whether to ignore modifiers (fields, ...).
368     *
369     * @param ignoreModifiers whether to ignore modifiers.
370     * @since 5.2
371     */
372    public void setIgnoreModifiers(boolean ignoreModifiers) {
373        this.ignoreModifiers = ignoreModifiers;
374    }
375
376    /**
377     * Private class to encapsulate the state.
378     */
379    private static final class ScopeState {
380
381        /** The state the check is in. */
382        private int currentScopeState = STATE_STATIC_VARIABLE_DEF;
383
384        /** The sub-state the check is in. */
385        private Scope declarationAccess = Scope.PUBLIC;
386
387        /**
388         * Creates a new {@code ScopeState} instance.
389         */
390        private ScopeState() {
391            // no code by default
392        }
393
394    }
395
396}