FinalizeImplementationCheck.java
////////////////////////////////////////////////////////////////////////////////
// checkstyle: Checks Java source code for adherence to a set of rules.
// Copyright (C) 2001-2019 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 com.puppycrawl.tools.checkstyle.api.AbstractCheck;
import com.puppycrawl.tools.checkstyle.api.DetailAST;
import com.puppycrawl.tools.checkstyle.api.TokenTypes;
/**
* Validates finalize method implementation.
* <p>
* This Check detects 3 most common cases of incorrect finalize() method implementation:
* </p>
* <p>
* 1. Negates effect of superclass finalize:
* </p>
* <pre>
* protected void finalize() { }
* protected void finalize() { doSomething(); }
* </pre>
* <p>
* 2. Useless (or worse) finalize:
* </p>
* <pre>
* protected void finalize() { super.finalize(); }
* </pre>
* <p>
* 3. Public finalize:
* </p>
* <pre>
* public void finalize(){
* try {doSomething();}
* finally {super.finalize()}
* }</pre>
*
* @author <a href="mailto:maxvetrenko2241@gmail.com">Max Vetrenko</a>
* @since 1.11.0
*/
public class FinalizeImplementationCheck extends AbstractCheck {
/**
* The key is pointing to the warning message text in "messages.properties"
* file.
*/
public static final String MSG_KEY_MISSED_TRY_FINALLY =
"finalize.implementation.missed.try.finally";
/**
* The key is pointing to the warning message text in "messages.properties"
* file.
*/
public static final String MSG_KEY_PUBLIC_FINALIZE = "finalize.implementation.public";
/**
* The key is pointing to the warning message text in "messages.properties"
* file.
*/
public static final String MSG_KEY_USELESS_FINALIZE = "finalize.implementation.useless";
/**
* The key is pointing to the warning message text in "messages.properties"
* file.
*/
public static final String MSG_KEY_MISSED_SUPER_FINALIZE_CALL =
"finalize.implementation.missed.super.finalize";
/**
* The name of finalize() method.
*/
private static final String FINALIZE_METHOD_NAME = "finalize";
@Override
public int[] getDefaultTokens() {
return new int[] {
TokenTypes.METHOD_DEF,
};
}
@Override
public int[] getAcceptableTokens() {
return getDefaultTokens();
}
@Override
public int[] getRequiredTokens() {
return getDefaultTokens();
}
@Override
public void visitToken(DetailAST methodDefToken) {
if (isFinalizeMethodSignature(methodDefToken)) {
final String warningMessage = validateFinalizeMethod(methodDefToken);
if (warningMessage != null) {
log(methodDefToken, warningMessage);
}
}
}
/**
* Checks, if finalize implementation is correct. If implementation is bad,
* this method will call log() with suitable warning message.
* @param finalizeMethodToken
* current finalize() token
* @return warning message or null, if all is well.
*/
private static String validateFinalizeMethod(DetailAST finalizeMethodToken) {
String warningMessage = null;
if (hasModifier(TokenTypes.LITERAL_PROTECTED, finalizeMethodToken)) {
final DetailAST methodOpeningBrace = finalizeMethodToken.getLastChild();
final DetailAST literalTry = methodOpeningBrace.findFirstToken(TokenTypes.LITERAL_TRY);
if (literalTry == null) {
if (containsSuperFinalizeCall(methodOpeningBrace)) {
warningMessage = MSG_KEY_USELESS_FINALIZE;
}
else {
warningMessage = MSG_KEY_MISSED_TRY_FINALLY;
}
}
else {
final DetailAST literalFinally = literalTry
.findFirstToken(TokenTypes.LITERAL_FINALLY);
if (literalFinally != null
&& !containsSuperFinalizeCall(literalFinally.getLastChild())) {
warningMessage = MSG_KEY_MISSED_SUPER_FINALIZE_CALL;
}
}
}
else {
warningMessage = MSG_KEY_PUBLIC_FINALIZE;
}
return warningMessage;
}
/**
* Checks, if current method is finalize().
* @param methodDefToken
* current method definition.
* @return true, if method is finalize() method.
*/
private static boolean isFinalizeMethodSignature(DetailAST methodDefToken) {
return !hasModifier(TokenTypes.LITERAL_STATIC, methodDefToken)
&& isFinalizeMethodName(methodDefToken) && isVoid(methodDefToken)
&& getParamsCount(methodDefToken) == 0;
}
/**
* Checks, if finalize() has "static" access modifier.
* @param modifierType
* modifier type.
* @param methodToken
* MODIFIERS Token.
* @return true, if finalize() has "protected" access modifier.
*/
private static boolean hasModifier(int modifierType, DetailAST methodToken) {
final DetailAST modifiersToken = methodToken.getFirstChild();
return modifiersToken.findFirstToken(modifierType) != null;
}
/**
* Checks, if current method name is "finalize".
* @param methodDefToken
* method definition Token.
* @return true, if current method name is "finalize".
*/
private static boolean isFinalizeMethodName(DetailAST methodDefToken) {
final DetailAST identToken = methodDefToken.findFirstToken(TokenTypes.IDENT);
return FINALIZE_METHOD_NAME.equals(identToken.getText());
}
/**
* Checks, if method is void.
* @param methodDefToken
* method definition Token.
* @return true, if method is void.
*/
private static boolean isVoid(DetailAST methodDefToken) {
final DetailAST typeToken = methodDefToken.findFirstToken(TokenTypes.TYPE);
return typeToken.findFirstToken(TokenTypes.LITERAL_VOID) != null;
}
/**
* Counts number of parameters.
* @param methodDefToken
* method definition Token.
* @return number of parameters.
*/
private static int getParamsCount(DetailAST methodDefToken) {
return methodDefToken.findFirstToken(TokenTypes.PARAMETERS).getChildCount();
}
/**
* Checks, if current method has super.finalize() call.
* @param openingBrace
* current method definition.
* @return true, if method has super.finalize() call.
*/
private static boolean containsSuperFinalizeCall(DetailAST openingBrace) {
boolean result = false;
final DetailAST methodCallToken = openingBrace.getFirstChild().getFirstChild();
if (methodCallToken != null && methodCallToken.getType() == TokenTypes.METHOD_CALL) {
final DetailAST dotToken = methodCallToken.getFirstChild();
if (dotToken.getType() == TokenTypes.DOT
&& dotToken.findFirstToken(TokenTypes.LITERAL_SUPER) != null) {
result = true;
}
}
return result;
}
}