View Javadoc
1   ///////////////////////////////////////////////////////////////////////////////////////////////
2   // checkstyle: Checks Java source code and other text files for adherence to a set of rules.
3   // Copyright (C) 2001-2024 the original author or authors.
4   //
5   // This library is free software; you can redistribute it and/or
6   // modify it under the terms of the GNU Lesser General Public
7   // License as published by the Free Software Foundation; either
8   // version 2.1 of the License, or (at your option) any later version.
9   //
10  // This library is distributed in the hope that it will be useful,
11  // but WITHOUT ANY WARRANTY; without even the implied warranty of
12  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  // Lesser General Public License for more details.
14  //
15  // You should have received a copy of the GNU Lesser General Public
16  // License along with this library; if not, write to the Free Software
17  // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18  ///////////////////////////////////////////////////////////////////////////////////////////////
19  
20  package com.puppycrawl.tools.checkstyle.checks.annotation;
21  
22  import java.util.Objects;
23  import java.util.Optional;
24  import java.util.regex.Pattern;
25  import java.util.stream.Stream;
26  
27  import com.puppycrawl.tools.checkstyle.StatelessCheck;
28  import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
29  import com.puppycrawl.tools.checkstyle.api.DetailAST;
30  import com.puppycrawl.tools.checkstyle.api.TokenTypes;
31  import com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocTagInfo;
32  import com.puppycrawl.tools.checkstyle.utils.AnnotationUtil;
33  import com.puppycrawl.tools.checkstyle.utils.CommonUtil;
34  import com.puppycrawl.tools.checkstyle.utils.JavadocUtil;
35  
36  /**
37   * <p>
38   * Verifies that the {@code @Override} annotation is present
39   * when the {@code @inheritDoc} javadoc tag is present.
40   * </p>
41   * <p>
42   * Rationale: The &#64;Override annotation helps
43   * compiler tools ensure that an override is actually occurring.  It is
44   * quite easy to accidentally overload a method or hide a static method
45   * and using the &#64;Override annotation points out these problems.
46   * </p>
47   * <p>
48   * This check will log a violation if using the &#64;inheritDoc tag on a method that
49   * is not valid (ex: private, or static method).
50   * </p>
51   * <p>
52   * There is a slight difference between the &#64;Override annotation in Java 5 versus
53   * Java 6 and above. In Java 5, any method overridden from an interface cannot
54   * be annotated with &#64;Override. In Java 6 this behavior is allowed.
55   * </p>
56   * <p>
57   * As a result of the aforementioned difference between Java 5 and Java 6, a
58   * property called {@code javaFiveCompatibility} is available. This
59   * property will only check classes, interfaces, etc. that do not contain the
60   * extends or implements keyword or are not anonymous classes. This means it
61   * only checks methods overridden from {@code java.lang.Object}.
62   * <b>Java 5 Compatibility mode severely limits this check. It is recommended to
63   * only use it on Java 5 source.</b>
64   * </p>
65   * <ul>
66   * <li>
67   * Property {@code javaFiveCompatibility} - Enable java 5 compatibility mode.
68   * Type is {@code boolean}.
69   * Default value is {@code false}.
70   * </li>
71   * </ul>
72   * <p>
73   * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
74   * </p>
75   * <p>
76   * Violation Message Keys:
77   * </p>
78   * <ul>
79   * <li>
80   * {@code annotation.missing.override}
81   * </li>
82   * <li>
83   * {@code tag.not.valid.on}
84   * </li>
85   * </ul>
86   *
87   * @since 5.0
88   */
89  @StatelessCheck
90  public final class MissingOverrideCheck extends AbstractCheck {
91  
92      /**
93       * A key is pointing to the warning message text in "messages.properties"
94       * file.
95       */
96      public static final String MSG_KEY_TAG_NOT_VALID_ON = "tag.not.valid.on";
97  
98      /**
99       * 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 }