1 ////////////////////////////////////////////////////////////////////////////////
2 // checkstyle: Checks Java source code for adherence to a set of rules.
3 // Copyright (C) 2001-2019 the original author or authors.
4 //
5 // This library is free software; you can redistribute it and/or
6 // modify it under the terms of the GNU Lesser General Public
7 // License as published by the Free Software Foundation; either
8 // version 2.1 of the License, or (at your option) any later version.
9 //
10 // This library is distributed in the hope that it will be useful,
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13 // Lesser General Public License for more details.
14 //
15 // You should have received a copy of the GNU Lesser General Public
16 // License along with this library; if not, write to the Free Software
17 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18 ////////////////////////////////////////////////////////////////////////////////
19
20 package com.puppycrawl.tools.checkstyle.checks.design;
21
22 import java.util.Map;
23 import java.util.SortedMap;
24 import java.util.TreeMap;
25
26 import com.puppycrawl.tools.checkstyle.FileStatefulCheck;
27 import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
28 import com.puppycrawl.tools.checkstyle.api.DetailAST;
29 import com.puppycrawl.tools.checkstyle.api.TokenTypes;
30 import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
31
32 /**
33 * Checks that each top-level class, interface
34 * or enum resides in a source file of its own.
35 * <p>
36 * Official description of a 'top-level' term:<a
37 * href="https://docs.oracle.com/javase/specs/jls/se8/html/jls-7.html#jls-7.6">
38 * 7.6. Top Level Type Declarations</a>. If file doesn't contains
39 * public class, enum or interface, top-level type is the first type in file.
40 * </p>
41 * <p>
42 * An example of code with violations:
43 * </p>
44 * <pre>{@code
45 * public class Foo{
46 * //methods
47 * }
48 *
49 * class Foo2{
50 * //methods
51 * }
52 * }</pre>
53 * <p>
54 * An example of code without top-level public type:
55 * </p>
56 * <pre>{@code
57 * class Foo{ //top-level class
58 * //methods
59 * }
60 *
61 * class Foo2{
62 * //methods
63 * }
64 * }</pre>
65 * <p>
66 * An example of check's configuration:
67 * </p>
68 * <pre>
69 * <module name="OneTopLevelClass"/>
70 * </pre>
71 *
72 * <p>
73 * An example of code without violations:
74 * </p>
75 * <pre>{@code
76 * public class Foo{
77 * //methods
78 * }
79 * }</pre>
80 *
81 * <p> ATTENTION: This Check does not support customization of validated tokens,
82 * so do not use the "tokens" property.
83 * </p>
84 *
85 */
86 @FileStatefulCheck
87 public class OneTopLevelClassCheck extends AbstractCheck {
88
89 /**
90 * A key is pointing to the warning message text in "messages.properties"
91 * file.
92 */
93 public static final String MSG_KEY = "one.top.level.class";
94
95 /**
96 * True if a java source file contains a type
97 * with a public access level modifier.
98 */
99 private boolean publicTypeFound;
100
101 /** Mapping between type names and line numbers of the type declarations.*/
102 private final SortedMap<Integer, String> lineNumberTypeMap = new TreeMap<>();
103
104 @Override
105 public int[] getDefaultTokens() {
106 return getRequiredTokens();
107 }
108
109 @Override
110 public int[] getAcceptableTokens() {
111 return getRequiredTokens();
112 }
113
114 // ZERO tokens as Check do Traverse of Tree himself, he does not need to subscribed to Tokens
115 @Override
116 public int[] getRequiredTokens() {
117 return CommonUtil.EMPTY_INT_ARRAY;
118 }
119
120 @Override
121 public void beginTree(DetailAST rootAST) {
122 publicTypeFound = false;
123 lineNumberTypeMap.clear();
124
125 DetailAST currentNode = rootAST;
126 while (currentNode != null) {
127 if (currentNode.getType() == TokenTypes.CLASS_DEF
128 || currentNode.getType() == TokenTypes.ENUM_DEF
129 || currentNode.getType() == TokenTypes.INTERFACE_DEF) {
130 if (isPublic(currentNode)) {
131 publicTypeFound = true;
132 }
133 else {
134 final String typeName = currentNode
135 .findFirstToken(TokenTypes.IDENT).getText();
136 lineNumberTypeMap.put(currentNode.getLineNo(), typeName);
137 }
138 }
139 currentNode = currentNode.getNextSibling();
140 }
141 }
142
143 @Override
144 public void finishTree(DetailAST rootAST) {
145 if (!lineNumberTypeMap.isEmpty()) {
146 if (!publicTypeFound) {
147 // skip first top-level type.
148 lineNumberTypeMap.remove(lineNumberTypeMap.firstKey());
149 }
150
151 for (Map.Entry<Integer, String> entry
152 : lineNumberTypeMap.entrySet()) {
153 log(entry.getKey(), MSG_KEY, entry.getValue());
154 }
155 }
156 }
157
158 /**
159 * Checks if a type is public.
160 * @param typeDef type definition node.
161 * @return true if a type has a public access level modifier.
162 */
163 private static boolean isPublic(DetailAST typeDef) {
164 final DetailAST modifiers =
165 typeDef.findFirstToken(TokenTypes.MODIFIERS);
166 return modifiers.findFirstToken(TokenTypes.LITERAL_PUBLIC) != null;
167 }
168
169 }