View Javadoc
1   ////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code for adherence to a set of rules.
3   // Copyright (C) 2001-2018 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.CommonUtils;
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   * @author maxvetrenko
86   */
87  @FileStatefulCheck
88  public class OneTopLevelClassCheck extends AbstractCheck {
89  
90      /**
91       * A key is pointing to the warning message text in "messages.properties"
92       * file.
93       */
94      public static final String MSG_KEY = "one.top.level.class";
95  
96      /**
97       * True if a java source file contains a type
98       * with a public access level modifier.
99       */
100     private boolean publicTypeFound;
101 
102     /** Mapping between type names and line numbers of the type declarations.*/
103     private final SortedMap<Integer, String> lineNumberTypeMap = new TreeMap<>();
104 
105     @Override
106     public int[] getDefaultTokens() {
107         return getRequiredTokens();
108     }
109 
110     @Override
111     public int[] getAcceptableTokens() {
112         return getRequiredTokens();
113     }
114 
115     // ZERO tokens as Check do Traverse of Tree himself, he does not need to subscribed to Tokens
116     @Override
117     public int[] getRequiredTokens() {
118         return CommonUtils.EMPTY_INT_ARRAY;
119     }
120 
121     @Override
122     public void beginTree(DetailAST rootAST) {
123         publicTypeFound = false;
124         lineNumberTypeMap.clear();
125 
126         DetailAST currentNode = rootAST;
127         while (currentNode != null) {
128             if (currentNode.getType() == TokenTypes.CLASS_DEF
129                     || currentNode.getType() == TokenTypes.ENUM_DEF
130                     || currentNode.getType() == TokenTypes.INTERFACE_DEF) {
131                 if (isPublic(currentNode)) {
132                     publicTypeFound = true;
133                 }
134                 else {
135                     final String typeName = currentNode
136                             .findFirstToken(TokenTypes.IDENT).getText();
137                     lineNumberTypeMap.put(currentNode.getLineNo(), typeName);
138                 }
139             }
140             currentNode = currentNode.getNextSibling();
141         }
142     }
143 
144     @Override
145     public void finishTree(DetailAST rootAST) {
146         if (!lineNumberTypeMap.isEmpty()) {
147             if (!publicTypeFound) {
148                 // skip first top-level type.
149                 lineNumberTypeMap.remove(lineNumberTypeMap.firstKey());
150             }
151 
152             for (Map.Entry<Integer, String> entry
153                     : lineNumberTypeMap.entrySet()) {
154                 log(entry.getKey(), MSG_KEY, entry.getValue());
155             }
156         }
157     }
158 
159     /**
160      * Checks if a type is public.
161      * @param typeDef type definition node.
162      * @return true if a type has a public access level modifier.
163      */
164     private static boolean isPublic(DetailAST typeDef) {
165         final DetailAST modifiers =
166                 typeDef.findFirstToken(TokenTypes.MODIFIERS);
167         return modifiers.findFirstToken(TokenTypes.LITERAL_PUBLIC) != null;
168     }
169 
170 }