001//////////////////////////////////////////////////////////////////////////////// 002// checkstyle: Checks Java source code for adherence to a set of rules. 003// Copyright (C) 2001-2018 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.coding; 021 022import java.util.HashSet; 023import java.util.Set; 024 025import com.puppycrawl.tools.checkstyle.FileStatefulCheck; 026import com.puppycrawl.tools.checkstyle.api.AbstractCheck; 027import com.puppycrawl.tools.checkstyle.api.DetailAST; 028import com.puppycrawl.tools.checkstyle.api.FullIdent; 029import com.puppycrawl.tools.checkstyle.api.TokenTypes; 030import com.puppycrawl.tools.checkstyle.utils.CheckUtils; 031 032/** 033 * <p>Checks that if a class defines a covariant method equals, 034 * then it defines method equals(java.lang.Object). 035 * Inspired by findbugs, 036 * http://findbugs.sourceforge.net/bugDescriptions.html#EQ_SELF_NO_OBJECT 037 * </p> 038 * <p> 039 * An example of how to configure the check is: 040 * </p> 041 * <pre> 042 * <module name="CovariantEquals"/> 043 * </pre> 044 * @author Rick Giles 045 */ 046@FileStatefulCheck 047public class CovariantEqualsCheck extends AbstractCheck { 048 049 /** 050 * A key is pointing to the warning message text in "messages.properties" 051 * file. 052 */ 053 public static final String MSG_KEY = "covariant.equals"; 054 055 /** Set of equals method definitions. */ 056 private final Set<DetailAST> equalsMethods = new HashSet<>(); 057 058 @Override 059 public int[] getDefaultTokens() { 060 return getRequiredTokens(); 061 } 062 063 @Override 064 public int[] getRequiredTokens() { 065 return new int[] {TokenTypes.CLASS_DEF, TokenTypes.LITERAL_NEW, TokenTypes.ENUM_DEF, }; 066 } 067 068 @Override 069 public int[] getAcceptableTokens() { 070 return getRequiredTokens(); 071 } 072 073 @Override 074 public void visitToken(DetailAST ast) { 075 equalsMethods.clear(); 076 077 // examine method definitions for equals methods 078 final DetailAST objBlock = ast.findFirstToken(TokenTypes.OBJBLOCK); 079 if (objBlock != null) { 080 DetailAST child = objBlock.getFirstChild(); 081 boolean hasEqualsObject = false; 082 while (child != null) { 083 if (child.getType() == TokenTypes.METHOD_DEF 084 && CheckUtils.isEqualsMethod(child)) { 085 if (isFirstParameterObject(child)) { 086 hasEqualsObject = true; 087 } 088 else { 089 equalsMethods.add(child); 090 } 091 } 092 child = child.getNextSibling(); 093 } 094 095 // report equals method definitions 096 if (!hasEqualsObject) { 097 for (DetailAST equalsAST : equalsMethods) { 098 final DetailAST nameNode = equalsAST 099 .findFirstToken(TokenTypes.IDENT); 100 log(nameNode.getLineNo(), nameNode.getColumnNo(), 101 MSG_KEY); 102 } 103 } 104 } 105 } 106 107 /** 108 * Tests whether a method's first parameter is an Object. 109 * @param methodDefAst the method definition AST to test. 110 * Precondition: ast is a TokenTypes.METHOD_DEF node. 111 * @return true if ast has first parameter of type Object. 112 */ 113 private static boolean isFirstParameterObject(DetailAST methodDefAst) { 114 final DetailAST paramsNode = methodDefAst.findFirstToken(TokenTypes.PARAMETERS); 115 116 // parameter type "Object"? 117 final DetailAST paramNode = 118 paramsNode.findFirstToken(TokenTypes.PARAMETER_DEF); 119 final DetailAST typeNode = paramNode.findFirstToken(TokenTypes.TYPE); 120 final FullIdent fullIdent = FullIdent.createFullIdentBelow(typeNode); 121 final String name = fullIdent.getText(); 122 return "Object".equals(name) || "java.lang.Object".equals(name); 123 } 124 125}