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