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