DiamondOperatorForVariableDefinitionCheck.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;
/**
* This Check highlights variable definition statements where <a href=
* "http://docs.oracle.com/javase/7/docs/technotes/guides/language/type-inference-generic-instance-creation.html">
* diamond operator</a> could be used.<br>
* <b>Rationale</b>: using diamond operator (introduced in Java 1.7) leads to shorter code<br>
* and better code readability. It is suggested by Oracle that the diamond primarily using<br>
* for variable declarations.<br><br>
* E.g. of statements:
*
* <p><b>Without diamond operator:</b><br><code>
* Map<String, Map<String, Integer>> someMap =
* new HashMap<String, Map<String, Integer>>();</code><br>
* <b>With diamond operator:</b><br>
* <code>
* Map<String, Map<String, Integer>> someMap = new HashMap<>();
* </code>
* </p>
* @author <a href="mailto:nesterenko-aleksey@list.ru">Aleksey Nesterenko</a>
* @since 1.12.0
*/
public class DiamondOperatorForVariableDefinitionCheck extends AbstractCheck {
/** A key is pointing to the warning message text in "messages.properties" file. */
public static final String MSG_KEY = "diamond.operator.for.variable.definition";
@Override
public int[] getDefaultTokens() {
return new int[] {TokenTypes.VARIABLE_DEF};
}
@Override
public int[] getAcceptableTokens() {
return getDefaultTokens();
}
@Override
public int[] getRequiredTokens() {
return getDefaultTokens();
}
@Override
public void visitToken(DetailAST variableDefNode) {
final DetailAST assignNode = variableDefNode.findFirstToken(TokenTypes.ASSIGN);
if (assignNode != null) {
final DetailAST newNode = assignNode.getFirstChild().getFirstChild();
// we check only creation by NEW
if (newNode.getType() == TokenTypes.LITERAL_NEW) {
final DetailAST variableDefNodeType =
variableDefNode.findFirstToken(TokenTypes.TYPE);
final DetailAST varDefArguments = getFirstTypeArgumentsToken(variableDefNodeType);
// generics has to be on left side
if (varDefArguments != null
&& newNode.getLastChild().getType() != TokenTypes.OBJBLOCK
// arrays can not be generics
&& newNode.findFirstToken(TokenTypes.ARRAY_DECLARATOR) == null) {
final DetailAST typeArgs = getFirstTypeArgumentsToken(newNode);
if (typeArgs != null && isTreeEqual(varDefArguments, typeArgs)) {
log(typeArgs, MSG_KEY);
}
}
}
}
}
/**
* Get first occurrence of TYPE_ARGUMENTS if exists.
* @param rootToken the token to start search from.
* @return TYPE_ARGUMENTS token if found.
*/
private static DetailAST getFirstTypeArgumentsToken(DetailAST rootToken) {
DetailAST resultNode = rootToken.getFirstChild();
if (resultNode != null) {
if (resultNode.getType() == TokenTypes.DOT) {
resultNode = resultNode.getFirstChild().getNextSibling();
}
final DetailAST childNode = getFirstTypeArgumentsToken(resultNode);
if (childNode == null) {
resultNode = resultNode.getNextSibling();
}
}
return resultNode;
}
/**
* Checks if the 2 given trees have the same children, type, and text.
* @param left One of the trees to compare.
* @param right The other tree to compare.
* @return {@code true} if the trees are equal.
*/
private static boolean isTreeEqual(DetailAST left, DetailAST right) {
boolean result;
if (isAstEqual(left, right)) {
result = true;
DetailAST leftChild = left.getFirstChild();
DetailAST rightChild = right.getFirstChild();
while (leftChild != rightChild) {
if (!isTreeEqual(leftChild, rightChild)) {
result = false;
break;
}
leftChild = leftChild.getNextSibling();
rightChild = rightChild.getNextSibling();
}
}
else {
result = false;
}
return result;
}
/**
* Checks if the 2 given ASTs have the same type and text.
* @param left One of the ASTs to compare.
* @param right The other AST to compare.
* @return {@code true} if the ASTs are equal.
*/
private static boolean isAstEqual(DetailAST left, DetailAST right) {
return left.getType() == right.getType() && left.getText().equals(right.getText());
}
}