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 * <module name="ModifierOrder"/> 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}