001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2022 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 * To configure the check: 074 * </p> 075 * <pre> 076 * <module name="MissingOverride"/> 077 * </pre> 078 * <p>Example:</p> 079 * <pre> 080 * class Test extends SuperClass { 081 * 082 * /** {@inheritDoc} */ 083 * @Override 084 * public void test1() { // OK 085 * 086 * } 087 * 088 * /** {@inheritDoc} */ 089 * public void test2() { // violation, should be annotated with @Override 090 * 091 * } 092 * 093 * /** {@inheritDoc} */ 094 * private void test3() { // violation, using the @inheritDoc tag on private method 095 * 096 * } 097 * 098 * /** {@inheritDoc} */ 099 * public static void test4() { // violation, using the @inheritDoc tag on static method 100 * 101 * } 102 * } 103 * </pre> 104 * <p> 105 * To configure the check for the {@code javaFiveCompatibility} mode: 106 * </p> 107 * <pre> 108 * <module name="MissingOverride"> 109 * <property name="javaFiveCompatibility" 110 * value="true"/> 111 * </module> 112 * </pre> 113 * <p>Example:</p> 114 * <pre> 115 * class Test1 { 116 * 117 * /** {@inheritDoc} */ 118 * public void equals() { // violation, should be annotated with @Override 119 * 120 * } 121 * } 122 * 123 * interface Test2 { 124 * 125 * /** {@inheritDoc} */ 126 * void test(); // violation, should be annotated with @Override 127 * } 128 * 129 * class Test3 extends SuperClass { 130 * 131 * /** {@inheritDoc} */ 132 * public void test() { // OK, is ignored because class extends other class 133 * 134 * } 135 * } 136 * 137 * class Test4 implements SuperInterface { 138 * 139 * /** {@inheritDoc} */ 140 * public void test() { // OK, is ignored because class implements interface 141 * 142 * } 143 * } 144 * 145 * class Test5 { 146 * Runnable r = new Runnable() { 147 * /** {@inheritDoc} */ 148 * public void run() { // OK, is ignored because class is anonymous class 149 * 150 * } 151 * }; 152 * } 153 * </pre> 154 * <p> 155 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker} 156 * </p> 157 * <p> 158 * Violation Message Keys: 159 * </p> 160 * <ul> 161 * <li> 162 * {@code annotation.missing.override} 163 * </li> 164 * <li> 165 * {@code tag.not.valid.on} 166 * </li> 167 * </ul> 168 * 169 * @since 5.0 170 */ 171@StatelessCheck 172public final class MissingOverrideCheck extends AbstractCheck { 173 174 /** 175 * A key is pointing to the warning message text in "messages.properties" 176 * file. 177 */ 178 public static final String MSG_KEY_TAG_NOT_VALID_ON = "tag.not.valid.on"; 179 180 /** 181 * A key is pointing to the warning message text in "messages.properties" 182 * file. 183 */ 184 public static final String MSG_KEY_ANNOTATION_MISSING_OVERRIDE = 185 "annotation.missing.override"; 186 187 /** Compiled regexp to match Javadoc tags with no argument and {}. */ 188 private static final Pattern MATCH_INHERIT_DOC = 189 CommonUtil.createPattern("\\{\\s*@(inheritDoc)\\s*\\}"); 190 191 /** 192 * Enable java 5 compatibility mode. 193 */ 194 private boolean javaFiveCompatibility; 195 196 /** 197 * Setter to enable java 5 compatibility mode. 198 * 199 * @param compatibility compatibility or not 200 */ 201 public void setJavaFiveCompatibility(final boolean compatibility) { 202 javaFiveCompatibility = compatibility; 203 } 204 205 @Override 206 public int[] getDefaultTokens() { 207 return getRequiredTokens(); 208 } 209 210 @Override 211 public int[] getAcceptableTokens() { 212 return getRequiredTokens(); 213 } 214 215 @Override 216 public boolean isCommentNodesRequired() { 217 return true; 218 } 219 220 @Override 221 public int[] getRequiredTokens() { 222 return new int[] 223 {TokenTypes.METHOD_DEF, }; 224 } 225 226 @Override 227 public void visitToken(final DetailAST ast) { 228 final boolean containsTag = containsInheritDocTag(ast); 229 if (containsTag && !JavadocTagInfo.INHERIT_DOC.isValidOn(ast)) { 230 log(ast, MSG_KEY_TAG_NOT_VALID_ON, 231 JavadocTagInfo.INHERIT_DOC.getText()); 232 } 233 else { 234 boolean check = true; 235 236 if (javaFiveCompatibility) { 237 final DetailAST defOrNew = ast.getParent().getParent(); 238 239 if (defOrNew.findFirstToken(TokenTypes.EXTENDS_CLAUSE) != null 240 || defOrNew.findFirstToken(TokenTypes.IMPLEMENTS_CLAUSE) != null 241 || defOrNew.getType() == TokenTypes.LITERAL_NEW) { 242 check = false; 243 } 244 } 245 246 if (check 247 && containsTag 248 && !AnnotationUtil.hasOverrideAnnotation(ast)) { 249 log(ast, MSG_KEY_ANNOTATION_MISSING_OVERRIDE); 250 } 251 } 252 } 253 254 /** 255 * Checks to see if the ast contains a inheritDoc tag. 256 * 257 * @param ast method AST node 258 * @return true if contains the tag 259 */ 260 private static boolean containsInheritDocTag(DetailAST ast) { 261 final DetailAST modifiers = ast.getFirstChild(); 262 final DetailAST startNode; 263 if (modifiers.hasChildren()) { 264 startNode = Optional.ofNullable(ast.getFirstChild() 265 .findFirstToken(TokenTypes.ANNOTATION)) 266 .orElse(modifiers); 267 } 268 else { 269 startNode = ast.findFirstToken(TokenTypes.TYPE); 270 } 271 final Optional<String> javadoc = 272 Stream.iterate(startNode.getLastChild(), Objects::nonNull, 273 DetailAST::getPreviousSibling) 274 .filter(node -> node.getType() == TokenTypes.BLOCK_COMMENT_BEGIN) 275 .map(DetailAST::getFirstChild) 276 .map(DetailAST::getText) 277 .filter(JavadocUtil::isJavadocComment) 278 .findFirst(); 279 return javadoc.isPresent() 280 && MATCH_INHERIT_DOC.matcher(javadoc.get()).find(); 281 } 282 283}