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 082 extends AbstractCheck { 083 084 /** 085 * A key is pointing to the warning message text in "messages.properties" 086 * file. 087 */ 088 public static final String MSG_ANNOTATION_ORDER = "annotation.order"; 089 090 /** 091 * A key is pointing to the warning message text in "messages.properties" 092 * file. 093 */ 094 public static final String MSG_MODIFIER_ORDER = "mod.order"; 095 096 /** 097 * A key is pointing to the warning message text in "messages.properties" 098 * file. 099 */ 100 public static final String MSG_MODIFIER_CUSTOM_ORDER = "mod.custom.order"; 101 102 /** 103 * The order of modifiers as suggested in sections 8.1.1, 104 * 8.3.1 and 8.4.3 of the JLS. 105 */ 106 private static final String[] JLS_ORDER = { 107 "public", "protected", "private", "abstract", "default", "static", 108 "sealed", "non-sealed", "final", "transient", "volatile", 109 "synchronized", "native", "strictfp", 110 }; 111 112 /** 113 * To specify the order of modifiers. 114 */ 115 private String[] modifiersOrder = JLS_ORDER; 116 117 /** 118 * Indicates if the order of modifiers is custom. 119 */ 120 private boolean isCustomOrder; 121 122 /** 123 * Creates a new {@code ModifierOrderCheck} instance. 124 */ 125 public ModifierOrderCheck() { 126 // no code by default 127 } 128 129 /** 130 * Setter to set the order of modifiers. 131 * 132 * @param modifierOrder the order of modifiers 133 * @since 14.2.0 134 */ 135 public void setModifiersOrder(String... modifierOrder) { 136 if (!Arrays.equals(modifierOrder, JLS_ORDER)) { 137 final Set<String> uniqueOrder = new LinkedHashSet<>(Arrays.asList(modifierOrder)); 138 Collections.addAll(uniqueOrder, JLS_ORDER); 139 modifiersOrder = uniqueOrder.toArray(new String[0]); 140 isCustomOrder = true; 141 } 142 } 143 144 @Override 145 public int[] getDefaultTokens() { 146 return getRequiredTokens(); 147 } 148 149 @Override 150 public int[] getAcceptableTokens() { 151 return getRequiredTokens(); 152 } 153 154 @Override 155 public int[] getRequiredTokens() { 156 return new int[] {TokenTypes.MODIFIERS}; 157 } 158 159 @Override 160 public void visitToken(DetailAST ast) { 161 final List<DetailAST> mods = new ArrayList<>(); 162 DetailAST modifier = ast.getFirstChild(); 163 while (modifier != null) { 164 mods.add(modifier); 165 modifier = modifier.getNextSibling(); 166 } 167 168 if (!mods.isEmpty()) { 169 final DetailAST error = checkOrderSuggestedByJls(mods); 170 if (error != null) { 171 if (error.getType() == TokenTypes.ANNOTATION) { 172 log(error, 173 MSG_ANNOTATION_ORDER, 174 error.getFirstChild().getText() 175 + error.getFirstChild().getNextSibling() 176 .getText()); 177 } 178 else { 179 if (isCustomOrder) { 180 log(error, MSG_MODIFIER_CUSTOM_ORDER, error.getText()); 181 } 182 else { 183 log(error, MSG_MODIFIER_ORDER, error.getText()); 184 } 185 } 186 } 187 } 188 } 189 190 /** 191 * Checks if the modifiers were added in the order suggested 192 * in the Java language specification. 193 * 194 * @param modifiers list of modifier AST tokens 195 * @return null if the order is correct, otherwise returns the offending 196 * modifier AST. 197 */ 198 private DetailAST checkOrderSuggestedByJls(List<DetailAST> modifiers) { 199 final Iterator<DetailAST> iterator = modifiers.iterator(); 200 201 // Speed past all initial annotations 202 DetailAST modifier = skipAnnotations(iterator); 203 204 DetailAST offendingModifier = null; 205 206 // All modifiers are annotations, no problem 207 if (modifier.getType() != TokenTypes.ANNOTATION) { 208 int index = 0; 209 210 while (modifier != null 211 && offendingModifier == null) { 212 if (modifier.getType() == TokenTypes.ANNOTATION) { 213 if (!isAnnotationOnType(modifier)) { 214 // Annotation not at start of modifiers, bad 215 offendingModifier = modifier; 216 } 217 break; 218 } 219 220 while (index < modifiersOrder.length 221 && !modifiersOrder[index].equals(modifier.getText())) { 222 index++; 223 } 224 225 if (index == modifiersOrder.length) { 226 // Current modifier is out of modifiers order 227 offendingModifier = modifier; 228 } 229 else if (iterator.hasNext()) { 230 modifier = iterator.next(); 231 } 232 else { 233 // Reached end of modifiers without problem 234 modifier = null; 235 } 236 } 237 } 238 return offendingModifier; 239 } 240 241 /** 242 * Skip all annotations in modifier block. 243 * 244 * @param modifierIterator iterator for collection of modifiers 245 * @return modifier next to last annotation 246 */ 247 private static DetailAST skipAnnotations(Iterator<DetailAST> modifierIterator) { 248 DetailAST modifier; 249 do { 250 modifier = modifierIterator.next(); 251 } while (modifierIterator.hasNext() && modifier.getType() == TokenTypes.ANNOTATION); 252 return modifier; 253 } 254 255 /** 256 * Checks whether annotation on type takes place. 257 * 258 * @param modifier modifier token. 259 * @return true if annotation on type takes place. 260 */ 261 private static boolean isAnnotationOnType(DetailAST modifier) { 262 boolean annotationOnType = false; 263 final DetailAST modifiers = modifier.getParent(); 264 final DetailAST definition = modifiers.getParent(); 265 final int definitionType = definition.getType(); 266 if (definitionType == TokenTypes.VARIABLE_DEF 267 || definitionType == TokenTypes.PARAMETER_DEF 268 || definitionType == TokenTypes.CTOR_DEF) { 269 annotationOnType = true; 270 } 271 else if (definitionType == TokenTypes.METHOD_DEF) { 272 final DetailAST typeToken = definition.findFirstToken(TokenTypes.TYPE); 273 final int methodReturnType = typeToken.getLastChild().getType(); 274 if (methodReturnType != TokenTypes.LITERAL_VOID) { 275 annotationOnType = true; 276 } 277 } 278 return annotationOnType; 279 } 280 281}