001/////////////////////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code and other text files for adherence to a set of rules. 003// Copyright (C) 2001-2024 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; 029import com.puppycrawl.tools.checkstyle.utils.TokenUtil; 030 031/** 032 * <div> 033 * Checks that the outer type name and the file name match. 034 * For example, the class {@code Foo} must be in a file named {@code Foo.java}. 035 * </div> 036 * 037 * <p> 038 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker} 039 * </p> 040 * 041 * <p> 042 * Violation Message Keys: 043 * </p> 044 * <ul> 045 * <li> 046 * {@code type.file.mismatch} 047 * </li> 048 * </ul> 049 * 050 * @since 5.3 051 */ 052@FileStatefulCheck 053public class OuterTypeFilenameCheck extends AbstractCheck { 054 055 /** 056 * A key is pointing to the warning message text in "messages.properties" 057 * file. 058 */ 059 public static final String MSG_KEY = "type.file.mismatch"; 060 061 /** Pattern matching any file extension with dot included. */ 062 private static final Pattern FILE_EXTENSION_PATTERN = Pattern.compile("\\.[^.]*$"); 063 064 /** Indicates whether the first token has been seen in the file. */ 065 private boolean seenFirstToken; 066 067 /** Current file name. */ 068 private String fileName; 069 070 /** If file has public type. */ 071 private boolean hasPublic; 072 073 /** Outer type with mismatched file name. */ 074 private DetailAST wrongType; 075 076 @Override 077 public int[] getDefaultTokens() { 078 return getRequiredTokens(); 079 } 080 081 @Override 082 public int[] getAcceptableTokens() { 083 return getRequiredTokens(); 084 } 085 086 @Override 087 public int[] getRequiredTokens() { 088 return new int[] { 089 TokenTypes.CLASS_DEF, 090 TokenTypes.INTERFACE_DEF, 091 TokenTypes.ENUM_DEF, 092 TokenTypes.ANNOTATION_DEF, 093 TokenTypes.RECORD_DEF, 094 }; 095 } 096 097 @Override 098 public void beginTree(DetailAST rootAST) { 099 fileName = getSourceFileName(); 100 seenFirstToken = false; 101 hasPublic = false; 102 wrongType = null; 103 } 104 105 @Override 106 public void visitToken(DetailAST ast) { 107 if (seenFirstToken) { 108 final DetailAST modifiers = ast.findFirstToken(TokenTypes.MODIFIERS); 109 if (modifiers.findFirstToken(TokenTypes.LITERAL_PUBLIC) != null 110 && TokenUtil.isRootNode(ast.getParent())) { 111 hasPublic = true; 112 } 113 } 114 else { 115 final String outerTypeName = ast.findFirstToken(TokenTypes.IDENT).getText(); 116 117 if (!fileName.equals(outerTypeName)) { 118 wrongType = ast; 119 } 120 } 121 seenFirstToken = true; 122 } 123 124 @Override 125 public void finishTree(DetailAST rootAST) { 126 if (!hasPublic && wrongType != null) { 127 log(wrongType, MSG_KEY); 128 } 129 } 130 131 /** 132 * Get source file name. 133 * 134 * @return source file name. 135 */ 136 private String getSourceFileName() { 137 String name = getFilePath(); 138 name = name.substring(name.lastIndexOf(File.separatorChar) + 1); 139 return FILE_EXTENSION_PATTERN.matcher(name).replaceAll(""); 140 } 141 142}