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