package com.intellij.refactoring.typeMigration.rules; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.util.Comparing; import com.intellij.psi.*; import com.intellij.psi.tree.IElementType; import com.intellij.psi.util.PsiUtil; import com.intellij.psi.util.TypeConversionUtil; import com.intellij.refactoring.typeMigration.TypeConversionDescriptor; import com.intellij.refactoring.typeMigration.TypeConversionDescriptorBase; import com.intellij.refactoring.typeMigration.TypeMigrationLabeler; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; public class ThreadLocalConversionRule extends TypeConversionRule { private static final Logger LOG = Logger.getInstance(ThreadLocalConversionRule.class); @Override public TypeConversionDescriptorBase findConversion(PsiType from, PsiType to, PsiMember member, PsiExpression context, TypeMigrationLabeler labeler) { if (to instanceof PsiClassType && isThreadLocalTypeMigration(from, (PsiClassType)to, context)) { return findDirectConversion(context, to, from, labeler); } return null; } private static boolean isThreadLocalTypeMigration(PsiType from, PsiClassType to, PsiExpression context) { final PsiClassType.ClassResolveResult resolveResult = PsiUtil.resolveGenericsClassInType(to); final PsiClass threadLocalClass = resolveResult.getElement(); if (threadLocalClass != null) { final String typeQualifiedName = threadLocalClass.getQualifiedName(); if (!Comparing.strEqual(typeQualifiedName, ThreadLocal.class.getName())) { return false; } final PsiTypeParameter[] typeParameters = threadLocalClass.getTypeParameters(); if (typeParameters.length != 1) return !PsiUtil.isLanguageLevel5OrHigher(context); final PsiType toTypeParameterValue = resolveResult.getSubstitutor().substitute(typeParameters[0]); if (toTypeParameterValue != null) { if (from instanceof PsiPrimitiveType) { final PsiPrimitiveType unboxedInitialType = PsiPrimitiveType.getUnboxedType(toTypeParameterValue); if (unboxedInitialType != null) { return TypeConversionUtil.areTypesConvertible(from, unboxedInitialType); } } else { return TypeConversionUtil.isAssignable(from, PsiUtil.captureToplevelWildcards(toTypeParameterValue, context)); } } return !PsiUtil.isLanguageLevel5OrHigher(context); } return false; } @Nullable public static TypeConversionDescriptor findDirectConversion(PsiElement context, PsiType to, PsiType from, TypeMigrationLabeler labeler) { final PsiClass toTypeClass = PsiUtil.resolveClassInType(to); LOG.assertTrue(toTypeClass != null); if (context instanceof PsiArrayAccessExpression) { return new TypeConversionDescriptor("$qualifier$[$val$]", "$qualifier$.get()[$val$]"); } final PsiElement parent = context.getParent(); if (parent instanceof PsiAssignmentExpression) { final IElementType operationSign = ((PsiAssignmentExpression)parent).getOperationTokenType(); if (operationSign == JavaTokenType.EQ) { return new TypeConversionDescriptor("$qualifier$ = $val$", "$qualifier$.set(" + toBoxed("$val$", from, context)+")", (PsiAssignmentExpression)parent); } } if (context instanceof PsiReferenceExpression) { final PsiExpression qualifierExpression = ((PsiReferenceExpression)context).getQualifierExpression(); final PsiExpression expression = context.getParent() instanceof PsiMethodCallExpression && qualifierExpression != null ? qualifierExpression : (PsiExpression)context; return new TypeConversionDescriptor("$qualifier$", toPrimitive("$qualifier$.get()", from, context), expression); } else if (context instanceof PsiBinaryExpression) { final PsiBinaryExpression binaryExpression = (PsiBinaryExpression)context; final String sign = binaryExpression.getOperationSign().getText(); return new TypeConversionDescriptor("$qualifier$" + sign + "$val$", toPrimitive("$qualifier$.get()", from, context) + " " + sign + " $val$"); } if (parent instanceof PsiExpressionStatement) { if (context instanceof PsiPostfixExpression) { final PsiPostfixExpression postfixExpression = (PsiPostfixExpression)context; final String sign = postfixExpression.getOperationSign().getText(); return new TypeConversionDescriptor("$qualifier$" + sign, "$qualifier$.set(" + getBoxedWrapper(from, to, toPrimitive("$qualifier$.get()", from, context) + " " + sign.charAt(0) + " 1", labeler, context, postfixExpression.getOperand().getText() + sign.charAt(0) + " 1") + ")"); } else if (context instanceof PsiPrefixExpression) { final PsiPrefixExpression prefixExpression = (PsiPrefixExpression)context; final PsiJavaToken operationSign = ((PsiPrefixExpression)context).getOperationSign(); if (operationSign.getTokenType() == JavaTokenType.EXCL) { return new TypeConversionDescriptor("!$qualifier$", "!$qualifier$.get()"); } final String sign = operationSign.getText(); final PsiExpression operand = prefixExpression.getOperand(); return new TypeConversionDescriptor(sign + "$qualifier$", "$qualifier$.set(" + getBoxedWrapper(from, to, toPrimitive("$qualifier$.get()", from, context) + " " + sign.charAt(0) + " 1", labeler, context, operand != null ? operand.getText() + sign.charAt(0) + " 1" : null) + ")"); } else if (context instanceof PsiAssignmentExpression) { final PsiAssignmentExpression assignmentExpression = (PsiAssignmentExpression)context; final PsiJavaToken signToken = assignmentExpression.getOperationSign(); final IElementType operationSign = signToken.getTokenType(); final String sign = signToken.getText(); final PsiExpression lExpression = assignmentExpression.getLExpression(); if (operationSign == JavaTokenType.EQ) { if (lExpression instanceof PsiReferenceExpression) { final PsiElement element = ((PsiReferenceExpression)lExpression).resolve(); if (element instanceof PsiVariable && ((PsiVariable)element).hasModifierProperty(PsiModifier.FINAL)) { return wrapWithNewExpression(to, from, ((PsiAssignmentExpression)context).getRExpression()); } } return new TypeConversionDescriptor("$qualifier$ = $val$", "$qualifier$.set(" + toBoxed("$val$", from, context) + ")"); } else { final PsiExpression rExpression = assignmentExpression.getRExpression(); return new TypeConversionDescriptor("$qualifier$" + sign + "$val$", "$qualifier$.set(" + getBoxedWrapper(from, to, toPrimitive("$qualifier$.get()", from, context) + " " + sign.charAt(0) + " $val$", labeler, context, rExpression != null ? lExpression .getText() + sign.charAt(0) + rExpression.getText() : null) + ")"); } } } return null; } public static TypeConversionDescriptor wrapWithNewExpression(PsiType to, PsiType from, PsiExpression initializer) { final String boxedTypeName = from instanceof PsiPrimitiveType ? ((PsiPrimitiveType)from).getBoxedTypeName() : from.getCanonicalText(); return new TypeConversionDescriptor("$qualifier$", "new " + to.getCanonicalText() + "() {\n" + "@Override \n" + "protected " + boxedTypeName + " initialValue() {\n" + " return " + (PsiUtil.isLanguageLevel5OrHigher(initializer) ? initializer.getText() : (from instanceof PsiPrimitiveType ? "new " + ((PsiPrimitiveType)from).getBoxedTypeName() + "(" + initializer.getText() + ")" : initializer.getText())) + ";\n" + "}\n" + "}", initializer); } private static String toPrimitive(String replaceByArg, PsiType from, PsiElement context) { return PsiUtil.isLanguageLevel5OrHigher(context) ? replaceByArg : from instanceof PsiPrimitiveType ? "((" + ((PsiPrimitiveType)from).getBoxedTypeName() + ")" + replaceByArg + ")." + from.getCanonicalText() + "Value()" : "((" + from.getCanonicalText() + ")" + replaceByArg + ")"; } private static String toBoxed(String replaceByArg, PsiType from, PsiElement context) { return PsiUtil.isLanguageLevel5OrHigher(context) ? replaceByArg : from instanceof PsiPrimitiveType ? "new " + ((PsiPrimitiveType)from).getBoxedTypeName() + "(" + replaceByArg + ")" : replaceByArg; } private static String getBoxedWrapper(final PsiType from, final PsiType to, @NotNull String arg, TypeMigrationLabeler labeler, PsiElement context, @Nullable String tryType) { if (from instanceof PsiPrimitiveType) { final PsiClassType.ClassResolveResult resolveResult = PsiUtil.resolveGenericsClassInType(to); final PsiClass threadLocalClass = resolveResult.getElement(); LOG.assertTrue(threadLocalClass != null); final PsiTypeParameter[] typeParameters = threadLocalClass.getTypeParameters(); if (typeParameters.length == 1) { final PsiType initial = resolveResult.getSubstitutor().substitute(typeParameters[0]); final PsiPrimitiveType unboxedInitialType = PsiPrimitiveType.getUnboxedType(initial); if (unboxedInitialType != null) { if (tryType != null) { final PsiType exprType = labeler.getTypeEvaluator().evaluateType( JavaPsiFacade.getElementFactory(threadLocalClass.getProject()).createExpressionFromText(tryType, context)); if (exprType != null && unboxedInitialType.isAssignableFrom(exprType)) { return toBoxed(arg, from, context); } } return "new " + initial.getCanonicalText() + "((" + unboxedInitialType.getCanonicalText() + ")(" + arg + "))"; } } } return toBoxed(arg, from, context); } }