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