001/////////////////////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code and other text files for adherence to a set of rules. 003// Copyright (C) 2001-2025 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 * <div> 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 * </div> 037 * 038 * @since 5.8 039 */ 040@StatelessCheck 041public class OneTopLevelClassCheck extends AbstractCheck { 042 043 /** 044 * A key is pointing to the warning message text in "messages.properties" 045 * file. 046 */ 047 public static final String MSG_KEY = "one.top.level.class"; 048 049 @Override 050 public int[] getDefaultTokens() { 051 return getRequiredTokens(); 052 } 053 054 @Override 055 public int[] getAcceptableTokens() { 056 return getRequiredTokens(); 057 } 058 059 @Override 060 public int[] getRequiredTokens() { 061 return new int[] { 062 TokenTypes.COMPILATION_UNIT, 063 }; 064 } 065 066 @Override 067 public void visitToken(DetailAST compilationUnit) { 068 DetailAST currentNode = compilationUnit.getFirstChild(); 069 070 boolean publicTypeFound = false; 071 DetailAST firstType = null; 072 073 while (currentNode != null) { 074 if (isTypeDef(currentNode)) { 075 if (isPublic(currentNode)) { 076 // log the first type later 077 publicTypeFound = true; 078 } 079 if (firstType == null) { 080 // first type is set aside 081 firstType = currentNode; 082 } 083 else if (!isPublic(currentNode)) { 084 // extra non-public type, log immediately 085 final String typeName = currentNode 086 .findFirstToken(TokenTypes.IDENT).getText(); 087 log(currentNode, MSG_KEY, typeName); 088 } 089 } 090 currentNode = currentNode.getNextSibling(); 091 } 092 093 // if there was a public type and first type is non-public, log it 094 if (publicTypeFound && !isPublic(firstType)) { 095 final String typeName = firstType 096 .findFirstToken(TokenTypes.IDENT).getText(); 097 log(firstType, MSG_KEY, typeName); 098 } 099 } 100 101 /** 102 * Checks if an AST node is a type definition. 103 * 104 * @param node AST node to check. 105 * @return true if the node is a type (class, enum, interface, annotation) definition. 106 */ 107 private static boolean isTypeDef(DetailAST node) { 108 return TokenUtil.isTypeDeclaration(node.getType()); 109 } 110 111 /** 112 * Checks if a type is public. 113 * 114 * @param typeDef type definition node. 115 * @return true if a type has a public access level modifier. 116 */ 117 private static boolean isPublic(DetailAST typeDef) { 118 final DetailAST modifiers = 119 typeDef.findFirstToken(TokenTypes.MODIFIERS); 120 return modifiers.findFirstToken(TokenTypes.LITERAL_PUBLIC) != null; 121 } 122 123}