| //////////////////////////////////////////////////////////////////////////////// |
| // checkstyle: Checks Java source code for adherence to a set of rules. |
| // Copyright (C) 2001-2017 the original author or authors. |
| // |
| // This library is free software; you can redistribute it and/or |
| // modify it under the terms of the GNU Lesser General Public |
| // License as published by the Free Software Foundation; either |
| // version 2.1 of the License, or (at your option) any later version. |
| // |
| // This library is distributed in the hope that it will be useful, |
| // but WITHOUT ANY WARRANTY; without even the implied warranty of |
| // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| // Lesser General Public License for more details. |
| // |
| // You should have received a copy of the GNU Lesser General Public |
| // License along with this library; if not, write to the Free Software |
| // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA |
| //////////////////////////////////////////////////////////////////////////////// |
| |
| package com.puppycrawl.tools.checkstyle.checks.design; |
| |
| import java.util.Map; |
| import java.util.SortedMap; |
| import java.util.TreeMap; |
| |
| import com.puppycrawl.tools.checkstyle.FileStatefulCheck; |
| import com.puppycrawl.tools.checkstyle.api.AbstractCheck; |
| import com.puppycrawl.tools.checkstyle.api.DetailAST; |
| import com.puppycrawl.tools.checkstyle.api.TokenTypes; |
| import com.puppycrawl.tools.checkstyle.utils.CommonUtils; |
| |
| /** |
| * Checks that each top-level class, interface |
| * or enum resides in a source file of its own. |
| * <p> |
| * Official description of a 'top-level' term:<a |
| * href="https://docs.oracle.com/javase/specs/jls/se8/html/jls-7.html#jls-7.6"> |
| * 7.6. Top Level Type Declarations</a>. If file doesn't contains |
| * public class, enum or interface, top-level type is the first type in file. |
| * </p> |
| * <p> |
| * An example of code with violations: |
| * </p> |
| * <pre>{@code |
| * public class Foo{ |
| * //methods |
| * } |
| * |
| * class Foo2{ |
| * //methods |
| * } |
| * }</pre> |
| * <p> |
| * An example of code without top-level public type: |
| * </p> |
| * <pre>{@code |
| * class Foo{ //top-level class |
| * //methods |
| * } |
| * |
| * class Foo2{ |
| * //methods |
| * } |
| * }</pre> |
| * <p> |
| * An example of check's configuration: |
| * </p> |
| * <pre> |
| * <module name="OneTopLevelClass"/> |
| * </pre> |
| * |
| * <p> |
| * An example of code without violations: |
| * </p> |
| * <pre>{@code |
| * public class Foo{ |
| * //methods |
| * } |
| * }</pre> |
| * |
| * <p> ATTENTION: This Check does not support customization of validated tokens, |
| * so do not use the "tokens" property. |
| * </p> |
| * |
| * @author maxvetrenko |
| */ |
| @FileStatefulCheck |
| public class OneTopLevelClassCheck extends AbstractCheck { |
| |
| /** |
| * A key is pointing to the warning message text in "messages.properties" |
| * file. |
| */ |
| public static final String MSG_KEY = "one.top.level.class"; |
| |
| /** |
| * True if a java source file contains a type |
| * with a public access level modifier. |
| */ |
| private boolean publicTypeFound; |
| |
| /** Mapping between type names and line numbers of the type declarations.*/ |
| private final SortedMap<Integer, String> lineNumberTypeMap = new TreeMap<>(); |
| |
| @Override |
| public int[] getDefaultTokens() { |
| return getAcceptableTokens(); |
| } |
| |
| // ZERO tokens as Check do Traverse of Tree himself, he does not need to subscribed to Tokens |
| @Override |
| public int[] getAcceptableTokens() { |
| return CommonUtils.EMPTY_INT_ARRAY; |
| } |
| |
| @Override |
| public int[] getRequiredTokens() { |
| return getAcceptableTokens(); |
| } |
| |
| @Override |
| public void beginTree(DetailAST rootAST) { |
| publicTypeFound = false; |
| lineNumberTypeMap.clear(); |
| |
| DetailAST currentNode = rootAST; |
| while (currentNode != null) { |
| if (currentNode.getType() == TokenTypes.CLASS_DEF |
| || currentNode.getType() == TokenTypes.ENUM_DEF |
| || currentNode.getType() == TokenTypes.INTERFACE_DEF) { |
| if (isPublic(currentNode)) { |
| publicTypeFound = true; |
| } |
| else { |
| final String typeName = currentNode |
| .findFirstToken(TokenTypes.IDENT).getText(); |
| lineNumberTypeMap.put(currentNode.getLineNo(), typeName); |
| } |
| } |
| currentNode = currentNode.getNextSibling(); |
| } |
| } |
| |
| @Override |
| public void finishTree(DetailAST rootAST) { |
| if (!lineNumberTypeMap.isEmpty()) { |
| if (!publicTypeFound) { |
| // skip first top-level type. |
| lineNumberTypeMap.remove(lineNumberTypeMap.firstKey()); |
| } |
| |
| for (Map.Entry<Integer, String> entry |
| : lineNumberTypeMap.entrySet()) { |
| log(entry.getKey(), MSG_KEY, entry.getValue()); |
| } |
| } |
| } |
| |
| /** |
| * Checks if a type is public. |
| * @param typeDef type definition node. |
| * @return true if a type has a public access level modifier. |
| */ |
| private static boolean isPublic(DetailAST typeDef) { |
| final DetailAST modifiers = |
| typeDef.findFirstToken(TokenTypes.MODIFIERS); |
| return modifiers.findFirstToken(TokenTypes.LITERAL_PUBLIC) != null; |
| } |
| } |