blob: 5f9f588198f54caf32724529afbaccfd9d90ec15 [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.ui.SingleCheckboxOptionsPanel;
import com.intellij.lang.java.JavaLanguage;
import com.intellij.openapi.project.Project;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiUtil;
import com.intellij.psi.util.TypeConversionUtil;
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.*;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
public class AutoBoxingInspection extends BaseInspection {
@SuppressWarnings({"PublicField"})
public boolean ignoreAddedToCollection = false;
/**
* @noinspection StaticCollection
*/
@NonNls static final Map<String, String> s_boxingClasses = new HashMap<String, String>(8);
static {
s_boxingClasses.put("byte", CommonClassNames.JAVA_LANG_BYTE);
s_boxingClasses.put("short", CommonClassNames.JAVA_LANG_SHORT);
s_boxingClasses.put("int", CommonClassNames.JAVA_LANG_INTEGER);
s_boxingClasses.put("long", CommonClassNames.JAVA_LANG_LONG);
s_boxingClasses.put("float", CommonClassNames.JAVA_LANG_FLOAT);
s_boxingClasses.put("double", CommonClassNames.JAVA_LANG_DOUBLE);
s_boxingClasses.put("boolean", CommonClassNames.JAVA_LANG_BOOLEAN);
s_boxingClasses.put("char", CommonClassNames.JAVA_LANG_CHARACTER);
}
@NonNls static final Set<String> convertableBoxedClassNames = new HashSet<String>();
static {
convertableBoxedClassNames.add(CommonClassNames.JAVA_LANG_BYTE);
convertableBoxedClassNames.add(CommonClassNames.JAVA_LANG_CHARACTER);
convertableBoxedClassNames.add(CommonClassNames.JAVA_LANG_SHORT);
}
@Override
@NotNull
public String getDisplayName() {
return InspectionGadgetsBundle.message("auto.boxing.display.name");
}
@Override
public String getAlternativeID() {
return "boxing";
}
@Override
@NotNull
public String buildErrorString(Object... infos) {
return InspectionGadgetsBundle.message("auto.boxing.problem.descriptor");
}
@Override
@Nullable
public JComponent createOptionsPanel() {
return new SingleCheckboxOptionsPanel(InspectionGadgetsBundle.message("auto.boxing.ignore.added.to.collection.option"), this,
"ignoreAddedToCollection");
}
@Override
public BaseInspectionVisitor buildVisitor() {
return new AutoBoxingVisitor();
}
@Override
public InspectionGadgetsFix buildFix(Object... infos) {
return new AutoBoxingFix();
}
private static class AutoBoxingFix extends InspectionGadgetsFix {
@Override
@NotNull
public String getName() {
return InspectionGadgetsBundle.message("auto.boxing.make.boxing.explicit.quickfix");
}
@NotNull
@Override
public String getFamilyName() {
return getName();
}
@Override
public void doFix(Project project, ProblemDescriptor descriptor) {
final PsiExpression expression = (PsiExpression)descriptor.getPsiElement();
final PsiType expectedType = ExpectedTypeUtils.findExpectedType(expression, false, true);
if (expectedType == null) {
return;
}
final String expectedTypeText = expectedType.getCanonicalText();
final String classToConstruct;
if (s_boxingClasses.containsValue(expectedTypeText)) {
classToConstruct = expectedTypeText;
}
else {
final PsiType type = expression.getType();
if (type == null) {
return;
}
final String expressionTypeText = type.getCanonicalText();
classToConstruct = s_boxingClasses.get(expressionTypeText);
}
if (shortcutReplace(expression, classToConstruct)) {
return;
}
final PsiExpression strippedExpression = ParenthesesUtils.stripParentheses(expression);
if (strippedExpression == null) {
return;
}
@NonNls final String expressionText = strippedExpression.getText();
@NonNls final String newExpression;
if ("true".equals(expressionText)) {
newExpression = "java.lang.Boolean.TRUE";
}
else if ("false".equals(expressionText)) {
newExpression = "java.lang.Boolean.FALSE";
}
else {
newExpression = classToConstruct + ".valueOf(" + expressionText + ')';
}
final PsiElement parent = expression.getParent();
if (parent instanceof PsiTypeCastExpression) {
final PsiTypeCastExpression typeCastExpression = (PsiTypeCastExpression)parent;
PsiReplacementUtil.replaceExpression(typeCastExpression, newExpression);
} else {
PsiReplacementUtil.replaceExpression(expression, newExpression);
}
}
private static boolean shortcutReplace(PsiExpression expression, String classToConstruct) {
if (!(expression instanceof PsiMethodCallExpression)) {
return false;
}
final PsiMethodCallExpression methodCallExpression = (PsiMethodCallExpression)expression;
final PsiReferenceExpression methodExpression = methodCallExpression.getMethodExpression();
final PsiExpression qualifierExpression = methodExpression.getQualifierExpression();
if (qualifierExpression == null) {
return false;
}
if (classToConstruct.equals(CommonClassNames.JAVA_LANG_INTEGER)) {
if (MethodCallUtils.isCallToMethod(methodCallExpression, CommonClassNames.JAVA_LANG_INTEGER, PsiType.INT, "intValue")) {
expression.replace(qualifierExpression);
return true;
}
}
else if (classToConstruct.equals(CommonClassNames.JAVA_LANG_SHORT)) {
if (MethodCallUtils.isCallToMethod(methodCallExpression, CommonClassNames.JAVA_LANG_SHORT, PsiType.SHORT, "shortValue")) {
expression.replace(qualifierExpression);
return true;
}
}
else if (classToConstruct.equals(CommonClassNames.JAVA_LANG_BYTE)) {
if (MethodCallUtils.isCallToMethod(methodCallExpression, CommonClassNames.JAVA_LANG_BYTE, PsiType.BYTE, "byteValue")) {
expression.replace(qualifierExpression);
return true;
}
}
else if (classToConstruct.equals(CommonClassNames.JAVA_LANG_CHARACTER)) {
if (MethodCallUtils.isCallToMethod(methodCallExpression, CommonClassNames.JAVA_LANG_CHARACTER, PsiType.CHAR, "charValue")) {
expression.replace(qualifierExpression);
return true;
}
}
else if (classToConstruct.equals(CommonClassNames.JAVA_LANG_LONG)) {
if (MethodCallUtils.isCallToMethod(methodCallExpression, CommonClassNames.JAVA_LANG_LONG, PsiType.LONG, "longValue")) {
expression.replace(qualifierExpression);
return true;
}
}
else if (classToConstruct.equals(CommonClassNames.JAVA_LANG_FLOAT)) {
if (MethodCallUtils.isCallToMethod(methodCallExpression, CommonClassNames.JAVA_LANG_FLOAT, PsiType.FLOAT, "floatValue")) {
expression.replace(qualifierExpression);
return true;
}
}
else if (classToConstruct.equals(CommonClassNames.JAVA_LANG_DOUBLE)) {
if (MethodCallUtils.isCallToMethod(methodCallExpression, CommonClassNames.JAVA_LANG_DOUBLE, PsiType.DOUBLE, "doubleValue")) {
expression.replace(qualifierExpression);
return true;
}
}
return false;
}
}
private class AutoBoxingVisitor extends BaseInspectionVisitor {
@Override
public void visitElement(PsiElement element) {
if (element.getLanguage() != JavaLanguage.INSTANCE) {
return;
}
if (!PsiUtil.isLanguageLevel5OrHigher(element)) {
return;
}
super.visitElement(element);
}
@Override
public void visitArrayAccessExpression(PsiArrayAccessExpression expression) {
super.visitArrayAccessExpression(expression);
checkExpression(expression);
}
@Override
public void visitAssignmentExpression(PsiAssignmentExpression expression) {
super.visitAssignmentExpression(expression);
checkExpression(expression);
}
@Override
public void visitConditionalExpression(PsiConditionalExpression expression) {
super.visitConditionalExpression(expression);
checkExpression(expression);
}
@Override
public void visitInstanceOfExpression(PsiInstanceOfExpression expression) {
super.visitInstanceOfExpression(expression);
checkExpression(expression);
}
@Override
public void visitLiteralExpression(PsiLiteralExpression expression) {
super.visitLiteralExpression(expression);
checkExpression(expression);
}
@Override
public void visitMethodCallExpression(PsiMethodCallExpression expression) {
super.visitMethodCallExpression(expression);
checkExpression(expression);
}
@Override
public void visitParenthesizedExpression(PsiParenthesizedExpression expression) {
super.visitParenthesizedExpression(expression);
checkExpression(expression);
}
@Override
public void visitPolyadicExpression(PsiPolyadicExpression expression) {
super.visitPolyadicExpression(expression);
checkExpression(expression);
}
@Override
public void visitPostfixExpression(PsiPostfixExpression expression) {
super.visitPostfixExpression(expression);
checkExpression(expression);
}
@Override
public void visitPrefixExpression(PsiPrefixExpression expression) {
super.visitPrefixExpression(expression);
checkExpression(expression);
}
@Override
public void visitReferenceExpression(PsiReferenceExpression expression) {
super.visitReferenceExpression(expression);
checkExpression(expression);
}
@Override
public void visitTypeCastExpression(PsiTypeCastExpression expression) {
super.visitTypeCastExpression(expression);
checkExpression(expression);
}
private void checkExpression(@NotNull PsiExpression expression) {
if (expression.getParent() instanceof PsiParenthesizedExpression) {
return;
}
final PsiType expressionType = expression.getType();
if (expressionType == null || expressionType.equals(PsiType.VOID) || !TypeConversionUtil.isPrimitiveAndNotNull(expressionType)) {
return;
}
final PsiPrimitiveType primitiveType = (PsiPrimitiveType)expressionType;
final PsiClassType boxedType = primitiveType.getBoxedType(expression);
if (boxedType == null) {
return;
}
final PsiType expectedType = ExpectedTypeUtils.findExpectedType(expression, false, true);
if (expectedType == null || ClassUtils.isPrimitive(expectedType)) {
return;
}
if (!expectedType.isAssignableFrom(boxedType)) {
// JLS 5.2 Assignment Conversion
// check if a narrowing primitive conversion is applicable
if (!(expectedType instanceof PsiClassType) || !PsiUtil.isConstantExpression(expression)) {
return;
}
final PsiClassType classType = (PsiClassType)expectedType;
final String className = classType.getCanonicalText();
if (!convertableBoxedClassNames.contains(className)) {
return;
}
if (!PsiType.BYTE.equals(expressionType) && !PsiType.CHAR.equals(expressionType) &&
!PsiType.SHORT.equals(expressionType) && !PsiType.INT.equals(expressionType)) {
return;
}
}
if (ignoreAddedToCollection && isAddedToCollection(expression)) {
return;
}
registerError(expression);
}
private boolean isAddedToCollection(PsiExpression expression) {
final PsiElement parent = expression.getParent();
if (!(parent instanceof PsiExpressionList)) {
return false;
}
final PsiExpressionList expressionList = (PsiExpressionList)parent;
final PsiElement grandParent = expressionList.getParent();
if (!(grandParent instanceof PsiMethodCallExpression)) {
return false;
}
final PsiMethodCallExpression methodCallExpression = (PsiMethodCallExpression)grandParent;
final PsiReferenceExpression methodExpression = methodCallExpression.getMethodExpression();
@NonNls final String methodName = methodExpression.getReferenceName();
if (!"put".equals(methodName) && !"set".equals(methodName) && !"add".equals(methodName)) {
return false;
}
final PsiExpression qualifier = methodExpression.getQualifierExpression();
return TypeUtils.expressionHasTypeOrSubtype(qualifier, CommonClassNames.JAVA_UTIL_COLLECTION, CommonClassNames.JAVA_UTIL_MAP) != null;
}
}
}