//////////////////////////////////////////////////////////////////////////////// // checkstyle: Checks Java source code for adherence to a set of rules. // Copyright (C) 2001-2017 the original author or authors. // // This library is free software; you can redistribute it and/or // modify it under the terms of the GNU Lesser General Public // License as published by the Free Software Foundation; either // version 2.1 of the License, or (at your option) any later version. // // This library is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU // Lesser General Public License for more details. // // You should have received a copy of the GNU Lesser General Public // License along with this library; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA //////////////////////////////////////////////////////////////////////////////// package com.github.sevntu.checkstyle.checks.coding; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Set; import java.util.regex.Pattern; import com.github.sevntu.checkstyle.Utils; import com.puppycrawl.tools.checkstyle.api.AbstractCheck; import com.puppycrawl.tools.checkstyle.api.DetailAST; import com.puppycrawl.tools.checkstyle.api.FullIdent; import com.puppycrawl.tools.checkstyle.api.TokenTypes; import com.puppycrawl.tools.checkstyle.utils.TokenUtils; /** * <p> * Disallow some set of modifiers for Java types specified by regexp. * <p> * Field modifiers types according to Java Spec: * (https://docs.oracle.com/javase/specs/jls/se7/html/jls-8.html#jls-8.3.1) * <ul> * <li><b>Annotation</b>: using the 'forbiddenClassesRegexpAnnotation' option. * <li><b>final</b>: using the 'forbiddenClassesRegexpFinal' option. * <li><b>static</b>: using the 'forbiddenClassesRegexpStatic'option. * <li><b>transient</b>: using the 'forbiddenClassesRegexpTransient' option. * <li><b>volatile</b>: using the 'forbiddenClassesRegexpVolatile' option. * <li><b>private</b>: using the 'forbiddenClassesRegexpPrivate' option. * <li><b>package-private</b>: using the 'forbiddenClassesRegexpPackagePrivate' option. * <li><b>protected</b>: using the 'forbiddenClassesRegexpProtected' option. * <li><b>public</b>: using the 'forbiddenClassesRegexpPublic' option. * </ul> * <p> * <b>Example 1:</b> Forbid use of 'static' modifiers for 'ULCComponents' * (http://ulc.canoo.com/ulccommunity/Contributions/Extensions/GoodPractices.html) * * <p> * Never keep instances of ULC classes in static variables (ULCIcons neither!). They cannot be * shared between different sessions. * <p> * So we can disallow "static" modifier for all ULC* components by setting up an * "forbiddenClassesRegexpStatic" option to "ULC.+" regexp String. * * <p> * <b>Configuration:</b> * <pre> * <module name="TreeWalker"> * <module name="AvoidModifiersForTypesCheck"> * <property name="forbiddenClassesRegexpStatic" value="ULC.+"/> * </module> * </module> * </pre> * * <p> * <b>Example 2:</b> Forbid using annotation for fields: (e.g. <code>@Autowired</code> ). This * can be done by setting up the "forbiddenClassesRegexpAnnotation" option to "Person" regexp * String. * * <p> * <b>Configuration:</b> * <pre> * <module name="TreeWalker"> * <module name="AvoidModifiersForTypesCheck"> * <property name="forbiddenClassesRegexpAnnotation" value="Person"/> * </module> * </module> * </pre> * * <pre> * public class Customer { * * @Autowired * private Person person; // Violation * * private int type; // OK * * private String action; // OK * * } * </pre> * <p> * <b>Example 3:</b> Forbid returning Logger out of the class, since it is a very bad practice as it * produce logs that are hard to investigate as logging class does not contains that code and search * should be done in other classes or in hierarchy (if filed is public or accessible by other * protected or package). * <p> * This check can be activated by setting up the "forbiddenClassesRegexpPublic", * "forbiddenClassesRegexpPackagePrivate" and "forbiddenClassesRegexpProtected" options to "Logger" * regexp String. * * <p> * <b>Configuration:</b> * <pre> * <module name="TreeWalker"> * <module name="AvoidModifiersForTypesCheck"> * <property name="forbiddenClassesRegexpProtected" value="Logger"/> * <property name="forbiddenClassesRegexpPublic" value="Logger"/> * <property name="forbiddenClassesRegexpPackagePrivate" value="Logger"/> * <module> * </module> * </pre> * * <pre> * public class Check { * * private Logger log1 = Logger.getLogger(getClass().getName()); // OK * * protected Logger log2 = Logger.getLogger(getClass().getName()); // Violation * * public Logger log3 = Logger.getLogger(getClass().getName()); // Violation * * Logger log4 = Logger.getLogger(getClass().getName()); // Violation * * } * </pre> * <p> * @author <a href="mailto:Daniil.Yaroslavtsev@gmail.com"> Daniil Yaroslavtsev</a> * @author <a href="mailto:yasser.aziza@gmail.com">Yasser Aziza</a> */ public class AvoidModifiersForTypesCheck extends AbstractCheck { /** * The key is pointing to the message text String in * "messages.properties file". */ public static final String MSG_KEY = "avoid.modifiers.for.types"; /** * Pattern object is used to store the regexp for the names of classes, that * could not have 'annotation' modifier. */ private Pattern forbiddenClassesRegexpAnnotation = Pattern.compile(""); /** * Pattern object is used to store the regexp for the names of classes, that * could not have 'final' modifier. */ private Pattern forbiddenClassesRegexpFinal = Pattern.compile(""); /** * Pattern object is used to store the regexp for the names of classes, that * could not have 'static' modifier. */ private Pattern forbiddenClassesRegexpStatic = Pattern.compile("ULC.+"); /** * Pattern object is used to store the regexp for the names of classes, that * could not have 'transient' modifier. */ private Pattern forbiddenClassesRegexpTransient = Pattern.compile(""); /** * Pattern object is used to store the regexp for the names of classes, that * could not have 'volatile' modifier. */ private Pattern forbiddenClassesRegexpVolatile = Pattern.compile(""); /** * Pattern object is used to store the regexp for the names of classes, that * could not have 'private' modifier. */ private Pattern forbiddenClassesRegexpPrivate = Pattern.compile(""); /** * Pattern object is used to store the regexp for the names of classes, that * could not have no modifier 'package-private'. */ private Pattern forbiddenClassesRegexpPackagePrivate = Pattern.compile(""); /** * Pattern object is used to store the regexp for the names of classes, that * could not have 'protected' modifier. */ private Pattern forbiddenClassesRegexpProtected = Pattern.compile(""); /** * Pattern object is used to store the regexp for the names of classes, that * could not have 'public' modifier. */ private Pattern forbiddenClassesRegexpPublic = Pattern.compile(""); /** * Sets the regexp for the names of classes, that could not have 'annotation' * modifier. * @param forbiddenClassesRegexpAnnotation * String contains the regex to set for the names of classes, that * could not have 'annotation' modifier. */ public void setForbiddenClassesRegexpAnnotation(String forbiddenClassesRegexpAnnotation) { final String regexp; if (forbiddenClassesRegexpAnnotation == null) { regexp = ""; } else { regexp = forbiddenClassesRegexpAnnotation; } this.forbiddenClassesRegexpAnnotation = Pattern.compile(regexp); } /** * Sets the regexp for the names of classes, that could not have 'final' * modifier. * @param forbiddenClassesRegexpFinal * String contains the regex to set for the names of classes, that * could not have 'final' modifier. */ public void setForbiddenClassesRegexpFinal(String forbiddenClassesRegexpFinal) { final String regexp; if (forbiddenClassesRegexpFinal == null) { regexp = ""; } else { regexp = forbiddenClassesRegexpFinal; } this.forbiddenClassesRegexpFinal = Pattern.compile(regexp); } /** * Sets the regexp for the names of classes, that could not have 'static' * modifier. * @param forbiddenClassesRegexpStatic * String contains the regex to set for the names of classes, that * could not have 'static' modifier. */ public void setForbiddenClassesRegexpStatic(String forbiddenClassesRegexpStatic) { final String regexp; if (forbiddenClassesRegexpStatic == null) { regexp = ""; } else { regexp = forbiddenClassesRegexpStatic; } this.forbiddenClassesRegexpStatic = Pattern.compile(regexp); } /** * Sets the regexp for the names of classes, that could not have 'transient' * modifier. * @param forbiddenClassesRegexpTransient * String contains the regex to set for the names of classes, that * could not have 'transient' modifier. */ public void setForbiddenClassesRegexpTransient(String forbiddenClassesRegexpTransient) { final String regexp; if (forbiddenClassesRegexpTransient == null) { regexp = ""; } else { regexp = forbiddenClassesRegexpTransient; } this.forbiddenClassesRegexpTransient = Pattern.compile(regexp); } /** * Sets the regexp for the names of classes, that could not have 'volatile' * modifier. * @param forbiddenClassesRegexpVolatile * String contains the regex to set for the names of classes, that * could not have 'volatile' modifier. */ public void setForbiddenClassesRegexpVolatile(String forbiddenClassesRegexpVolatile) { final String regexp; if (forbiddenClassesRegexpVolatile == null) { regexp = ""; } else { regexp = forbiddenClassesRegexpVolatile; } this.forbiddenClassesRegexpVolatile = Pattern.compile(regexp); } /** * Sets the regexp for the names of classes, that could not have 'private' * modifier. * @param forbiddenClassesRegexpPrivate * String contains the regex to set for the names of classes, that * could not have 'private' modifier. */ public void setForbiddenClassesRegexpPrivate(String forbiddenClassesRegexpPrivate) { final String regexp; if (forbiddenClassesRegexpPrivate == null) { regexp = ""; } else { regexp = forbiddenClassesRegexpPrivate; } this.forbiddenClassesRegexpPrivate = Pattern.compile(regexp); } /** * Sets the regexp for the names of classes, that could not have no modifier * ('package-private'). * @param forbiddenClassesRegexpPackagePrivate * String contains the regex to set for the names of classes, that * could not have no modifier ('package-private'). */ public void setForbiddenClassesRegexpPackagePrivate( String forbiddenClassesRegexpPackagePrivate) { final String regexp; if (forbiddenClassesRegexpPackagePrivate == null) { regexp = ""; } else { regexp = forbiddenClassesRegexpPackagePrivate; } this.forbiddenClassesRegexpPackagePrivate = Pattern.compile(regexp); } /** * Sets the regexp for the names of classes, that could not have 'protected' * modifier. * @param forbiddenClassesRegexpProtected * String contains the regex to set for the names of classes, that * could not have 'protected' modifier. */ public void setForbiddenClassesRegexpProtected(String forbiddenClassesRegexpProtected) { final String regexp; if (forbiddenClassesRegexpProtected == null) { regexp = ""; } else { regexp = forbiddenClassesRegexpProtected; } this.forbiddenClassesRegexpProtected = Pattern.compile(regexp); } /** * Sets the regexp for the names of classes, that could not have 'public' * modifier. * @param forbiddenClassesRegexpPublic * String contains the regex to set for the names of classes, that * could not have 'public' modifier. */ public void setForbiddenClassesRegexpPublic(String forbiddenClassesRegexpPublic) { final String regexp; if (forbiddenClassesRegexpPublic == null) { regexp = ""; } else { regexp = forbiddenClassesRegexpPublic; } this.forbiddenClassesRegexpPublic = Pattern.compile(regexp); } @Override public int[] getDefaultTokens() { return new int[] {TokenTypes.VARIABLE_DEF }; } @Override public void visitToken(DetailAST ast) { final String classNameAndPath = getClassNameAndPath(ast); if (classNameAndPath != null) { final String className = getClassName(classNameAndPath); final Set<Integer> modifiersSet = getModifiers(ast); if (ast.getParent().getType() == TokenTypes.OBJBLOCK && !modifiersSet.contains(TokenTypes.LITERAL_PUBLIC) && !modifiersSet.contains(TokenTypes.LITERAL_PROTECTED) && !modifiersSet.contains(TokenTypes.LITERAL_PRIVATE) && forbiddenClassesRegexpPackagePrivate.matcher(className).matches()) { log(ast, MSG_KEY, className, "package-private"); } for (int modifierType : modifiersSet) { if (match(modifierType, className)) { String tokenName = TokenUtils.getTokenName(modifierType); // Remove literal prefix and switch to lower case for better readability tokenName = tokenName.toLowerCase().replaceAll("literal_", ""); log(ast, MSG_KEY, className, tokenName); } } } } /** * Checks whether a specific Java modifier is used in a given class with * the specified regular expression. * @param modifierType the modifier type * @param className the class name * @return either <code>true</code> if the regexp match the className, * else <code>false</code> */ private boolean match(int modifierType, String className) { final Pattern pattern = mapToRegExp(modifierType); return pattern.matcher(className).matches(); } /** * Maps the modifierType to a regular expression. * @param modifierType the modifier type * @return the Pattern object storing the regexp for the names of classes, * that must not have the modifierType. */ private Pattern mapToRegExp(int modifierType) { Pattern result = null; switch (modifierType) { case TokenTypes.ANNOTATION: result = forbiddenClassesRegexpAnnotation; break; case TokenTypes.FINAL: result = forbiddenClassesRegexpFinal; break; case TokenTypes.LITERAL_STATIC: result = forbiddenClassesRegexpStatic; break; case TokenTypes.LITERAL_TRANSIENT: result = forbiddenClassesRegexpTransient; break; case TokenTypes.LITERAL_VOLATILE: result = forbiddenClassesRegexpVolatile; break; case TokenTypes.LITERAL_PRIVATE: result = forbiddenClassesRegexpPrivate; break; case TokenTypes.LITERAL_PROTECTED: result = forbiddenClassesRegexpProtected; break; case TokenTypes.LITERAL_PUBLIC: result = forbiddenClassesRegexpPublic; break; default: Utils.reportInvalidToken(modifierType); break; } return result; } /** * Gets the full className of the defined variable. * @param variableDefNode * A DetailAST node is related to variable definition (VARIABLE_DEF * node type). * @return String contains the className of the defined variable or null if * the current processed object is an array of primitive types */ private static String getClassNameAndPath(DetailAST variableDefNode) { String result = null; final DetailAST type = variableDefNode.findFirstToken(TokenTypes.TYPE); final DetailAST textWithoutDots = type.findFirstToken(TokenTypes.IDENT); if (textWithoutDots == null) { // if there are TokenTypes.DOT nodes in subTree. final DetailAST parentDotAST = type.findFirstToken(TokenTypes.DOT); if (parentDotAST != null) { final FullIdent dottedPathIdent = FullIdent .createFullIdentBelow(parentDotAST); final DetailAST nameAST = parentDotAST.getLastChild(); result = dottedPathIdent.getText() + "." + nameAST.getText(); } } // if subtree doesn`t contain dots. else { result = textWithoutDots.getText(); } return result; } /** * Gets the class name from full (dotted) classPath. * @param classNameAndPath * - the full (dotted) classPath. Must not be null. * @return the name of the class is specified by the current full name&path. * Guaranteed to not be null if aClassNameAndPath is not null. */ private static String getClassName(final String classNameAndPath) { return classNameAndPath.replaceAll(".+\\.", ""); } /** * Gets the modifiers of the defined variable (annotation, public, private, final, static, * transient or volatile). * @param variableDefAst * A DeatilAST node is related to the variable definition * (VARIABLE_DEF type) * @return List of token types is related to the given variable modifiers. */ private static Set<Integer> getModifiers(DetailAST variableDefAst) { final Set<Integer> modifiersSet = new HashSet<>(); final DetailAST modifiersAST = variableDefAst .findFirstToken(TokenTypes.MODIFIERS); for (DetailAST modifier : getChildren(modifiersAST)) { modifiersSet.add(modifier.getType()); } return modifiersSet; } /** * Gets all the children which are one level below on the current DetailAST * parent node. * @param node * The parent node. * @return The list of children one level below on the current parent node. */ private static List<DetailAST> getChildren(final DetailAST node) { final List<DetailAST> result = new LinkedList<>(); DetailAST curNode = node.getFirstChild(); while (curNode != null) { result.add(curNode); curNode = curNode.getNextSibling(); } return result; } }