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.annotation; 021 022import java.util.Objects; 023import java.util.Optional; 024import java.util.regex.Pattern; 025import java.util.stream.Stream; 026 027import com.puppycrawl.tools.checkstyle.StatelessCheck; 028import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 029import com.puppycrawl.tools.checkstyle.api.DetailAST; 030import com.puppycrawl.tools.checkstyle.api.TokenTypes; 031import com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocTagInfo; 032import com.puppycrawl.tools.checkstyle.utils.AnnotationUtil; 033import com.puppycrawl.tools.checkstyle.utils.CommonUtil; 034import com.puppycrawl.tools.checkstyle.utils.JavadocUtil; 035 036/** 037 * <p> 038 * Verifies that the {@code @Override} annotation is present 039 * when the {@code @inheritDoc} javadoc tag is present. 040 * </p> 041 * <p> 042 * Rationale: The @Override annotation helps 043 * compiler tools ensure that an override is actually occurring. It is 044 * quite easy to accidentally overload a method or hide a static method 045 * and using the @Override annotation points out these problems. 046 * </p> 047 * <p> 048 * This check will log a violation if using the @inheritDoc tag on a method that 049 * is not valid (ex: private, or static method). 050 * </p> 051 * <p> 052 * There is a slight difference between the @Override annotation in Java 5 versus 053 * Java 6 and above. In Java 5, any method overridden from an interface cannot 054 * be annotated with @Override. In Java 6 this behavior is allowed. 055 * </p> 056 * <p> 057 * As a result of the aforementioned difference between Java 5 and Java 6, a 058 * property called {@code javaFiveCompatibility} is available. This 059 * property will only check classes, interfaces, etc. that do not contain the 060 * extends or implements keyword or are not anonymous classes. This means it 061 * only checks methods overridden from {@code java.lang.Object}. 062 * <b>Java 5 Compatibility mode severely limits this check. It is recommended to 063 * only use it on Java 5 source.</b> 064 * </p> 065 * <ul> 066 * <li> 067 * Property {@code javaFiveCompatibility} - Enable java 5 compatibility mode. 068 * Type is {@code boolean}. 069 * Default value is {@code false}. 070 * </li> 071 * </ul> 072 * <p> 073 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker} 074 * </p> 075 * <p> 076 * Violation Message Keys: 077 * </p> 078 * <ul> 079 * <li> 080 * {@code annotation.missing.override} 081 * </li> 082 * <li> 083 * {@code tag.not.valid.on} 084 * </li> 085 * </ul> 086 * 087 * @since 5.0 088 */ 089@StatelessCheck 090public final class MissingOverrideCheck extends AbstractCheck { 091 092 /** 093 * A key is pointing to the warning message text in "messages.properties" 094 * file. 095 */ 096 public static final String MSG_KEY_TAG_NOT_VALID_ON = "tag.not.valid.on"; 097 098 /** 099 * A key is pointing to the warning message text in "messages.properties" 100 * file. 101 */ 102 public static final String MSG_KEY_ANNOTATION_MISSING_OVERRIDE = 103 "annotation.missing.override"; 104 105 /** Compiled regexp to match Javadoc tags with no argument and {}. */ 106 private static final Pattern MATCH_INHERIT_DOC = 107 CommonUtil.createPattern("\\{\\s*@(inheritDoc)\\s*\\}"); 108 109 /** 110 * Enable java 5 compatibility mode. 111 */ 112 private boolean javaFiveCompatibility; 113 114 /** 115 * Setter to enable java 5 compatibility mode. 116 * 117 * @param compatibility compatibility or not 118 * @since 5.0 119 */ 120 public void setJavaFiveCompatibility(final boolean compatibility) { 121 javaFiveCompatibility = compatibility; 122 } 123 124 @Override 125 public int[] getDefaultTokens() { 126 return getRequiredTokens(); 127 } 128 129 @Override 130 public int[] getAcceptableTokens() { 131 return getRequiredTokens(); 132 } 133 134 @Override 135 public boolean isCommentNodesRequired() { 136 return true; 137 } 138 139 @Override 140 public int[] getRequiredTokens() { 141 return new int[] 142 {TokenTypes.METHOD_DEF, }; 143 } 144 145 @Override 146 public void visitToken(final DetailAST ast) { 147 final boolean containsTag = containsInheritDocTag(ast); 148 if (containsTag && !JavadocTagInfo.INHERIT_DOC.isValidOn(ast)) { 149 log(ast, MSG_KEY_TAG_NOT_VALID_ON, 150 JavadocTagInfo.INHERIT_DOC.getText()); 151 } 152 else { 153 boolean check = true; 154 155 if (javaFiveCompatibility) { 156 final DetailAST defOrNew = ast.getParent().getParent(); 157 158 if (defOrNew.findFirstToken(TokenTypes.EXTENDS_CLAUSE) != null 159 || defOrNew.findFirstToken(TokenTypes.IMPLEMENTS_CLAUSE) != null 160 || defOrNew.getType() == TokenTypes.LITERAL_NEW) { 161 check = false; 162 } 163 } 164 165 if (check 166 && containsTag 167 && !AnnotationUtil.hasOverrideAnnotation(ast)) { 168 log(ast, MSG_KEY_ANNOTATION_MISSING_OVERRIDE); 169 } 170 } 171 } 172 173 /** 174 * Checks to see if the ast contains a inheritDoc tag. 175 * 176 * @param ast method AST node 177 * @return true if contains the tag 178 */ 179 private static boolean containsInheritDocTag(DetailAST ast) { 180 final DetailAST modifiers = ast.getFirstChild(); 181 final DetailAST startNode; 182 if (modifiers.hasChildren()) { 183 startNode = Optional.ofNullable(ast.getFirstChild() 184 .findFirstToken(TokenTypes.ANNOTATION)) 185 .orElse(modifiers); 186 } 187 else { 188 startNode = ast.findFirstToken(TokenTypes.TYPE); 189 } 190 final Optional<String> javadoc = 191 Stream.iterate(startNode.getLastChild(), Objects::nonNull, 192 DetailAST::getPreviousSibling) 193 .filter(node -> node.getType() == TokenTypes.BLOCK_COMMENT_BEGIN) 194 .map(DetailAST::getFirstChild) 195 .map(DetailAST::getText) 196 .filter(JavadocUtil::isJavadocComment) 197 .findFirst(); 198 return javadoc.isPresent() 199 && MATCH_INHERIT_DOC.matcher(javadoc.orElseThrow()).find(); 200 } 201 202}