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