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