001////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code for adherence to a set of rules.
003// Copyright (C) 2001-2017 the original author or authors.
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////////////////////////////////////////////////////////////////////////////////
019
020package com.puppycrawl.tools.checkstyle.checks;
021
022import java.io.File;
023import java.util.regex.Pattern;
024
025import com.puppycrawl.tools.checkstyle.FileStatefulCheck;
026import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
027import com.puppycrawl.tools.checkstyle.api.DetailAST;
028import com.puppycrawl.tools.checkstyle.api.TokenTypes;
029
030/**
031 * Checks that the outer type name and the file name match.
032 * @author Oliver Burn
033 * @author maxvetrenko
034 */
035@FileStatefulCheck
036public class OuterTypeFilenameCheck extends AbstractCheck {
037    /**
038     * A key is pointing to the warning message text in "messages.properties"
039     * file.
040     */
041    public static final String MSG_KEY = "type.file.mismatch";
042
043    /** Pattern matching any file extension with dot included. */
044    private static final Pattern FILE_EXTENSION_PATTERN = Pattern.compile("\\.[^.]*$");
045
046    /** Indicates whether the first token has been seen in the file. */
047    private boolean seenFirstToken;
048
049    /** Current file name. */
050    private String fileName;
051
052    /** If file has public type. */
053    private boolean hasPublic;
054
055    /** If first type has has same name as file. */
056    private boolean validFirst;
057
058    /** Outer type with mismatched file name. */
059    private DetailAST wrongType;
060
061    @Override
062    public int[] getDefaultTokens() {
063        return getRequiredTokens();
064    }
065
066    @Override
067    public int[] getAcceptableTokens() {
068        return getRequiredTokens();
069    }
070
071    @Override
072    public int[] getRequiredTokens() {
073        return new int[] {
074            TokenTypes.CLASS_DEF,
075            TokenTypes.INTERFACE_DEF,
076            TokenTypes.ENUM_DEF,
077            TokenTypes.ANNOTATION_DEF,
078        };
079    }
080
081    @Override
082    public void beginTree(DetailAST rootAST) {
083        fileName = getFileName();
084        seenFirstToken = false;
085        validFirst = false;
086        hasPublic = false;
087        wrongType = null;
088    }
089
090    @Override
091    public void visitToken(DetailAST ast) {
092        if (seenFirstToken) {
093            final DetailAST modifiers = ast.findFirstToken(TokenTypes.MODIFIERS);
094            if (modifiers.findFirstToken(TokenTypes.LITERAL_PUBLIC) != null
095                    && ast.getParent() == null) {
096                hasPublic = true;
097            }
098        }
099        else {
100            final String outerTypeName = ast.findFirstToken(TokenTypes.IDENT).getText();
101
102            if (fileName.equals(outerTypeName)) {
103                validFirst = true;
104            }
105            else {
106                wrongType = ast;
107            }
108        }
109        seenFirstToken = true;
110    }
111
112    @Override
113    public void finishTree(DetailAST rootAST) {
114        if (!validFirst && !hasPublic && wrongType != null) {
115            log(wrongType.getLineNo(), MSG_KEY);
116        }
117    }
118
119    /**
120     * Get source file name.
121     * @return source file name.
122     */
123    private String getFileName() {
124        String name = getFileContents().getFileName();
125        name = name.substring(name.lastIndexOf(File.separatorChar) + 1);
126        name = FILE_EXTENSION_PATTERN.matcher(name).replaceAll("");
127        return name;
128    }
129}