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