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.modifier;
021
022import java.util.ArrayList;
023import java.util.Arrays;
024import java.util.Collections;
025import java.util.Iterator;
026import java.util.LinkedHashSet;
027import java.util.List;
028import java.util.Set;
029
030import com.puppycrawl.tools.checkstyle.FileStatefulCheck;
031import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
032import com.puppycrawl.tools.checkstyle.api.DetailAST;
033import com.puppycrawl.tools.checkstyle.api.TokenTypes;
034
035/**
036 * <div>
037 * Validates that the modifiers appear in the correct, standard order.
038 * By default the order of modifiers conforms to the suggestions in the
039 * <a href="https://docs.oracle.com/javase/specs/jls/se21/html/jls-8.html">
040 * Java Language specification, &#167; 8.1.1, 8.3.1, 8.4.3</a> and
041 * <a href="https://docs.oracle.com/javase/specs/jls/se21/html/jls-9.html#jls-9.4">9.4</a>.
042 * The default order is:
043 * </div>
044 *
045 * <ol>
046 * <li> {@code public} </li>
047 * <li> {@code protected} </li>
048 * <li> {@code private} </li>
049 * <li> {@code abstract} </li>
050 * <li> {@code default} </li>
051 * <li> {@code static} </li>
052 * <li> {@code sealed} </li>
053 * <li> {@code non-sealed} </li>
054 * <li> {@code final} </li>
055 * <li> {@code transient} </li>
056 * <li> {@code volatile} </li>
057 * <li> {@code synchronized} </li>
058 * <li> {@code native} </li>
059 * <li> {@code strictfp} </li>
060 * </ol>
061 *
062 * <p>
063 * Additionally, modifiers are checked to ensure all annotations
064 * are declared before all other modifiers.
065 * </p>
066 *
067 * <p>
068 * Rationale: Code is easier to read if everybody follows
069 * a standard.
070 * </p>
071 *
072 * <p>
073 * ATTENTION: We skip
074 * <a href="https://www.oracle.com/technical-resources/articles/java/ma14-architect-annotations.html">
075 * type annotations</a> from validation.
076 * </p>
077 *
078 * @since 3.0
079 */
080@FileStatefulCheck
081public class ModifierOrderCheck
082    extends AbstractCheck {
083
084    /**
085     * A key is pointing to the warning message text in "messages.properties"
086     * file.
087     */
088    public static final String MSG_ANNOTATION_ORDER = "annotation.order";
089
090    /**
091     * A key is pointing to the warning message text in "messages.properties"
092     * file.
093     */
094    public static final String MSG_MODIFIER_ORDER = "mod.order";
095
096    /**
097     * A key is pointing to the warning message text in "messages.properties"
098     * file.
099     */
100    public static final String MSG_MODIFIER_CUSTOM_ORDER = "mod.custom.order";
101
102    /**
103     * The order of modifiers as suggested in sections 8.1.1,
104     * 8.3.1 and 8.4.3 of the JLS.
105     */
106    private static final String[] JLS_ORDER = {
107        "public", "protected", "private", "abstract", "default", "static",
108        "sealed", "non-sealed", "final", "transient", "volatile",
109        "synchronized", "native", "strictfp",
110    };
111
112    /**
113     * To specify the order of modifiers.
114     */
115    private String[] modifiersOrder = JLS_ORDER;
116
117    /**
118     * Indicates if the order of modifiers is custom.
119     */
120    private boolean isCustomOrder;
121
122    /**
123     * Creates a new {@code ModifierOrderCheck} instance.
124     */
125    public ModifierOrderCheck() {
126        // no code by default
127    }
128
129    /**
130     * Setter to set the order of modifiers.
131     *
132     * @param modifierOrder the order of modifiers
133     * @since 14.2.0
134     */
135    public void setModifiersOrder(String... modifierOrder) {
136        if (!Arrays.equals(modifierOrder, JLS_ORDER)) {
137            final Set<String> uniqueOrder = new LinkedHashSet<>(Arrays.asList(modifierOrder));
138            Collections.addAll(uniqueOrder, JLS_ORDER);
139            modifiersOrder = uniqueOrder.toArray(new String[0]);
140            isCustomOrder = true;
141        }
142    }
143
144    @Override
145    public int[] getDefaultTokens() {
146        return getRequiredTokens();
147    }
148
149    @Override
150    public int[] getAcceptableTokens() {
151        return getRequiredTokens();
152    }
153
154    @Override
155    public int[] getRequiredTokens() {
156        return new int[] {TokenTypes.MODIFIERS};
157    }
158
159    @Override
160    public void visitToken(DetailAST ast) {
161        final List<DetailAST> mods = new ArrayList<>();
162        DetailAST modifier = ast.getFirstChild();
163        while (modifier != null) {
164            mods.add(modifier);
165            modifier = modifier.getNextSibling();
166        }
167
168        if (!mods.isEmpty()) {
169            final DetailAST error = checkOrderSuggestedByJls(mods);
170            if (error != null) {
171                if (error.getType() == TokenTypes.ANNOTATION) {
172                    log(error,
173                            MSG_ANNOTATION_ORDER,
174                             error.getFirstChild().getText()
175                             + error.getFirstChild().getNextSibling()
176                                .getText());
177                }
178                else {
179                    if (isCustomOrder) {
180                        log(error, MSG_MODIFIER_CUSTOM_ORDER, error.getText());
181                    }
182                    else {
183                        log(error, MSG_MODIFIER_ORDER, error.getText());
184                    }
185                }
186            }
187        }
188    }
189
190    /**
191     * Checks if the modifiers were added in the order suggested
192     * in the Java language specification.
193     *
194     * @param modifiers list of modifier AST tokens
195     * @return null if the order is correct, otherwise returns the offending
196     *     modifier AST.
197     */
198    private DetailAST checkOrderSuggestedByJls(List<DetailAST> modifiers) {
199        final Iterator<DetailAST> iterator = modifiers.iterator();
200
201        // Speed past all initial annotations
202        DetailAST modifier = skipAnnotations(iterator);
203
204        DetailAST offendingModifier = null;
205
206        // All modifiers are annotations, no problem
207        if (modifier.getType() != TokenTypes.ANNOTATION) {
208            int index = 0;
209
210            while (modifier != null
211                    && offendingModifier == null) {
212                if (modifier.getType() == TokenTypes.ANNOTATION) {
213                    if (!isAnnotationOnType(modifier)) {
214                        // Annotation not at start of modifiers, bad
215                        offendingModifier = modifier;
216                    }
217                    break;
218                }
219
220                while (index < modifiersOrder.length
221                       && !modifiersOrder[index].equals(modifier.getText())) {
222                    index++;
223                }
224
225                if (index == modifiersOrder.length) {
226                    // Current modifier is out of modifiers order
227                    offendingModifier = modifier;
228                }
229                else if (iterator.hasNext()) {
230                    modifier = iterator.next();
231                }
232                else {
233                    // Reached end of modifiers without problem
234                    modifier = null;
235                }
236            }
237        }
238        return offendingModifier;
239    }
240
241    /**
242     * Skip all annotations in modifier block.
243     *
244     * @param modifierIterator iterator for collection of modifiers
245     * @return modifier next to last annotation
246     */
247    private static DetailAST skipAnnotations(Iterator<DetailAST> modifierIterator) {
248        DetailAST modifier;
249        do {
250            modifier = modifierIterator.next();
251        } while (modifierIterator.hasNext() && modifier.getType() == TokenTypes.ANNOTATION);
252        return modifier;
253    }
254
255    /**
256     * Checks whether annotation on type takes place.
257     *
258     * @param modifier modifier token.
259     * @return true if annotation on type takes place.
260     */
261    private static boolean isAnnotationOnType(DetailAST modifier) {
262        boolean annotationOnType = false;
263        final DetailAST modifiers = modifier.getParent();
264        final DetailAST definition = modifiers.getParent();
265        final int definitionType = definition.getType();
266        if (definitionType == TokenTypes.VARIABLE_DEF
267                || definitionType == TokenTypes.PARAMETER_DEF
268                || definitionType == TokenTypes.CTOR_DEF) {
269            annotationOnType = true;
270        }
271        else if (definitionType == TokenTypes.METHOD_DEF) {
272            final DetailAST typeToken = definition.findFirstToken(TokenTypes.TYPE);
273            final int methodReturnType = typeToken.getLastChild().getType();
274            if (methodReturnType != TokenTypes.LITERAL_VOID) {
275                annotationOnType = true;
276            }
277        }
278        return annotationOnType;
279    }
280
281}