001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2019 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/technetwork/articles/java/ma14-architect-annotations-2177655.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 *
073 * @since 3.0
074 */
075@StatelessCheck
076public class ModifierOrderCheck
077    extends AbstractCheck {
078
079    /**
080     * A key is pointing to the warning message text in "messages.properties"
081     * file.
082     */
083    public static final String MSG_ANNOTATION_ORDER = "annotation.order";
084
085    /**
086     * A key is pointing to the warning message text in "messages.properties"
087     * file.
088     */
089    public static final String MSG_MODIFIER_ORDER = "mod.order";
090
091    /**
092     * The order of modifiers as suggested in sections 8.1.1,
093     * 8.3.1 and 8.4.3 of the JLS.
094     */
095    private static final String[] JLS_ORDER = {
096        "public", "protected", "private", "abstract", "default", "static",
097        "final", "transient", "volatile", "synchronized", "native", "strictfp",
098    };
099
100    @Override
101    public int[] getDefaultTokens() {
102        return getRequiredTokens();
103    }
104
105    @Override
106    public int[] getAcceptableTokens() {
107        return getRequiredTokens();
108    }
109
110    @Override
111    public int[] getRequiredTokens() {
112        return new int[] {TokenTypes.MODIFIERS};
113    }
114
115    @Override
116    public void visitToken(DetailAST ast) {
117        final List<DetailAST> mods = new ArrayList<>();
118        DetailAST modifier = ast.getFirstChild();
119        while (modifier != null) {
120            mods.add(modifier);
121            modifier = modifier.getNextSibling();
122        }
123
124        if (!mods.isEmpty()) {
125            final DetailAST error = checkOrderSuggestedByJls(mods);
126            if (error != null) {
127                if (error.getType() == TokenTypes.ANNOTATION) {
128                    log(error,
129                            MSG_ANNOTATION_ORDER,
130                             error.getFirstChild().getText()
131                             + error.getFirstChild().getNextSibling()
132                                .getText());
133                }
134                else {
135                    log(error, MSG_MODIFIER_ORDER, error.getText());
136                }
137            }
138        }
139    }
140
141    /**
142     * Checks if the modifiers were added in the order suggested
143     * in the Java language specification.
144     *
145     * @param modifiers list of modifier AST tokens
146     * @return null if the order is correct, otherwise returns the offending
147     *     modifier AST.
148     */
149    private static DetailAST checkOrderSuggestedByJls(List<DetailAST> modifiers) {
150        final Iterator<DetailAST> iterator = modifiers.iterator();
151
152        //Speed past all initial annotations
153        DetailAST modifier = skipAnnotations(iterator);
154
155        DetailAST offendingModifier = null;
156
157        //All modifiers are annotations, no problem
158        if (modifier.getType() != TokenTypes.ANNOTATION) {
159            int index = 0;
160
161            while (modifier != null
162                    && offendingModifier == null) {
163                if (modifier.getType() == TokenTypes.ANNOTATION) {
164                    if (!isAnnotationOnType(modifier)) {
165                        //Annotation not at start of modifiers, bad
166                        offendingModifier = modifier;
167                    }
168                    break;
169                }
170
171                while (index < JLS_ORDER.length
172                       && !JLS_ORDER[index].equals(modifier.getText())) {
173                    index++;
174                }
175
176                if (index == JLS_ORDER.length) {
177                    //Current modifier is out of JLS order
178                    offendingModifier = modifier;
179                }
180                else if (iterator.hasNext()) {
181                    modifier = iterator.next();
182                }
183                else {
184                    //Reached end of modifiers without problem
185                    modifier = null;
186                }
187            }
188        }
189        return offendingModifier;
190    }
191
192    /**
193     * Skip all annotations in modifier block.
194     * @param modifierIterator iterator for collection of modifiers
195     * @return modifier next to last annotation
196     */
197    private static DetailAST skipAnnotations(Iterator<DetailAST> modifierIterator) {
198        DetailAST modifier;
199        do {
200            modifier = modifierIterator.next();
201        } while (modifierIterator.hasNext() && modifier.getType() == TokenTypes.ANNOTATION);
202        return modifier;
203    }
204
205    /**
206     * Checks whether annotation on type takes place.
207     * @param modifier modifier token.
208     * @return true if annotation on type takes place.
209     */
210    private static boolean isAnnotationOnType(DetailAST modifier) {
211        boolean annotationOnType = false;
212        final DetailAST modifiers = modifier.getParent();
213        final DetailAST definition = modifiers.getParent();
214        final int definitionType = definition.getType();
215        if (definitionType == TokenTypes.VARIABLE_DEF
216                || definitionType == TokenTypes.PARAMETER_DEF
217                || definitionType == TokenTypes.CTOR_DEF) {
218            annotationOnType = true;
219        }
220        else if (definitionType == TokenTypes.METHOD_DEF) {
221            final DetailAST typeToken = definition.findFirstToken(TokenTypes.TYPE);
222            final int methodReturnType = typeToken.getLastChild().getType();
223            if (methodReturnType != TokenTypes.LITERAL_VOID) {
224                annotationOnType = true;
225            }
226        }
227        return annotationOnType;
228    }
229
230}