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 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_ANNOTATION_ORDER = "annotation.order";
087
088    /**
089     * A key is pointing to the warning message text in "messages.properties" file.
090     */
091    public static final String MSG_MODIFIER_ORDER = "mod.order";
092
093    /**
094     * A key is pointing to the warning message text in "messages.properties" file.
095     */
096    public static final String MSG_MODIFIER_CUSTOM_ORDER = "mod.custom.order";
097
098    /**
099     * The order of modifiers as suggested in sections 8.1.1,
100     * 8.3.1 and 8.4.3 of the JLS.
101     */
102    private static final String[] JLS_ORDER = {
103        "public", "protected", "private", "abstract", "default", "static",
104        "sealed", "non-sealed", "final", "transient", "volatile",
105        "synchronized", "native", "strictfp",
106    };
107
108    /**
109     * To specify the order of modifiers.
110     */
111    private String[] modifiersOrder = JLS_ORDER;
112
113    /**
114     * Indicates if the order of modifiers is custom.
115     */
116    private boolean isCustomOrder;
117
118    /**
119     * Creates a new {@code ModifierOrderCheck} instance.
120     */
121    public ModifierOrderCheck() {
122        // no code by default
123    }
124
125    /**
126     * Setter to set the order of modifiers.
127     *
128     * @param modifierOrder the order of modifiers
129     * @since 14.2.0
130     */
131    public void setModifiersOrder(String... modifierOrder) {
132        if (!Arrays.equals(modifierOrder, JLS_ORDER)) {
133            final Set<String> uniqueOrder = new LinkedHashSet<>(Arrays.asList(modifierOrder));
134            Collections.addAll(uniqueOrder, JLS_ORDER);
135            modifiersOrder = uniqueOrder.toArray(new String[0]);
136            isCustomOrder = true;
137        }
138    }
139
140    @Override
141    public int[] getDefaultTokens() {
142        return getRequiredTokens();
143    }
144
145    @Override
146    public int[] getAcceptableTokens() {
147        return getRequiredTokens();
148    }
149
150    @Override
151    public int[] getRequiredTokens() {
152        return new int[] {TokenTypes.MODIFIERS};
153    }
154
155    @Override
156    public void visitToken(DetailAST ast) {
157        final List<DetailAST> mods = new ArrayList<>();
158        DetailAST modifier = ast.getFirstChild();
159        while (modifier != null) {
160            mods.add(modifier);
161            modifier = modifier.getNextSibling();
162        }
163
164        if (!mods.isEmpty()) {
165            final DetailAST error = checkOrderSuggestedByJls(mods);
166            if (error != null) {
167                if (error.getType() == TokenTypes.ANNOTATION) {
168                    log(error,
169                            MSG_ANNOTATION_ORDER,
170                             error.getFirstChild().getText()
171                             + error.getFirstChild().getNextSibling()
172                                .getText());
173                }
174                else {
175                    if (isCustomOrder) {
176                        log(error, MSG_MODIFIER_CUSTOM_ORDER, error.getText());
177                    }
178                    else {
179                        log(error, MSG_MODIFIER_ORDER, error.getText());
180                    }
181                }
182            }
183        }
184    }
185
186    /**
187     * Checks if the modifiers were added in the order suggested in the Java language specification.
188     *
189     * @param modifiers list of modifier AST tokens
190     * @return null if the order is correct, otherwise returns the offending modifier AST.
191     */
192    private DetailAST checkOrderSuggestedByJls(List<DetailAST> modifiers) {
193        final Iterator<DetailAST> iterator = modifiers.iterator();
194
195        // Speed past all initial annotations
196        DetailAST modifier = skipAnnotations(iterator);
197
198        DetailAST offendingModifier = null;
199
200        // All modifiers are annotations, no problem
201        if (modifier.getType() != TokenTypes.ANNOTATION) {
202            int index = 0;
203
204            while (modifier != null
205                    && offendingModifier == null) {
206                if (modifier.getType() == TokenTypes.ANNOTATION) {
207                    if (!isAnnotationOnType(modifier)) {
208                        // Annotation not at start of modifiers, bad
209                        offendingModifier = modifier;
210                    }
211                    break;
212                }
213
214                while (index < modifiersOrder.length
215                       && !modifiersOrder[index].equals(modifier.getText())) {
216                    index++;
217                }
218
219                if (index == modifiersOrder.length) {
220                    // Current modifier is out of modifiers order
221                    offendingModifier = modifier;
222                }
223                else if (iterator.hasNext()) {
224                    modifier = iterator.next();
225                }
226                else {
227                    // Reached end of modifiers without problem
228                    modifier = null;
229                }
230            }
231        }
232        return offendingModifier;
233    }
234
235    /**
236     * Skip all annotations in modifier block.
237     *
238     * @param modifierIterator iterator for collection of modifiers
239     * @return modifier next to last annotation
240     */
241    private static DetailAST skipAnnotations(Iterator<DetailAST> modifierIterator) {
242        DetailAST modifier;
243        do {
244            modifier = modifierIterator.next();
245        } while (modifierIterator.hasNext() && modifier.getType() == TokenTypes.ANNOTATION);
246        return modifier;
247    }
248
249    /**
250     * Checks whether annotation on type takes place.
251     *
252     * @param modifier modifier token.
253     * @return true if annotation on type takes place.
254     */
255    private static boolean isAnnotationOnType(DetailAST modifier) {
256        boolean annotationOnType = false;
257        final DetailAST modifiers = modifier.getParent();
258        final DetailAST definition = modifiers.getParent();
259        final int definitionType = definition.getType();
260        if (definitionType == TokenTypes.VARIABLE_DEF
261                || definitionType == TokenTypes.PARAMETER_DEF
262                || definitionType == TokenTypes.CTOR_DEF) {
263            annotationOnType = true;
264        }
265        else if (definitionType == TokenTypes.METHOD_DEF) {
266            final DetailAST typeToken = definition.findFirstToken(TokenTypes.TYPE);
267            final int methodReturnType = typeToken.getLastChild().getType();
268            if (methodReturnType != TokenTypes.LITERAL_VOID) {
269                annotationOnType = true;
270            }
271        }
272        return annotationOnType;
273    }
274
275}