001/////////////////////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code and other text files for adherence to a set of rules. 003// Copyright (C) 2001-2023 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.design; 021 022import com.puppycrawl.tools.checkstyle.StatelessCheck; 023import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 024import com.puppycrawl.tools.checkstyle.api.DetailAST; 025import com.puppycrawl.tools.checkstyle.api.TokenTypes; 026import com.puppycrawl.tools.checkstyle.utils.TokenUtil; 027 028/** 029 * <p> 030 * Checks that each top-level class, interface, enum 031 * or annotation resides in a source file of its own. 032 * Official description of a 'top-level' term: 033 * <a href="https://docs.oracle.com/javase/specs/jls/se11/html/jls-7.html#jls-7.6"> 034 * 7.6. Top Level Type Declarations</a>. If file doesn't contain 035 * public class, interface, enum or annotation, top-level type is the first type in file. 036 * </p> 037 * <p> 038 * To configure the check: 039 * </p> 040 * <pre> 041 * <module name="OneTopLevelClass"/> 042 * </pre> 043 * <p> 044 * <b>ATTENTION:</b> This Check does not support customization of validated tokens, 045 * so do not use the "tokens" property. 046 * </p> 047 * <p> 048 * An example of code with violations: 049 * </p> 050 * <pre> 051 * public class Foo { // OK, first top-level class 052 * // methods 053 * } 054 * 055 * class Foo2 { // violation, second top-level class 056 * // methods 057 * } 058 * 059 * record Foo3 { // violation, third top-level "class" 060 * // methods 061 * } 062 * </pre> 063 * <p> 064 * An example of code without public top-level type: 065 * </p> 066 * <pre> 067 * class Foo { // OK, first top-level class 068 * // methods 069 * } 070 * 071 * class Foo2 { // violation, second top-level class 072 * // methods 073 * } 074 * </pre> 075 * <p> 076 * An example of code without violations: 077 * </p> 078 * <pre> 079 * public class Foo { // OK, only one top-level class 080 * // methods 081 * } 082 * </pre> 083 * <p> 084 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker} 085 * </p> 086 * <p> 087 * Violation Message Keys: 088 * </p> 089 * <ul> 090 * <li> 091 * {@code one.top.level.class} 092 * </li> 093 * </ul> 094 * 095 * @since 5.8 096 */ 097@StatelessCheck 098public class OneTopLevelClassCheck extends AbstractCheck { 099 100 /** 101 * A key is pointing to the warning message text in "messages.properties" 102 * file. 103 */ 104 public static final String MSG_KEY = "one.top.level.class"; 105 106 @Override 107 public int[] getDefaultTokens() { 108 return getRequiredTokens(); 109 } 110 111 @Override 112 public int[] getAcceptableTokens() { 113 return getRequiredTokens(); 114 } 115 116 @Override 117 public int[] getRequiredTokens() { 118 return new int[] { 119 TokenTypes.COMPILATION_UNIT, 120 }; 121 } 122 123 @Override 124 public void visitToken(DetailAST compilationUnit) { 125 DetailAST currentNode = compilationUnit.getFirstChild(); 126 127 boolean publicTypeFound = false; 128 DetailAST firstType = null; 129 130 while (currentNode != null) { 131 if (isTypeDef(currentNode)) { 132 if (isPublic(currentNode)) { 133 // log the first type later 134 publicTypeFound = true; 135 } 136 if (firstType == null) { 137 // first type is set aside 138 firstType = currentNode; 139 } 140 else if (!isPublic(currentNode)) { 141 // extra non-public type, log immediately 142 final String typeName = currentNode 143 .findFirstToken(TokenTypes.IDENT).getText(); 144 log(currentNode, MSG_KEY, typeName); 145 } 146 } 147 currentNode = currentNode.getNextSibling(); 148 } 149 150 // if there was a public type and first type is non-public, log it 151 if (publicTypeFound && !isPublic(firstType)) { 152 final String typeName = firstType 153 .findFirstToken(TokenTypes.IDENT).getText(); 154 log(firstType, MSG_KEY, typeName); 155 } 156 } 157 158 /** 159 * Checks if an AST node is a type definition. 160 * 161 * @param node AST node to check. 162 * @return true if the node is a type (class, enum, interface, annotation) definition. 163 */ 164 private static boolean isTypeDef(DetailAST node) { 165 return TokenUtil.isTypeDeclaration(node.getType()); 166 } 167 168 /** 169 * Checks if a type is public. 170 * 171 * @param typeDef type definition node. 172 * @return true if a type has a public access level modifier. 173 */ 174 private static boolean isPublic(DetailAST typeDef) { 175 final DetailAST modifiers = 176 typeDef.findFirstToken(TokenTypes.MODIFIERS); 177 return modifiers.findFirstToken(TokenTypes.LITERAL_PUBLIC) != null; 178 } 179 180}