001///////////////////////////////////////////////////////////////////////////////////////////////
002// checkstyle: Checks Java source code and other text files for adherence to a set of rules.
003// Copyright (C) 2001-2023 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.design;
021
022import java.util.Arrays;
023import java.util.Objects;
024import java.util.Optional;
025import java.util.Set;
026import java.util.function.Predicate;
027import java.util.regex.Matcher;
028import java.util.regex.Pattern;
029import java.util.stream.Collectors;
030
031import com.puppycrawl.tools.checkstyle.StatelessCheck;
032import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
033import com.puppycrawl.tools.checkstyle.api.DetailAST;
034import com.puppycrawl.tools.checkstyle.api.Scope;
035import com.puppycrawl.tools.checkstyle.api.TokenTypes;
036import com.puppycrawl.tools.checkstyle.utils.JavadocUtil;
037import com.puppycrawl.tools.checkstyle.utils.ScopeUtil;
038import com.puppycrawl.tools.checkstyle.utils.TokenUtil;
039
040/**
041 * <p>
042 * Checks that classes are designed for extension (subclass creation).
043 * </p>
044 * <p>
045 * Nothing wrong could be with founded classes.
046 * This check makes sense only for library projects (not application projects)
047 * which care of ideal OOP-design to make sure that class works in all cases even misusage.
048 * Even in library projects this check most likely will find classes that are designed for extension
049 * by somebody. User needs to use suppressions extensively to got a benefit from this check,
050 * and keep in suppressions all confirmed/known classes that are deigned for inheritance
051 * intentionally to let the check catch only new classes, and bring this to team/user attention.
052 * </p>
053 *
054 * <p>
055 * ATTENTION: Only user can decide whether a class is designed for extension or not.
056 * The check just shows all classes which are possibly designed for extension.
057 * If smth inappropriate is found please use suppression.
058 * </p>
059 *
060 * <p>
061 * ATTENTION: If the method which can be overridden in a subclass has a javadoc comment
062 * (a good practice is to explain its self-use of overridable methods) the check will not
063 * rise a violation. The violation can also be skipped if the method which can be overridden
064 * in a subclass has one or more annotations that are specified in ignoredAnnotations
065 * option. Note, that by default @Override annotation is not included in the
066 * ignoredAnnotations set as in a subclass the method which has the annotation can also be
067 * overridden in its subclass.
068 * </p>
069 * <p>
070 * Problem is described at "Effective Java, 2nd Edition by Joshua Bloch" book, chapter
071 * "Item 17: Design and document for inheritance or else prohibit it".
072 * </p>
073 * <p>
074 * Some quotes from book:
075 * </p>
076 * <blockquote>The class must document its self-use of overridable methods.
077 * By convention, a method that invokes overridable methods contains a description
078 * of these invocations at the end of its documentation comment. The description
079 * begins with the phrase “This implementation.”
080 * </blockquote>
081 * <blockquote>
082 * The best solution to this problem is to prohibit subclassing in classes that
083 * are not designed and documented to be safely subclassed.
084 * </blockquote>
085 * <blockquote>
086 * If a concrete class does not implement a standard interface, then you may
087 * inconvenience some programmers by prohibiting inheritance. If you feel that you
088 * must allow inheritance from such a class, one reasonable approach is to ensure
089 * that the class never invokes any of its overridable methods and to document this
090 * fact. In other words, eliminate the class’s self-use of overridable methods entirely.
091 * In doing so, you’ll create a class that is reasonably safe to subclass. Overriding a
092 * method will never affect the behavior of any other method.
093 * </blockquote>
094 * <p>
095 * The check finds classes that have overridable methods (public or protected methods
096 * that are non-static, not-final, non-abstract) and have non-empty implementation.
097 * </p>
098 * <p>
099 * Rationale: This library design style protects superclasses against being broken
100 * by subclasses. The downside is that subclasses are limited in their flexibility,
101 * in particular they cannot prevent execution of code in the superclass, but that
102 * also means that subclasses cannot corrupt the state of the superclass by forgetting
103 * to call the superclass's method.
104 * </p>
105 * <p>
106 * More specifically, it enforces a programming style where superclasses provide
107 * empty "hooks" that can be implemented by subclasses.
108 * </p>
109 * <p>
110 * Example of code that cause violation as it is designed for extension:
111 * </p>
112 * <pre>
113 * public abstract class Plant {
114 *   private String roots;
115 *   private String trunk;
116 *
117 *   protected void validate() {
118 *     if (roots == null) throw new IllegalArgumentException("No roots!");
119 *     if (trunk == null) throw new IllegalArgumentException("No trunk!");
120 *   }
121 *
122 *   public abstract void grow();
123 * }
124 *
125 * public class Tree extends Plant {
126 *   private List leaves;
127 *
128 *   &#64;Overrides
129 *   protected void validate() {
130 *     super.validate();
131 *     if (leaves == null) throw new IllegalArgumentException("No leaves!");
132 *   }
133 *
134 *   public void grow() {
135 *     validate();
136 *   }
137 * }
138 * </pre>
139 * <p>
140 * Example of code without violation:
141 * </p>
142 * <pre>
143 * public abstract class Plant {
144 *   private String roots;
145 *   private String trunk;
146 *
147 *   private void validate() {
148 *     if (roots == null) throw new IllegalArgumentException("No roots!");
149 *     if (trunk == null) throw new IllegalArgumentException("No trunk!");
150 *     validateEx();
151 *   }
152 *
153 *   protected void validateEx() { }
154 *
155 *   public abstract void grow();
156 * }
157 * </pre>
158 * <ul>
159 * <li>
160 * Property {@code ignoredAnnotations} - Specify annotations which allow the check to
161 * skip the method from validation.
162 * Type is {@code java.lang.String[]}.
163 * Default value is {@code After, AfterClass, Before, BeforeClass, Test}.
164 * </li>
165 * <li>
166 * Property {@code requiredJavadocPhrase} - Specify the comment text pattern which qualifies a
167 * method as designed for extension. Supports multi-line regex.
168 * Type is {@code java.util.regex.Pattern}.
169 * Default value is {@code ".*"}.
170 * </li>
171 * </ul>
172 * <p>
173 * To configure the check:
174 * </p>
175 * <pre>
176 * &lt;module name=&quot;DesignForExtension&quot;/&gt;
177 * </pre>
178 * <p>Example:</p>
179 * <pre>
180 * public abstract class Foo {
181 *   private int bar;
182 *
183 *   public int m1() {return 2;}  // Violation. No javadoc.
184 *
185 *   public int m2() {return 8;}  // Violation. No javadoc.
186 *
187 *   private void m3() {m4();}  // OK. Private method.
188 *
189 *   protected void m4() { }  // OK. No implementation.
190 *
191 *   public abstract void m5();  // OK. Abstract method.
192 *
193 *   &#47;**
194 *    * This implementation ...
195 *    &#64;return some int value.
196 *    *&#47;
197 *   public int m6() {return 1;}  // OK. Have javadoc on overridable method.
198 *
199 *   &#47;**
200 *    * Some comments ...
201 *    *&#47;
202 *   public int m7() {return 1;}  // OK. Have javadoc on overridable method.
203 *
204 *   &#47;**
205 *    * This
206 *    * implementation ...
207 *    *&#47;
208 *   public int m8() {return 2;}  // OK. Have javadoc on overridable method.
209 *
210 *   &#64;Override
211 *   public String toString() {  // Violation. No javadoc for @Override method.
212 *     return "";
213 *   }
214 * }
215 * </pre>
216 * <p>
217 * To configure the check to allow methods which have @Override annotations
218 * to be designed for extension.
219 * </p>
220 * <pre>
221 * &lt;module name=&quot;DesignForExtension&quot;&gt;
222 *   &lt;property name=&quot;ignoredAnnotations&quot; value=&quot;Override&quot;/&gt;
223 * &lt;/module&gt;
224 * </pre>
225 * <p>Example:</p>
226 * <pre>
227 * public abstract class Foo {
228 *   private int bar;
229 *
230 *   public int m1() {return 2;}  // Violation. No javadoc.
231 *
232 *   public int m2() {return 8;}  // Violation. No javadoc.
233 *
234 *   private void m3() {m4();}  // OK. Private method.
235 *
236 *   protected void m4() { }  // OK. No implementation.
237 *
238 *   public abstract void m5();  // OK. Abstract method.
239 *
240 *   &#47;**
241 *    * This implementation ...
242 *    &#64;return some int value.
243 *    *&#47;
244 *   public int m6() {return 1;}  // OK. Have javadoc on overridable method.
245 *
246 *   &#47;**
247 *    * Some comments ...
248 *    *&#47;
249 *   public int m7() {return 1;}  // OK. Have javadoc on overridable method.
250 *
251 *   &#47;**
252 *    * This
253 *    * implementation ...
254 *    *&#47;
255 *   public int m8() {return 2;}  // OK. Have javadoc on overridable method.
256 *
257 *   &#64;Override
258 *   public String toString() {  // OK. Have javadoc on overridable method.
259 *     return "";
260 *   }
261 * }
262 * </pre>
263 * <p>
264 * To configure the check to allow methods which contain a specified comment text
265 * pattern in their javadoc to be designed for extension.
266 * </p>
267 * <pre>
268 * &lt;module name=&quot;DesignForExtension&quot;&gt;
269 *   &lt;property name=&quot;requiredJavadocPhrase&quot; value=&quot;This implementation&quot;/&gt;
270 * &lt;/module&gt;
271 * </pre>
272 * <p>Example:</p>
273 * <pre>
274 * public abstract class Foo {
275 *   private int bar;
276 *
277 *   public int m1() {return 2;}  // Violation. No javadoc.
278 *
279 *   public int m2() {return 8;}  // Violation. No javadoc.
280 *
281 *   private void m3() {m4();}  // OK. Private method.
282 *
283 *   protected void m4() { }  // OK. No implementation.
284 *
285 *   public abstract void m5();  // OK. Abstract method.
286 *
287 *   &#47;**
288 *    * This implementation ...
289 *    &#64;return some int value.
290 *    *&#47;
291 *   public int m6() {return 1;}  // OK. Have required javadoc.
292 *
293 *   &#47;**
294 *    * Some comments ...
295 *    *&#47;
296 *   public int m7() {return 1;}  // Violation. No required javadoc.
297 *
298 *   &#47;**
299 *    * This
300 *    * implementation ...
301 *    *&#47;
302 *   public int m8() {return 2;}  // Violation. No required javadoc.
303 *
304 *   &#64;Override
305 *   public String toString() {  // Violation. No required javadoc.
306 *     return "";
307 *   }
308 * }
309 * </pre>
310 * <p>
311 * To configure the check to allow methods which contain a specified comment text
312 * pattern in their javadoc which can span multiple lines
313 * to be designed for extension.
314 * </p>
315 * <pre>
316 * &lt;module name=&quot;DesignForExtension&quot;&gt;
317 *   &lt;property name=&quot;requiredJavadocPhrase&quot;
318 *     value=&quot;This[\s\S]*implementation&quot;/&gt;
319 * &lt;/module&gt;
320 * </pre>
321 * <p>Example:</p>
322 * <pre>
323 * public abstract class Foo {
324 *   private int bar;
325 *
326 *   public int m1() {return 2;}  // Violation. No javadoc.
327 *
328 *   public int m2() {return 8;}  // Violation. No javadoc.
329 *
330 *   private void m3() {m4();}
331 *
332 *   protected void m4() { }  // OK. No implementation.
333 *
334 *   public abstract void m5();  // OK. Abstract method.
335 *
336 *   &#47;**
337 *    * This implementation ...
338 *    &#64;return some int value.
339 *    *&#47;
340 *   public int m6() {return 1;}  // OK. Have required javadoc.
341 *
342 *   &#47;**
343 *    * Some comments ...
344 *    *&#47;
345 *   public int m7() {return 1;}  // Violation. No required javadoc.
346 *
347 *   &#47;**
348 *    * This
349 *    * implementation ...
350 *    *&#47;
351 *   public int m8() {return 2;}  // OK. Have required javadoc.
352 *
353 *   &#64;Override
354 *   public String toString() {  // Violation. No required javadoc.
355 *     return "";
356 *   }
357 * }
358 * </pre>
359 * <p>
360 * Parent is {@code com.puppycrawl.tools.checkstyle.TreeWalker}
361 * </p>
362 * <p>
363 * Violation Message Keys:
364 * </p>
365 * <ul>
366 * <li>
367 * {@code design.forExtension}
368 * </li>
369 * </ul>
370 *
371 * @since 3.1
372 */
373@StatelessCheck
374public class DesignForExtensionCheck extends AbstractCheck {
375
376    /**
377     * A key is pointing to the warning message text in "messages.properties"
378     * file.
379     */
380    public static final String MSG_KEY = "design.forExtension";
381
382    /**
383     * Specify annotations which allow the check to skip the method from validation.
384     */
385    private Set<String> ignoredAnnotations = Arrays.stream(new String[] {"Test", "Before", "After",
386        "BeforeClass", "AfterClass", }).collect(Collectors.toSet());
387
388    /**
389     * Specify the comment text pattern which qualifies a method as designed for extension.
390     * Supports multi-line regex.
391     */
392    private Pattern requiredJavadocPhrase = Pattern.compile(".*");
393
394    /**
395     * Setter to specify annotations which allow the check to skip the method from validation.
396     *
397     * @param ignoredAnnotations method annotations.
398     */
399    public void setIgnoredAnnotations(String... ignoredAnnotations) {
400        this.ignoredAnnotations = Arrays.stream(ignoredAnnotations).collect(Collectors.toSet());
401    }
402
403    /**
404     * Setter to specify the comment text pattern which qualifies a
405     * method as designed for extension. Supports multi-line regex.
406     *
407     * @param requiredJavadocPhrase method annotations.
408     */
409    public void setRequiredJavadocPhrase(Pattern requiredJavadocPhrase) {
410        this.requiredJavadocPhrase = requiredJavadocPhrase;
411    }
412
413    @Override
414    public int[] getDefaultTokens() {
415        return getRequiredTokens();
416    }
417
418    @Override
419    public int[] getAcceptableTokens() {
420        return getRequiredTokens();
421    }
422
423    @Override
424    public int[] getRequiredTokens() {
425        // The check does not subscribe to CLASS_DEF token as now it is stateless. If the check
426        // subscribes to CLASS_DEF token it will become stateful, since we need to have additional
427        // stack to hold CLASS_DEF tokens.
428        return new int[] {TokenTypes.METHOD_DEF};
429    }
430
431    @Override
432    public boolean isCommentNodesRequired() {
433        return true;
434    }
435
436    @Override
437    public void visitToken(DetailAST ast) {
438        if (!hasJavadocComment(ast)
439                && canBeOverridden(ast)
440                && (isNativeMethod(ast)
441                    || !hasEmptyImplementation(ast))
442                && !hasIgnoredAnnotation(ast, ignoredAnnotations)
443                && !ScopeUtil.isInRecordBlock(ast)) {
444            final DetailAST classDef = getNearestClassOrEnumDefinition(ast);
445            if (canBeSubclassed(classDef)) {
446                final String className = classDef.findFirstToken(TokenTypes.IDENT).getText();
447                final String methodName = ast.findFirstToken(TokenTypes.IDENT).getText();
448                log(ast, MSG_KEY, className, methodName);
449            }
450        }
451    }
452
453    /**
454     * Checks whether a method has a javadoc comment.
455     *
456     * @param methodDef method definition token.
457     * @return true if a method has a javadoc comment.
458     */
459    private boolean hasJavadocComment(DetailAST methodDef) {
460        return hasJavadocCommentOnToken(methodDef, TokenTypes.MODIFIERS)
461                || hasJavadocCommentOnToken(methodDef, TokenTypes.TYPE);
462    }
463
464    /**
465     * Checks whether a token has a javadoc comment.
466     *
467     * @param methodDef method definition token.
468     * @param tokenType token type.
469     * @return true if a token has a javadoc comment.
470     */
471    private boolean hasJavadocCommentOnToken(DetailAST methodDef, int tokenType) {
472        final DetailAST token = methodDef.findFirstToken(tokenType);
473        return branchContainsJavadocComment(token);
474    }
475
476    /**
477     * Checks whether a javadoc comment exists under the token.
478     *
479     * @param token tree token.
480     * @return true if a javadoc comment exists under the token.
481     */
482    private boolean branchContainsJavadocComment(DetailAST token) {
483        boolean result = false;
484        DetailAST curNode = token;
485        while (curNode != null) {
486            if (curNode.getType() == TokenTypes.BLOCK_COMMENT_BEGIN
487                    && JavadocUtil.isJavadocComment(curNode)) {
488                result = hasValidJavadocComment(curNode);
489                break;
490            }
491
492            DetailAST toVisit = curNode.getFirstChild();
493            while (toVisit == null) {
494                if (curNode == token) {
495                    break;
496                }
497
498                toVisit = curNode.getNextSibling();
499                curNode = curNode.getParent();
500            }
501            curNode = toVisit;
502        }
503
504        return result;
505    }
506
507    /**
508     * Checks whether a javadoc contains the specified comment pattern that denotes
509     * a method as designed for extension.
510     *
511     * @param detailAST the ast we are checking for possible extension
512     * @return true if the javadoc of this ast contains the required comment pattern
513     */
514    private boolean hasValidJavadocComment(DetailAST detailAST) {
515        final String javadocString =
516            JavadocUtil.getBlockCommentContent(detailAST);
517
518        final Matcher requiredJavadocPhraseMatcher =
519            requiredJavadocPhrase.matcher(javadocString);
520
521        return requiredJavadocPhraseMatcher.find();
522    }
523
524    /**
525     * Checks whether a method is native.
526     *
527     * @param ast method definition token.
528     * @return true if a methods is native.
529     */
530    private static boolean isNativeMethod(DetailAST ast) {
531        final DetailAST mods = ast.findFirstToken(TokenTypes.MODIFIERS);
532        return mods.findFirstToken(TokenTypes.LITERAL_NATIVE) != null;
533    }
534
535    /**
536     * Checks whether a method has only comments in the body (has an empty implementation).
537     * Method is OK if its implementation is empty.
538     *
539     * @param ast method definition token.
540     * @return true if a method has only comments in the body.
541     */
542    private static boolean hasEmptyImplementation(DetailAST ast) {
543        boolean hasEmptyBody = true;
544        final DetailAST methodImplOpenBrace = ast.findFirstToken(TokenTypes.SLIST);
545        final DetailAST methodImplCloseBrace = methodImplOpenBrace.getLastChild();
546        final Predicate<DetailAST> predicate = currentNode -> {
547            return currentNode != methodImplCloseBrace
548                && !TokenUtil.isCommentType(currentNode.getType());
549        };
550        final Optional<DetailAST> methodBody =
551            TokenUtil.findFirstTokenByPredicate(methodImplOpenBrace, predicate);
552        if (methodBody.isPresent()) {
553            hasEmptyBody = false;
554        }
555        return hasEmptyBody;
556    }
557
558    /**
559     * Checks whether a method can be overridden.
560     * Method can be overridden if it is not private, abstract, final or static.
561     * Note that the check has nothing to do for interfaces.
562     *
563     * @param methodDef method definition token.
564     * @return true if a method can be overridden in a subclass.
565     */
566    private static boolean canBeOverridden(DetailAST methodDef) {
567        final DetailAST modifiers = methodDef.findFirstToken(TokenTypes.MODIFIERS);
568        return ScopeUtil.getSurroundingScope(methodDef).isIn(Scope.PROTECTED)
569            && !ScopeUtil.isInInterfaceOrAnnotationBlock(methodDef)
570            && modifiers.findFirstToken(TokenTypes.LITERAL_PRIVATE) == null
571            && modifiers.findFirstToken(TokenTypes.ABSTRACT) == null
572            && modifiers.findFirstToken(TokenTypes.FINAL) == null
573            && modifiers.findFirstToken(TokenTypes.LITERAL_STATIC) == null;
574    }
575
576    /**
577     * Checks whether a method has any of ignored annotations.
578     *
579     * @param methodDef method definition token.
580     * @param annotations a set of ignored annotations.
581     * @return true if a method has any of ignored annotations.
582     */
583    private static boolean hasIgnoredAnnotation(DetailAST methodDef, Set<String> annotations) {
584        final DetailAST modifiers = methodDef.findFirstToken(TokenTypes.MODIFIERS);
585        final Optional<DetailAST> annotation = TokenUtil.findFirstTokenByPredicate(modifiers,
586            currentToken -> {
587                return currentToken.getType() == TokenTypes.ANNOTATION
588                    && annotations.contains(getAnnotationName(currentToken));
589            });
590        return annotation.isPresent();
591    }
592
593    /**
594     * Gets the name of the annotation.
595     *
596     * @param annotation to get name of.
597     * @return the name of the annotation.
598     */
599    private static String getAnnotationName(DetailAST annotation) {
600        final DetailAST dotAst = annotation.findFirstToken(TokenTypes.DOT);
601        final DetailAST parent = Objects.requireNonNullElse(dotAst, annotation);
602        return parent.findFirstToken(TokenTypes.IDENT).getText();
603    }
604
605    /**
606     * Returns CLASS_DEF or ENUM_DEF token which is the nearest to the given ast node.
607     * Searches the tree towards the root until it finds a CLASS_DEF or ENUM_DEF node.
608     *
609     * @param ast the start node for searching.
610     * @return the CLASS_DEF or ENUM_DEF token.
611     */
612    private static DetailAST getNearestClassOrEnumDefinition(DetailAST ast) {
613        DetailAST searchAST = ast;
614        while (searchAST.getType() != TokenTypes.CLASS_DEF
615               && searchAST.getType() != TokenTypes.ENUM_DEF) {
616            searchAST = searchAST.getParent();
617        }
618        return searchAST;
619    }
620
621    /**
622     * Checks if the given class (given CLASS_DEF node) can be subclassed.
623     *
624     * @param classDef class definition token.
625     * @return true if the containing class can be subclassed.
626     */
627    private static boolean canBeSubclassed(DetailAST classDef) {
628        final DetailAST modifiers = classDef.findFirstToken(TokenTypes.MODIFIERS);
629        return classDef.getType() != TokenTypes.ENUM_DEF
630            && modifiers.findFirstToken(TokenTypes.FINAL) == null
631            && hasDefaultOrExplicitNonPrivateCtor(classDef);
632    }
633
634    /**
635     * Checks whether a class has default or explicit non-private constructor.
636     *
637     * @param classDef class ast token.
638     * @return true if a class has default or explicit non-private constructor.
639     */
640    private static boolean hasDefaultOrExplicitNonPrivateCtor(DetailAST classDef) {
641        // check if subclassing is prevented by having only private ctors
642        final DetailAST objBlock = classDef.findFirstToken(TokenTypes.OBJBLOCK);
643
644        boolean hasDefaultConstructor = true;
645        boolean hasExplicitNonPrivateCtor = false;
646
647        DetailAST candidate = objBlock.getFirstChild();
648
649        while (candidate != null) {
650            if (candidate.getType() == TokenTypes.CTOR_DEF) {
651                hasDefaultConstructor = false;
652
653                final DetailAST ctorMods =
654                        candidate.findFirstToken(TokenTypes.MODIFIERS);
655                if (ctorMods.findFirstToken(TokenTypes.LITERAL_PRIVATE) == null) {
656                    hasExplicitNonPrivateCtor = true;
657                    break;
658                }
659            }
660            candidate = candidate.getNextSibling();
661        }
662
663        return hasDefaultConstructor || hasExplicitNonPrivateCtor;
664    }
665
666}