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 * </pre> 060 * <p> 061 * An example of code without public top-level type: 062 * </p> 063 * <pre> 064 * class Foo { // OK, first top-level class 065 * // methods 066 * } 067 * 068 * class Foo2 { // violation, second top-level class 069 * // methods 070 * } 071 * </pre> 072 * <p> 073 * An example of code without violations: 074 * </p> 075 * <pre> 076 * public class Foo { // OK, only one top-level class 077 * // methods 078 * } 079 * </pre> 080 * <p> 081 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker} 082 * </p> 083 * <p> 084 * Violation Message Keys: 085 * </p> 086 * <ul> 087 * <li> 088 * {@code one.top.level.class} 089 * </li> 090 * </ul> 091 * 092 * @since 5.8 093 */ 094@StatelessCheck 095public class OneTopLevelClassCheck extends AbstractCheck { 096 097 /** 098 * A key is pointing to the warning message text in "messages.properties" 099 * file. 100 */ 101 public static final String MSG_KEY = "one.top.level.class"; 102 103 @Override 104 public int[] getDefaultTokens() { 105 return getRequiredTokens(); 106 } 107 108 @Override 109 public int[] getAcceptableTokens() { 110 return getRequiredTokens(); 111 } 112 113 // ZERO tokens as Check do Traverse of Tree himself, he does not need to subscribed to Tokens 114 @Override 115 public int[] getRequiredTokens() { 116 return CommonUtil.EMPTY_INT_ARRAY; 117 } 118 119 @Override 120 public void beginTree(DetailAST rootAST) { 121 DetailAST currentNode = rootAST; 122 boolean publicTypeFound = false; 123 DetailAST firstType = null; 124 125 while (currentNode != null) { 126 if (isTypeDef(currentNode)) { 127 if (isPublic(currentNode)) { 128 // log the first type later 129 publicTypeFound = true; 130 } 131 if (firstType == null) { 132 // first type is set aside 133 firstType = currentNode; 134 } 135 else if (!isPublic(currentNode)) { 136 // extra non-public type, log immediately 137 final String typeName = currentNode 138 .findFirstToken(TokenTypes.IDENT).getText(); 139 log(currentNode, MSG_KEY, typeName); 140 } 141 } 142 currentNode = currentNode.getNextSibling(); 143 } 144 145 // if there was a public type and first type is non-public, log it 146 if (publicTypeFound && !isPublic(firstType)) { 147 final String typeName = firstType 148 .findFirstToken(TokenTypes.IDENT).getText(); 149 log(firstType, MSG_KEY, typeName); 150 } 151 } 152 153 /** 154 * Checks if an AST node is a type definition. 155 * 156 * @param node AST node to check. 157 * @return true if the node is a type (class, enum, interface, annotation) definition. 158 */ 159 private static boolean isTypeDef(DetailAST node) { 160 return TokenUtil.isTypeDeclaration(node.getType()); 161 } 162 163 /** 164 * Checks if a type is public. 165 * 166 * @param typeDef type definition node. 167 * @return true if a type has a public access level modifier. 168 */ 169 private static boolean isPublic(DetailAST typeDef) { 170 final DetailAST modifiers = 171 typeDef.findFirstToken(TokenTypes.MODIFIERS); 172 return modifiers.findFirstToken(TokenTypes.LITERAL_PUBLIC) != null; 173 } 174 175}