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