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