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   * &lt;module name="OneTopLevelClass"/&gt;
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 }