001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2014  Oliver Burn
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////////////////////////////////////////////////////////////////////////////////
019package com.puppycrawl.tools.checkstyle.checks.design;
020
021import java.util.Map;
022import java.util.TreeMap;
023
024import com.puppycrawl.tools.checkstyle.api.Check;
025import com.puppycrawl.tools.checkstyle.api.DetailAST;
026import com.puppycrawl.tools.checkstyle.api.TokenTypes;
027
028/**
029 *
030 * Checks that each top-level class, interface
031 * or enum resides in a source file of its own.
032 * <p>
033 * Official description of a 'top-level' term:<a
034 * href="http://docs.oracle.com/javase/specs/jls/se7/html/jls-7.html#jls-7.6">
035 * 7.6. Top Level Type Declarations</a>. If file doesn't contains
036 * public class, enum or interface, top-level type is the first type in file.
037 * </p>
038 * <p>
039 * An example of code with violations:
040 * </p>
041 * <pre><code>
042 * public class Foo{
043 *     //methods
044 * }
045 *
046 * class Foo2{
047 *     //methods
048 * }
049 * </code></pre>
050 * <p>
051 * An example of code without top-level public type:
052 * </p>
053 * <pre><code>
054 * class Foo{ //top-level class
055 *     //methods
056 * }
057 *
058 * class Foo2{
059 *     //methods
060 * }
061 * </code></pre>
062 * <p>
063 * An example of check's configuration:
064 * </p>
065 * <pre>
066 * &lt;module name="OneTopLevelClass"/&gt;
067 * </pre>
068 *
069 * <p>
070 * An example of code without violations:
071 * </p>
072 * <pre><code>
073 * public class Foo{
074 *     //methods
075 * }
076 * </code></pre>
077 *
078 * @author maxvetrenko
079 */
080public class OneTopLevelClassCheck extends Check
081{
082
083    /**
084     * True if a java source file contains a type
085     * with a public access level modifier.
086     */
087    private boolean publicTypeFound;
088
089    /** Mapping between type names and line numbers of the type declarations.*/
090    private TreeMap<Integer, String> lineNumberTypeMap =
091            new TreeMap<Integer, String>();
092
093    @Override
094    public int[] getDefaultTokens()
095    {
096        return new int[] {};
097    }
098
099    @Override
100    public void beginTree(DetailAST rootAST)
101    {
102        DetailAST currentNode = rootAST;
103        while (currentNode != null) {
104            if (currentNode.getType() == TokenTypes.CLASS_DEF
105                    || currentNode.getType() == TokenTypes.ENUM_DEF
106                    || currentNode.getType() == TokenTypes.INTERFACE_DEF)
107            {
108                if (isPublic(currentNode)) {
109                    publicTypeFound = true;
110                }
111
112                else {
113                    final String typeName = currentNode.
114                            findFirstToken(TokenTypes.IDENT).getText();
115                    lineNumberTypeMap.put(currentNode.getLineNo(), typeName);
116                }
117            }
118            currentNode = currentNode.getNextSibling();
119        }
120    }
121
122    @Override
123    public void finishTree(DetailAST rootAST)
124    {
125        if (!publicTypeFound && !lineNumberTypeMap.isEmpty()) {
126            // skip first top-level type.
127            lineNumberTypeMap.remove(lineNumberTypeMap.firstKey());
128        }
129
130        for (Map.Entry<Integer, String> entry
131                : lineNumberTypeMap.entrySet())
132        {
133            log(entry.getKey(), "one.top.level.class", entry.getValue());
134        }
135
136        lineNumberTypeMap.clear();
137        publicTypeFound = false;
138    }
139
140    /**
141     * Checks if a type is public.
142     * @param typeDef type definition node.
143     * @return true if a type has a public access level modifier.
144     */
145    private boolean isPublic(DetailAST typeDef)
146    {
147        final DetailAST modifiers =
148                typeDef.findFirstToken(TokenTypes.MODIFIERS);
149        return modifiers.findFirstToken(TokenTypes.LITERAL_PUBLIC) != null;
150    }
151}