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