001/////////////////////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code and other text files for adherence to a set of rules. 003// Copyright (C) 2001-2026 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.Arrays; 024import java.util.Collections; 025import java.util.Iterator; 026import java.util.LinkedHashSet; 027import java.util.List; 028import java.util.Set; 029 030import com.puppycrawl.tools.checkstyle.FileStatefulCheck; 031import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 032import com.puppycrawl.tools.checkstyle.api.DetailAST; 033import com.puppycrawl.tools.checkstyle.api.TokenTypes; 034 035/** 036 * <div> 037 * Validates that the modifiers appear in the correct, standard order. 038 * By default the order of modifiers conforms to the suggestions in the 039 * <a href="https://docs.oracle.com/javase/specs/jls/se21/html/jls-8.html"> 040 * Java Language specification, § 8.1.1, 8.3.1, 8.4.3</a> and 041 * <a href="https://docs.oracle.com/javase/specs/jls/se21/html/jls-9.html#jls-9.4">9.4</a>. 042 * The default order is: 043 * </div> 044 * 045 * <ol> 046 * <li> {@code public} </li> 047 * <li> {@code protected} </li> 048 * <li> {@code private} </li> 049 * <li> {@code abstract} </li> 050 * <li> {@code default} </li> 051 * <li> {@code static} </li> 052 * <li> {@code sealed} </li> 053 * <li> {@code non-sealed} </li> 054 * <li> {@code final} </li> 055 * <li> {@code transient} </li> 056 * <li> {@code volatile} </li> 057 * <li> {@code synchronized} </li> 058 * <li> {@code native} </li> 059 * <li> {@code strictfp} </li> 060 * </ol> 061 * 062 * <p> 063 * Additionally, modifiers are checked to ensure all annotations 064 * are declared before all other modifiers. 065 * </p> 066 * 067 * <p> 068 * Rationale: Code is easier to read if everybody follows 069 * a standard. 070 * </p> 071 * 072 * <p> 073 * ATTENTION: We skip 074 * <a href="https://www.oracle.com/technical-resources/articles/java/ma14-architect-annotations.html"> 075 * type annotations</a> from validation. 076 * </p> 077 * 078 * @since 3.0 079 */ 080@FileStatefulCheck 081public class ModifierOrderCheck extends AbstractCheck { 082 083 /** 084 * A key is pointing to the warning message text in "messages.properties" file. 085 */ 086 public static final String MSG_ANNOTATION_ORDER = "annotation.order"; 087 088 /** 089 * A key is pointing to the warning message text in "messages.properties" file. 090 */ 091 public static final String MSG_MODIFIER_ORDER = "mod.order"; 092 093 /** 094 * A key is pointing to the warning message text in "messages.properties" file. 095 */ 096 public static final String MSG_MODIFIER_CUSTOM_ORDER = "mod.custom.order"; 097 098 /** 099 * The order of modifiers as suggested in sections 8.1.1, 100 * 8.3.1 and 8.4.3 of the JLS. 101 */ 102 private static final String[] JLS_ORDER = { 103 "public", "protected", "private", "abstract", "default", "static", 104 "sealed", "non-sealed", "final", "transient", "volatile", 105 "synchronized", "native", "strictfp", 106 }; 107 108 /** 109 * To specify the order of modifiers. 110 */ 111 private String[] modifiersOrder = JLS_ORDER; 112 113 /** 114 * Indicates if the order of modifiers is custom. 115 */ 116 private boolean isCustomOrder; 117 118 /** 119 * Creates a new {@code ModifierOrderCheck} instance. 120 */ 121 public ModifierOrderCheck() { 122 // no code by default 123 } 124 125 /** 126 * Setter to set the order of modifiers. 127 * 128 * @param modifierOrder the order of modifiers 129 * @since 14.2.0 130 */ 131 public void setModifiersOrder(String... modifierOrder) { 132 if (!Arrays.equals(modifierOrder, JLS_ORDER)) { 133 final Set<String> uniqueOrder = new LinkedHashSet<>(Arrays.asList(modifierOrder)); 134 Collections.addAll(uniqueOrder, JLS_ORDER); 135 modifiersOrder = uniqueOrder.toArray(new String[0]); 136 isCustomOrder = true; 137 } 138 } 139 140 @Override 141 public int[] getDefaultTokens() { 142 return getRequiredTokens(); 143 } 144 145 @Override 146 public int[] getAcceptableTokens() { 147 return getRequiredTokens(); 148 } 149 150 @Override 151 public int[] getRequiredTokens() { 152 return new int[] {TokenTypes.MODIFIERS}; 153 } 154 155 @Override 156 public void visitToken(DetailAST ast) { 157 final List<DetailAST> mods = new ArrayList<>(); 158 DetailAST modifier = ast.getFirstChild(); 159 while (modifier != null) { 160 mods.add(modifier); 161 modifier = modifier.getNextSibling(); 162 } 163 164 if (!mods.isEmpty()) { 165 final DetailAST error = checkOrderSuggestedByJls(mods); 166 if (error != null) { 167 if (error.getType() == TokenTypes.ANNOTATION) { 168 log(error, 169 MSG_ANNOTATION_ORDER, 170 error.getFirstChild().getText() 171 + error.getFirstChild().getNextSibling() 172 .getText()); 173 } 174 else { 175 if (isCustomOrder) { 176 log(error, MSG_MODIFIER_CUSTOM_ORDER, error.getText()); 177 } 178 else { 179 log(error, MSG_MODIFIER_ORDER, error.getText()); 180 } 181 } 182 } 183 } 184 } 185 186 /** 187 * Checks if the modifiers were added in the order suggested in the Java language specification. 188 * 189 * @param modifiers list of modifier AST tokens 190 * @return null if the order is correct, otherwise returns the offending modifier AST. 191 */ 192 private DetailAST checkOrderSuggestedByJls(List<DetailAST> modifiers) { 193 final Iterator<DetailAST> iterator = modifiers.iterator(); 194 195 // Speed past all initial annotations 196 DetailAST modifier = skipAnnotations(iterator); 197 198 DetailAST offendingModifier = null; 199 200 // All modifiers are annotations, no problem 201 if (modifier.getType() != TokenTypes.ANNOTATION) { 202 int index = 0; 203 204 while (modifier != null 205 && offendingModifier == null) { 206 if (modifier.getType() == TokenTypes.ANNOTATION) { 207 if (!isAnnotationOnType(modifier)) { 208 // Annotation not at start of modifiers, bad 209 offendingModifier = modifier; 210 } 211 break; 212 } 213 214 while (index < modifiersOrder.length 215 && !modifiersOrder[index].equals(modifier.getText())) { 216 index++; 217 } 218 219 if (index == modifiersOrder.length) { 220 // Current modifier is out of modifiers order 221 offendingModifier = modifier; 222 } 223 else if (iterator.hasNext()) { 224 modifier = iterator.next(); 225 } 226 else { 227 // Reached end of modifiers without problem 228 modifier = null; 229 } 230 } 231 } 232 return offendingModifier; 233 } 234 235 /** 236 * Skip all annotations in modifier block. 237 * 238 * @param modifierIterator iterator for collection of modifiers 239 * @return modifier next to last annotation 240 */ 241 private static DetailAST skipAnnotations(Iterator<DetailAST> modifierIterator) { 242 DetailAST modifier; 243 do { 244 modifier = modifierIterator.next(); 245 } while (modifierIterator.hasNext() && modifier.getType() == TokenTypes.ANNOTATION); 246 return modifier; 247 } 248 249 /** 250 * Checks whether annotation on type takes place. 251 * 252 * @param modifier modifier token. 253 * @return true if annotation on type takes place. 254 */ 255 private static boolean isAnnotationOnType(DetailAST modifier) { 256 boolean annotationOnType = false; 257 final DetailAST modifiers = modifier.getParent(); 258 final DetailAST definition = modifiers.getParent(); 259 final int definitionType = definition.getType(); 260 if (definitionType == TokenTypes.VARIABLE_DEF 261 || definitionType == TokenTypes.PARAMETER_DEF 262 || definitionType == TokenTypes.CTOR_DEF) { 263 annotationOnType = true; 264 } 265 else if (definitionType == TokenTypes.METHOD_DEF) { 266 final DetailAST typeToken = definition.findFirstToken(TokenTypes.TYPE); 267 final int methodReturnType = typeToken.getLastChild().getType(); 268 if (methodReturnType != TokenTypes.LITERAL_VOID) { 269 annotationOnType = true; 270 } 271 } 272 return annotationOnType; 273 } 274 275}