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