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