blob: 5322dcb5c503083a927bfbc4d3c9366743383b76 [file] [log] [blame]
/*
* Copyright 2003-2013 Dave Griffith, Bas Leijdekkers
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.siyeh.ig.jdk;
import com.intellij.codeInspection.ProblemDescriptor;
import com.intellij.codeInspection.ProblemsHolder;
import com.intellij.lang.java.JavaLanguage;
import com.intellij.openapi.project.Project;
import com.intellij.psi.*;
import com.intellij.psi.search.LocalSearchScope;
import com.intellij.psi.search.searches.ReferencesSearch;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.psi.util.TypeConversionUtil;
import com.intellij.util.Query;
import com.siyeh.InspectionGadgetsBundle;
import com.siyeh.ig.BaseInspection;
import com.siyeh.ig.BaseInspectionVisitor;
import com.siyeh.ig.InspectionGadgetsFix;
import com.siyeh.ig.PsiReplacementUtil;
import com.siyeh.ig.psiutils.ExpectedTypeUtils;
import com.siyeh.ig.psiutils.MethodCallUtils;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
public class AutoUnboxingInspection extends BaseInspection {
/**
* @noinspection StaticCollection
*/
@NonNls static final Map<String, String> s_unboxingMethods = new HashMap<String, String>(8);
static {
s_unboxingMethods.put("byte", "byteValue");
s_unboxingMethods.put("short", "shortValue");
s_unboxingMethods.put("int", "intValue");
s_unboxingMethods.put("long", "longValue");
s_unboxingMethods.put("float", "floatValue");
s_unboxingMethods.put("double", "doubleValue");
s_unboxingMethods.put("boolean", "booleanValue");
s_unboxingMethods.put("char", "charValue");
}
@Override
@NotNull
public String getDisplayName() {
return InspectionGadgetsBundle.message("auto.unboxing.display.name");
}
@Override
@NotNull
public String buildErrorString(Object... infos) {
return InspectionGadgetsBundle.message("auto.unboxing.problem.descriptor");
}
@Override
@Nullable
public InspectionGadgetsFix buildFix(Object... infos) {
if (!isFixApplicable((PsiExpression)infos[0])) {
return null;
}
return new AutoUnboxingFix();
}
private static boolean isFixApplicable(PsiExpression location) {
// conservative check to see if the result value of the postfix
// expression is used later in the same expression statement.
// Applying the quick fix in such a case would break the code
// because the explicit unboxing code would split the expression in
// multiple statements.
final PsiElement parent = location.getParent();
if (!(parent instanceof PsiPostfixExpression)) {
return true;
}
final PsiReferenceExpression reference;
if (location instanceof PsiReferenceExpression) {
reference = (PsiReferenceExpression)location;
}
else if (location instanceof PsiArrayAccessExpression) {
final PsiArrayAccessExpression arrayAccessExpression = (PsiArrayAccessExpression)location;
final PsiExpression expression = arrayAccessExpression.getArrayExpression();
if (!(expression instanceof PsiReferenceExpression)) {
return true;
}
reference = (PsiReferenceExpression)expression;
}
else {
return true;
}
final PsiElement element = reference.resolve();
if (element == null) {
return true;
}
final PsiStatement statement = PsiTreeUtil.getParentOfType(parent, PsiStatement.class);
final LocalSearchScope scope = new LocalSearchScope(statement);
final Query<PsiReference> query = ReferencesSearch.search(element, scope);
final Collection<PsiReference> references = query.findAll();
return references.size() <= 1;
}
private static class AutoUnboxingFix extends InspectionGadgetsFix {
@Override
@NotNull
public String getName() {
return InspectionGadgetsBundle.message("auto.unboxing.make.unboxing.explicit.quickfix");
}
@Override
@NotNull
public String getFamilyName() {
return getName();
}
@Override
public void doFix(Project project, ProblemDescriptor descriptor) {
final PsiExpression expression = (PsiExpression)descriptor.getPsiElement();
final PsiType type = expression.getType();
if (type == null) {
return;
}
final PsiPrimitiveType unboxedType = (PsiPrimitiveType)ExpectedTypeUtils.findExpectedType(expression, false, true);
if (unboxedType == null) {
return;
}
final String newExpressionText = buildNewExpressionText(expression, unboxedType);
final JavaPsiFacade psiFacade = JavaPsiFacade.getInstance(project);
final PsiElementFactory factory = psiFacade.getElementFactory();
final PsiElement parent = expression.getParent();
final String expressionText = expression.getText();
if (parent instanceof PsiTypeCastExpression) {
final PsiTypeCastExpression typeCastExpression = (PsiTypeCastExpression)parent;
PsiReplacementUtil.replaceExpression(typeCastExpression, newExpressionText);
}
else if (parent instanceof PsiPrefixExpression && !unboxedType.equalsToText("boolean")) {
final PsiPrefixExpression prefixExpression = (PsiPrefixExpression)parent;
final IElementType tokenType = prefixExpression.getOperationTokenType();
if (JavaTokenType.PLUSPLUS.equals(tokenType)) {
PsiReplacementUtil.replaceExpression(prefixExpression, expressionText + '=' + newExpressionText + "+1");
}
else if (JavaTokenType.MINUSMINUS.equals(tokenType)) {
PsiReplacementUtil.replaceExpression(prefixExpression, expressionText + '=' + newExpressionText + "-1");
} else {
PsiReplacementUtil.replaceExpression(prefixExpression, prefixExpression.getOperationSign().getText() + newExpressionText);
}
}
else if (parent instanceof PsiPostfixExpression) {
final PsiPostfixExpression postfixExpression = (PsiPostfixExpression)parent;
final IElementType tokenType = postfixExpression.getOperationTokenType();
final PsiElement grandParent = postfixExpression.getParent();
if (grandParent instanceof PsiExpressionStatement) {
if (JavaTokenType.PLUSPLUS.equals(tokenType)) {
PsiReplacementUtil.replaceExpression(postfixExpression, expressionText + '=' + newExpressionText + "+1");
}
else if (JavaTokenType.MINUSMINUS.equals(tokenType)) {
PsiReplacementUtil.replaceExpression(postfixExpression, expressionText + '=' + newExpressionText + "-1");
}
}
else {
final PsiElement element = postfixExpression.replace(postfixExpression.getOperand());
final PsiStatement statement = PsiTreeUtil.getParentOfType(element, PsiStatement.class);
if (statement == null) {
return;
}
final PsiStatement newStatement;
if (JavaTokenType.PLUSPLUS.equals(tokenType)) {
newStatement = factory.createStatementFromText(expressionText + '=' + newExpressionText + "+1;", statement);
}
else {
newStatement = factory.createStatementFromText(expressionText + '=' + newExpressionText + "-1;", statement);
}
final PsiElement greatGrandParent = statement.getParent();
greatGrandParent.addAfter(newStatement, statement);
}
}
else if (parent instanceof PsiAssignmentExpression) {
final PsiAssignmentExpression assignmentExpression = (PsiAssignmentExpression)parent;
final PsiExpression lExpression = assignmentExpression.getLExpression();
if (expression.equals(lExpression)) {
final PsiJavaToken operationSign = assignmentExpression.getOperationSign();
final String operationSignText = operationSign.getText();
final char sign = operationSignText.charAt(0);
final PsiExpression rExpression = assignmentExpression.getRExpression();
if (rExpression == null) {
return;
}
final String text = lExpression.getText() + '=' + newExpressionText + sign + rExpression.getText();
final PsiExpression newExpression = factory.createExpressionFromText(text, assignmentExpression);
assignmentExpression.replace(newExpression);
}
else {
PsiReplacementUtil.replaceExpression(expression, newExpressionText);
}
}
else {
PsiReplacementUtil.replaceExpression(expression, newExpressionText);
}
}
private static String buildNewExpressionText(PsiExpression expression, PsiPrimitiveType unboxedType) {
final String unboxedTypeText = unboxedType.getCanonicalText();
final String expressionText = expression.getText();
final String boxMethodName = s_unboxingMethods.get(unboxedTypeText);
if (expression instanceof PsiTypeCastExpression) {
return '(' + expressionText + ")." + boxMethodName + "()";
}
final String constantText = computeConstantBooleanText(expression);
if (constantText != null) {
return constantText;
}
if (expression instanceof PsiMethodCallExpression) {
final PsiMethodCallExpression methodCallExpression = (PsiMethodCallExpression)expression;
if (isValueOfCall(methodCallExpression)) {
final PsiExpressionList argumentList = methodCallExpression.getArgumentList();
final PsiExpression[] arguments = argumentList.getExpressions();
final PsiExpression argument = arguments[0];
return argument.getText();
}
}
final PsiType type = expression.getType();
if (type != null && type.equalsToText(CommonClassNames.JAVA_LANG_OBJECT)) {
return "((" + unboxedType.getBoxedTypeName() + ')' + expressionText + ")." + boxMethodName + "()";
}
return expressionText + '.' + boxMethodName + "()";
}
private static boolean isValueOfCall(PsiMethodCallExpression methodCallExpression) {
final PsiExpressionList argumentList = methodCallExpression.getArgumentList();
final PsiExpression[] arguments = argumentList.getExpressions();
if (arguments.length != 1) {
return false;
}
final PsiExpression argument = arguments[0];
final PsiType type = argument.getType();
return (MethodCallUtils.isCallToMethod(methodCallExpression, CommonClassNames.JAVA_LANG_INTEGER, null, "valueOf", PsiType.INT) &&
PsiType.INT.equals(type)) ||
(MethodCallUtils.isCallToMethod(methodCallExpression, CommonClassNames.JAVA_LANG_SHORT, null, "valueOf", PsiType.SHORT) &&
PsiType.SHORT.equals(type)) ||
(MethodCallUtils.isCallToMethod(methodCallExpression, CommonClassNames.JAVA_LANG_BYTE, null, "valueOf", PsiType.BYTE) &&
PsiType.BYTE.equals(type)) ||
(MethodCallUtils.isCallToMethod(methodCallExpression, CommonClassNames.JAVA_LANG_LONG, null, "valueOf", PsiType.LONG) &&
PsiType.LONG.equals(type)) ||
(MethodCallUtils.isCallToMethod(methodCallExpression, CommonClassNames.JAVA_LANG_CHARACTER, null, "valueOf", PsiType.CHAR) &&
PsiType.CHAR.equals(type)) ||
(MethodCallUtils.isCallToMethod(methodCallExpression, CommonClassNames.JAVA_LANG_DOUBLE, null, "valueOf", PsiType.DOUBLE) &&
PsiType.DOUBLE.equals(type)) ||
(MethodCallUtils.isCallToMethod(methodCallExpression, CommonClassNames.JAVA_LANG_FLOAT, null, "valueOf", PsiType.FLOAT) &&
PsiType.FLOAT.equals(type));
}
@NonNls
private static String computeConstantBooleanText(PsiExpression expression) {
if (!(expression instanceof PsiReferenceExpression)) {
return null;
}
final PsiReferenceExpression referenceExpression = (PsiReferenceExpression)expression;
final PsiElement target = referenceExpression.resolve();
if (!(target instanceof PsiField)) {
return null;
}
final PsiField field = (PsiField)target;
final PsiClass containingClass = field.getContainingClass();
if (containingClass == null) {
return null;
}
final String qualifiedName = containingClass.getQualifiedName();
if (!CommonClassNames.JAVA_LANG_BOOLEAN.equals(qualifiedName)) {
return null;
}
@NonNls final String name = field.getName();
if ("TRUE".equals(name)) {
return "true";
}
else if ("FALSE".equals(name)) {
return "false";
}
else {
return null;
}
}
}
@Override
public BaseInspectionVisitor buildVisitor() {
return new AutoUnboxingVisitor();
}
@NotNull
@Override
public PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly) {
PsiFile psiFile = holder.getFile();
if (psiFile.getLanguage() != JavaLanguage.INSTANCE || !PsiUtil.isLanguageLevel5OrHigher(psiFile)) {
return new PsiElementVisitor() {
};
}
return super.buildVisitor(holder, isOnTheFly);
}
private static class AutoUnboxingVisitor extends BaseInspectionVisitor {
@Override
public void visitArrayAccessExpression(PsiArrayAccessExpression expression) {
super.visitArrayAccessExpression(expression);
checkExpression(expression);
}
@Override
public void visitConditionalExpression(PsiConditionalExpression expression) {
super.visitConditionalExpression(expression);
checkExpression(expression);
}
@Override
public void visitReferenceExpression(PsiReferenceExpression expression) {
super.visitReferenceExpression(expression);
checkExpression(expression);
}
@Override
public void visitNewExpression(PsiNewExpression expression) {
super.visitNewExpression(expression);
checkExpression(expression);
}
@Override
public void visitMethodCallExpression(PsiMethodCallExpression expression) {
super.visitMethodCallExpression(expression);
checkExpression(expression);
}
@Override
public void visitTypeCastExpression(PsiTypeCastExpression expression) {
super.visitTypeCastExpression(expression);
checkExpression(expression);
}
@Override
public void visitAssignmentExpression(PsiAssignmentExpression expression) {
super.visitAssignmentExpression(expression);
checkExpression(expression);
}
@Override
public void visitParenthesizedExpression(PsiParenthesizedExpression expression) {
super.visitParenthesizedExpression(expression);
checkExpression(expression);
}
private void checkExpression(PsiExpression expression) {
if (expression.getParent() instanceof PsiParenthesizedExpression) {
return;
}
final PsiType expressionType = expression.getType();
if (expressionType == null) {
return;
}
if (expressionType.getArrayDimensions() > 0) {
// a horrible hack to get around what happens when you pass
// an array to a vararg expression
return;
}
if (TypeConversionUtil.isPrimitiveAndNotNull(expressionType)) {
return;
}
if (!TypeConversionUtil.isAssignableFromPrimitiveWrapper(expressionType)) {
return;
}
final PsiType expectedType = ExpectedTypeUtils.findExpectedType(expression, false, true);
if (expectedType == null || !TypeConversionUtil.isPrimitiveAndNotNull(expectedType)) {
return;
}
registerError(expression, expression);
}
}
}