type migration: convert to atomic / convert to thread local should use refactoring instead of manual conversion

This commit is contained in:
Dmitry Batkovich
2017-06-08 17:03:20 +03:00
parent 3865e588a8
commit 2066338d72
42 changed files with 293 additions and 308 deletions
@@ -120,7 +120,7 @@ public class RawTypeCanBeGenericInspection extends BaseJavaBatchLocalInspectionT
if (type != null) {
final TypeMigrationRules rules = new TypeMigrationRules();
rules.setBoundScope(PsiSearchHelper.SERVICE.getInstance(project).getUseScope(variable));
TypeMigrationProcessor.runHighlightingTypeMigration(project, null, rules, variable, type, false);
TypeMigrationProcessor.runHighlightingTypeMigration(project, null, rules, variable, type, false, true);
}
}
}
@@ -147,7 +147,8 @@ public class ExtractEnumProcessor {
myTypeMigrationProcessor = new TypeMigrationProcessor(myProject,
PsiUtilCore.toPsiElementArray(myEnumConstants),
Functions.constant(JavaPsiFacade.getElementFactory(myProject).createType(myClass)),
rules);
rules,
true);
for (UsageInfo usageInfo : myTypeMigrationProcessor.findUsages()) {
final PsiElement migrateElement = usageInfo.getElement();
if (migrateElement instanceof PsiField) {
@@ -74,6 +74,7 @@ public class TypeMigrationLabeler {
private final TypeMigrationRules myRules;
private final Function<PsiElement, PsiType> myMigrationRootTypeFunction;
@Nullable private final Set<PsiElement> myAllowedRoots;
private TypeEvaluator myTypeEvaluator;
private final LinkedHashMap<PsiElement, Object> myConversions;
private final Map<Pair<SmartPsiElementPointer<PsiExpression>, PsiType>, TypeMigrationUsageInfo> myFailedConversions;
@@ -89,13 +90,16 @@ public class TypeMigrationLabeler {
private final Map<Pair<TypeMigrationUsageInfo, TypeMigrationUsageInfo>, Set<PsiElement>> myRootUsagesTree = new HashMap<>();
private final Set<TypeMigrationUsageInfo> myProcessedRoots = new HashSet<>();
public TypeMigrationLabeler(final TypeMigrationRules rules, PsiType rootType) {
this(rules, Functions.constant(rootType));
public TypeMigrationLabeler(TypeMigrationRules rules, PsiType rootType) {
this(rules, Functions.constant(rootType), null);
}
public TypeMigrationLabeler(final TypeMigrationRules rules, Function<PsiElement, PsiType> migrationRootTypeFunction) {
public TypeMigrationLabeler(TypeMigrationRules rules,
Function<PsiElement, PsiType> migrationRootTypeFunction,
@Nullable("any root accepted if null") PsiElement[] allowedRoots) {
myRules = rules;
myMigrationRootTypeFunction = migrationRootTypeFunction;
myAllowedRoots = allowedRoots == null ? null : ContainerUtil.set(allowedRoots);
myConversions = new LinkedHashMap<>();
myFailedConversions = new LinkedHashMap<>();
@@ -574,6 +578,9 @@ public class TypeMigrationLabeler {
boolean alreadyProcessed,
final boolean isContraVariantPosition,
final boolean userDefinedType) {
if (myAllowedRoots != null && !myAllowedRoots.contains(element)) {
return false;
}
if (type.equals(PsiType.NULL)) {
return false;
}
@@ -50,18 +50,25 @@ import java.util.Set;
import static com.intellij.util.ObjectUtils.assertNotNull;
public class TypeMigrationProcessor extends BaseRefactoringProcessor {
public volatile static boolean ourSkipFailedConversionInTestMode;
private final static int MAX_ROOT_IN_PREVIEW_PRESENTATION = 3;
private PsiElement[] myRoots;
private final Function<PsiElement, PsiType> myRootTypes;
private final boolean myAllowDependentRoots;
private final TypeMigrationRules myRules;
private TypeMigrationLabeler myLabeler;
public TypeMigrationProcessor(final Project project, final PsiElement[] roots, final Function<PsiElement, PsiType> rootTypes, final TypeMigrationRules rules) {
public TypeMigrationProcessor(final Project project,
final PsiElement[] roots,
final Function<PsiElement, PsiType> rootTypes,
final TypeMigrationRules rules,
final boolean allowDependentRoots) {
super(project);
myRoots = roots;
myRules = rules;
myRootTypes = rootTypes;
myAllowDependentRoots = allowDependentRoots;
}
public static void runHighlightingTypeMigration(final Project project,
@@ -69,7 +76,7 @@ public class TypeMigrationProcessor extends BaseRefactoringProcessor {
final TypeMigrationRules rules,
final PsiElement root,
final PsiType migrationType) {
runHighlightingTypeMigration(project, editor, rules, root, migrationType, false);
runHighlightingTypeMigration(project, editor, rules, root, migrationType, false, true);
}
public static void runHighlightingTypeMigration(final Project project,
@@ -77,8 +84,9 @@ public class TypeMigrationProcessor extends BaseRefactoringProcessor {
final TypeMigrationRules rules,
final PsiElement root,
final PsiType migrationType,
final boolean optimizeImports) {
runHighlightingTypeMigration(project, editor, rules, new PsiElement[] {root}, Functions.constant(migrationType), optimizeImports);
final boolean optimizeImports,
boolean allowDependentRoots) {
runHighlightingTypeMigration(project, editor, rules, new PsiElement[] {root}, Functions.constant(migrationType), optimizeImports, allowDependentRoots);
}
@@ -87,9 +95,10 @@ public class TypeMigrationProcessor extends BaseRefactoringProcessor {
final TypeMigrationRules rules,
final PsiElement[] roots,
final Function<PsiElement, PsiType> migrationTypeFunction,
final boolean optimizeImports) {
final Set<PsiFile> containingFiles = ContainerUtil.map2Set(roots, element -> element.getContainingFile());
final TypeMigrationProcessor processor = new TypeMigrationProcessor(project, roots, migrationTypeFunction, rules) {
final boolean optimizeImports,
boolean allowDependentRoots) {
final Set<PsiFile> containingFiles = ContainerUtil.map2Set(roots, PsiElement::getContainingFile);
final TypeMigrationProcessor processor = new TypeMigrationProcessor(project, roots, migrationTypeFunction, rules, allowDependentRoots) {
@Override
public void performRefactoring(@NotNull final UsageInfo[] usages) {
super.performRefactoring(usages);
@@ -142,6 +151,10 @@ public class TypeMigrationProcessor extends BaseRefactoringProcessor {
protected boolean preprocessUsages(@NotNull Ref<UsageInfo[]> refUsages) {
if (hasFailedConversions()) {
if (ApplicationManager.getApplication().isUnitTestMode()) {
if (ourSkipFailedConversionInTestMode) {
prepareSuccessful();
return true;
}
throw new BaseRefactoringProcessor.ConflictsInTestsException(Arrays.asList(myLabeler.getFailedConversionsReport()));
}
FailedConversionsDialog dialog = new FailedConversionsDialog(myLabeler.getFailedConversionsReport(), myProject);
@@ -213,7 +226,7 @@ public class TypeMigrationProcessor extends BaseRefactoringProcessor {
@NotNull
@Override
public UsageInfo[] findUsages() {
myLabeler = new TypeMigrationLabeler(myRules, myRootTypes);
myLabeler = new TypeMigrationLabeler(myRules, myRootTypes, myAllowDependentRoots ? null : myRoots);
try {
return myLabeler.getMigratedUsages(!isPreviewUsages(), myRoots);
@@ -287,6 +300,7 @@ public class TypeMigrationProcessor extends BaseRefactoringProcessor {
return myLabeler;
}
@NotNull
@Override
protected String getCommandName() {
return "TypeMigration";
@@ -386,6 +386,8 @@ class TypeMigrationStatementProcessor extends JavaRecursiveElementVisitor {
final PsiExpression rOperand = operands[i];
if (rOperand == null) return;
final TypeView right = new TypeView(rOperand);
if (tryFindConversionIfOperandIsNull(left, right, rOperand)) continue;
if (tryFindConversionIfOperandIsNull(right, left, lOperand)) continue;
if (!TypeConversionUtil.isBinaryOperatorApplicable(operationTokenType, left.getType(), right.getType(), false)) {
if (left.isChanged()) {
findConversionOrFail(lOperand, lOperand, left.getTypePair());
@@ -399,6 +401,19 @@ class TypeMigrationStatementProcessor extends JavaRecursiveElementVisitor {
}
}
protected boolean tryFindConversionIfOperandIsNull(TypeView nullCandidate, TypeView comparingType, PsiExpression comparingExpr) {
if (nullCandidate.getType() == PsiType.NULL && comparingType.isChanged()) {
Pair<PsiType, PsiType> typePair = comparingType.getTypePair();
final TypeConversionDescriptorBase
conversion = myLabeler.getRules().findConversion(typePair.getFirst(), typePair.getSecond(), null, comparingExpr, false, myLabeler);
if (conversion != null) {
myLabeler.setConversionMapping(comparingExpr, conversion);
}
return true;
}
return false;
}
private void processArrayInitializer(final PsiArrayInitializerExpression expression, final PsiExpression parentExpression) {
final PsiExpression[] initializers = expression.getInitializers();
PsiType migrationType = null;
@@ -593,6 +608,7 @@ class TypeMigrationStatementProcessor extends JavaRecursiveElementVisitor {
}
}
private static boolean canBeVariableType(@NotNull PsiType type) {
return !type.getDeepComponentType().equals(PsiType.VOID);
}
@@ -98,7 +98,7 @@ public abstract class TypeMigrationDialog extends RefactoringDialog {
myRules = new TypeMigrationRules();
myRules.setBoundScope(myScopeChooserCombo.getSelectedScope());
}
invokeRefactoring(new TypeMigrationProcessor(myProject, myRoots, getMigrationTypeFunction(), myRules));
invokeRefactoring(new TypeMigrationProcessor(myProject, myRoots, getMigrationTypeFunction(), myRules, true));
}
@NotNull
@@ -1,6 +1,5 @@
package com.intellij.refactoring.typeMigration;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.fileTypes.StdFileTypes;
import com.intellij.openapi.project.Project;
import com.intellij.psi.JavaPsiFacade;
@@ -15,8 +14,6 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class TypeConversionDescriptor extends TypeConversionDescriptorBase {
private static final Logger LOG = Logger.getInstance(TypeConversionDescriptor.class);
private String myStringToReplace = null;
private String myReplaceByString = "$";
private PsiExpression myExpression;
@@ -75,9 +72,15 @@ public class TypeConversionDescriptor extends TypeConversionDescriptorBase {
@Override
public PsiExpression replace(PsiExpression expression, @NotNull TypeEvaluator evaluator) {
if (getExpression() != null) expression = getExpression();
expression = adjustExpressionBeforeReplacement(expression);
return replaceExpression(expression, getStringToReplace(), getReplaceByString());
}
@NotNull
protected PsiExpression adjustExpressionBeforeReplacement(@NotNull PsiExpression expression) {
return expression;
}
@NotNull
public static PsiExpression replaceExpression(@NotNull PsiExpression expression,
String stringToReplace,
@@ -50,7 +50,7 @@ public class TypeMigrationVariableTypeFixProvider implements ChangeVariableTypeQ
@Nullable("is null when called from inspection") Editor editor,
@NotNull PsiElement startElement,
@NotNull PsiElement endElement) {
runTypeMigrationOnVariable((PsiVariable)startElement, getReturnType(), editor, optimizeImports);
runTypeMigrationOnVariable((PsiVariable)startElement, getReturnType(), editor, optimizeImports, true);
}
@Override
@@ -63,14 +63,15 @@ public class TypeMigrationVariableTypeFixProvider implements ChangeVariableTypeQ
public static void runTypeMigrationOnVariable(@NotNull PsiVariable variable,
@NotNull PsiType targetType,
@Nullable("is null when called from inspection") Editor editor,
boolean optimizeImports) {
boolean optimizeImports,
boolean allowDependentRoots) {
Project project = variable.getProject();
if (!FileModificationService.getInstance().prepareFileForWrite(variable.getContainingFile())) return;
try {
WriteAction.run(() -> variable.normalizeDeclaration());
final TypeMigrationRules rules = new TypeMigrationRules();
rules.setBoundScope(GlobalSearchScope.projectScope(project));
TypeMigrationProcessor.runHighlightingTypeMigration(project, editor, rules, variable, targetType, optimizeImports);
TypeMigrationProcessor.runHighlightingTypeMigration(project, editor, rules, variable, targetType, optimizeImports, allowDependentRoots);
WriteAction.run(() -> JavaCodeStyleManager.getInstance(project).shortenClassReferences(variable));
UndoUtil.markPsiFileForUndo(variable.getContainingFile());
}
@@ -359,6 +359,7 @@ public class GuavaInspection extends BaseJavaLocalInspectionTool {
rules,
elements.toArray(new PsiElement[elements.size()]),
createMigrationTypeFunction(elements, types),
true,
true);
}
@@ -41,7 +41,7 @@ public class ConvertAtomicToLongAdderIntention extends PsiElementBaseIntentionAc
if (variable != null) {
final PsiType longAdder =
JavaPsiFacade.getElementFactory(project).createTypeFromText(LongAdderConversionRule.JAVA_UTIL_CONCURRENT_ATOMIC_LONG_ADDER, element);
TypeMigrationVariableTypeFixProvider.runTypeMigrationOnVariable(variable, longAdder, null, false);
TypeMigrationVariableTypeFixProvider.runTypeMigrationOnVariable(variable, longAdder, null, false, false);
}
}
@@ -9,31 +9,21 @@ import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.CodeStyleManager;
import com.intellij.psi.codeStyle.CodeStyleSettingsManager;
import com.intellij.psi.codeStyle.JavaCodeStyleManager;
import com.intellij.psi.impl.AllowedApiFilterExtension;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.search.searches.ReferencesSearch;
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.TypeEvaluator;
import com.intellij.refactoring.typeMigration.TypeMigrationReplacementUtil;
import com.intellij.refactoring.typeMigration.rules.AtomicConversionRule;
import com.intellij.refactoring.util.RefactoringUtil;
import com.intellij.refactoring.typeMigration.TypeMigrationVariableTypeFixProvider;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.Query;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.*;
import static com.intellij.psi.util.TypeConversionUtil.isBinaryOperatorApplicable;
import static com.intellij.util.ObjectUtils.assertNotNull;
/**
@@ -102,145 +92,118 @@ public class ConvertFieldToAtomicIntention extends PsiElementBaseIntentionAction
@Override
public void invoke(@NotNull Project project, Editor editor, @NotNull PsiElement element) throws IncorrectOperationException {
final PsiVariable psiVariable = getVariable(element);
LOG.assertTrue(psiVariable != null);
final PsiVariable var = getVariable(element);
LOG.assertTrue(var != null);
final Query<PsiReference> refs = ReferencesSearch.search(psiVariable);
final PsiType fromType = var.getType();
PsiClassType toType = getMigrationTargetType(project, element, fromType);
if (toType == null) return;
final Set<PsiElement> elements = new HashSet<>();
elements.add(element);
for (PsiReference reference : refs) {
elements.add(reference.getElement());
if (!FileModificationService.getInstance().preparePsiElementsForWrite(var)) return;
addExplicitInitializer(var);
String toTypeCanonicalText = toType.getCanonicalText();
TypeMigrationVariableTypeFixProvider.runTypeMigrationOnVariable(var, toType, editor, false, false);
postProcessVariable(var, toTypeCanonicalText);
}
static void addExplicitInitializer(@NotNull PsiVariable var) {
PsiExpression currentInitializer = var.getInitializer();
if (currentInitializer != null) return;
final PsiType type = var.getType();
String initializerText = null;
if (PsiType.BOOLEAN.equals(type)) {
initializerText = "false";
}
if (!FileModificationService.getInstance().preparePsiElementsForWrite(elements)) return;
else if (type instanceof PsiPrimitiveType) {
initializerText = "0";
}
if (initializerText != null) {
String finalInitializerText = initializerText;
WriteAction.run(() -> {
PsiExpression initializer = JavaPsiFacade.getElementFactory(var.getProject()).createExpressionFromText(finalInitializerText, var);
if (var instanceof PsiLocalVariable) {
((PsiLocalVariable)var).setInitializer(initializer);
}
else if (var instanceof PsiField) {
((PsiField)var).setInitializer(initializer);
}
});
}
}
WriteAction.run(() -> {
psiVariable.normalizeDeclaration();
static void postProcessVariable(@NotNull PsiVariable var, @NotNull String toType) {
final JavaPsiFacade psiFacade = JavaPsiFacade.getInstance(project);
final PsiElementFactory factory = JavaPsiFacade.getElementFactory(project);
final PsiType fromType = psiVariable.getType();
PsiClassType toType;
final String atomicQualifiedName = myFromToMap.get(fromType);
if (atomicQualifiedName != null) {
final PsiClass atomicClass = psiFacade.findClass(atomicQualifiedName, GlobalSearchScope.allScope(project));
if (atomicClass == null) {//show warning
return;
}
toType = factory.createType(atomicClass);
}
else if (fromType instanceof PsiArrayType) {
final PsiClass atomicReferenceArrayClass =
psiFacade.findClass(AtomicReferenceArray.class.getName(), GlobalSearchScope.allScope(project));
if (atomicReferenceArrayClass == null) {//show warning
return;
}
final Map<PsiTypeParameter, PsiType> substitutor = ContainerUtil.newHashMap();
final PsiTypeParameter[] typeParameters = atomicReferenceArrayClass.getTypeParameters();
if (typeParameters.length == 1) {
PsiType componentType = ((PsiArrayType)fromType).getComponentType();
if (componentType instanceof PsiPrimitiveType) componentType = ((PsiPrimitiveType)componentType).getBoxedType(element);
substitutor.put(typeParameters[0], componentType);
}
toType = factory.createType(atomicReferenceArrayClass, factory.createSubstitutor(substitutor));
}
else {
final PsiClass atomicReferenceClass = psiFacade.findClass(AtomicReference.class.getName(), GlobalSearchScope.allScope(project));
if (atomicReferenceClass == null) {//show warning
return;
}
final Map<PsiTypeParameter, PsiType> substitutor = ContainerUtil.newHashMap();
final PsiTypeParameter[] typeParameters = atomicReferenceClass.getTypeParameters();
if (typeParameters.length == 1) {
PsiType type = fromType;
if (type instanceof PsiPrimitiveType) type = ((PsiPrimitiveType)fromType).getBoxedType(element);
substitutor.put(typeParameters[0], type);
}
toType = factory.createType(atomicReferenceClass, factory.createSubstitutor(substitutor));
}
try {
for (PsiReference reference : refs) {
PsiElement refElement = reference.getElement();
PsiElement psiElement = refElement;
if (psiElement instanceof PsiExpression) {
final PsiElement parent = psiElement.getParent();
if (parent instanceof PsiExpression && !(parent instanceof PsiReferenceExpression || parent instanceof PsiPolyadicExpression)) {
psiElement = parent;
}
if (psiElement instanceof PsiBinaryExpression) {
PsiBinaryExpression binary = (PsiBinaryExpression)psiElement;
if (isBinaryOpApplicable(binary.getOperationTokenType(), binary.getLOperand(), binary.getROperand(), refElement, toType)) {
continue;
}
}
else if (psiElement instanceof PsiAssignmentExpression) {
final PsiAssignmentExpression assignment = (PsiAssignmentExpression)psiElement;
final IElementType opSign = TypeConversionUtil.convertEQtoOperation(assignment.getOperationTokenType());
if (isBinaryOpApplicable(opSign, assignment.getLExpression(), assignment.getRExpression(), refElement, toType)) {
continue;
}
}
final TypeConversionDescriptor directConversion = AtomicConversionRule.findDirectConversion(psiElement, toType, fromType);
if (directConversion != null) {
TypeMigrationReplacementUtil
.replaceExpression((PsiExpression)psiElement, project, directConversion, new TypeEvaluator(null, null));
}
Project project = var.getProject();
if (var instanceof PsiField || CodeStyleSettingsManager.getSettings(project).GENERATE_FINAL_LOCALS) {
PsiModifierList modifierList = assertNotNull(var.getModifierList());
WriteAction.run(() -> {
if (var.getInitializer() == null) {
final PsiExpression newInitializer = JavaPsiFacade.getElementFactory(project).createExpressionFromText("new " + toType + "()", var);
if (var instanceof PsiLocalVariable) {
((PsiLocalVariable)var).setInitializer(newInitializer);
}
else if (var instanceof PsiField) {
((PsiField)var).setInitializer(newInitializer);
}
JavaCodeStyleManager.getInstance(var.getProject()).shortenClassReferences(var.getInitializer());
}
PsiExpression initializer = psiVariable.getInitializer();
if (initializer != null) {
if (initializer instanceof PsiArrayInitializerExpression) {
PsiExpression normalizedExpr =
RefactoringUtil.createNewExpressionFromArrayInitializer((PsiArrayInitializerExpression)initializer, psiVariable.getType());
initializer = (PsiExpression)initializer.replace(normalizedExpr);
}
final TypeConversionDescriptor directConversion =
AtomicConversionRule.wrapWithNewExpression(toType, fromType, initializer, element);
if (directConversion != null) {
TypeMigrationReplacementUtil.replaceExpression(initializer, project, directConversion, new TypeEvaluator(null, null));
}
}
else if (!assertNotNull(psiVariable.getModifierList()).hasModifierProperty(PsiModifier.FINAL)) {
final PsiExpression newInitializer = factory.createExpressionFromText("new " + toType.getCanonicalText() + "()", psiVariable);
if (psiVariable instanceof PsiLocalVariable) {
((PsiLocalVariable)psiVariable).setInitializer(newInitializer);
}
else if (psiVariable instanceof PsiField) {
((PsiField)psiVariable).setInitializer(newInitializer);
}
JavaCodeStyleManager.getInstance(project).shortenClassReferences(psiVariable.getInitializer());
}
modifierList.setModifierProperty(PsiModifier.FINAL, true);
modifierList.setModifierProperty(PsiModifier.VOLATILE, false);
PsiElement replaced = assertNotNull(psiVariable.getTypeElement()).replace(factory.createTypeElement(toType));
JavaCodeStyleManager.getInstance(project).shortenClassReferences(replaced);
JavaCodeStyleManager.getInstance(project).shortenClassReferences(var);
CodeStyleManager.getInstance(project).reformat(var);
});
}
}
if (psiVariable instanceof PsiField || CodeStyleSettingsManager.getSettings(project).GENERATE_FINAL_LOCALS) {
final PsiModifierList modifierList = assertNotNull(psiVariable.getModifierList());
modifierList.setModifierProperty(PsiModifier.FINAL, true);
modifierList.setModifierProperty(PsiModifier.VOLATILE, false);
}
@Nullable
private PsiClassType getMigrationTargetType(@NotNull Project project,
@NotNull PsiElement element,
@NotNull PsiType fromType) {
JavaPsiFacade psiFacade = JavaPsiFacade.getInstance(project);
PsiElementFactory factory = psiFacade.getElementFactory();
final String atomicQualifiedName = myFromToMap.get(fromType);
if (atomicQualifiedName != null) {
final PsiClass atomicClass = psiFacade.findClass(atomicQualifiedName, GlobalSearchScope.allScope(project));
if (atomicClass == null) {//show warning
return null;
}
catch (IncorrectOperationException e) {
LOG.error(e);
return factory.createType(atomicClass);
}
else if (fromType instanceof PsiArrayType) {
final PsiClass atomicReferenceArrayClass =
psiFacade.findClass(AtomicReferenceArray.class.getName(), GlobalSearchScope.allScope(project));
if (atomicReferenceArrayClass == null) {//show warning
return null;
}
});
final Map<PsiTypeParameter, PsiType> substitutor = ContainerUtil.newHashMap();
final PsiTypeParameter[] typeParameters = atomicReferenceArrayClass.getTypeParameters();
if (typeParameters.length == 1) {
PsiType componentType = ((PsiArrayType)fromType).getComponentType();
if (componentType instanceof PsiPrimitiveType) componentType = ((PsiPrimitiveType)componentType).getBoxedType(element);
substitutor.put(typeParameters[0], componentType);
}
return factory.createType(atomicReferenceArrayClass, factory.createSubstitutor(substitutor));
}
else {
final PsiClass atomicReferenceClass = psiFacade.findClass(AtomicReference.class.getName(), GlobalSearchScope.allScope(project));
if (atomicReferenceClass == null) {//show warning
return null;
}
final Map<PsiTypeParameter, PsiType> substitutor = ContainerUtil.newHashMap();
final PsiTypeParameter[] typeParameters = atomicReferenceClass.getTypeParameters();
if (typeParameters.length == 1) {
PsiType type = fromType;
if (type instanceof PsiPrimitiveType) type = ((PsiPrimitiveType)fromType).getBoxedType(element);
substitutor.put(typeParameters[0], type);
}
return factory.createType(atomicReferenceClass, factory.createSubstitutor(substitutor));
}
}
@Override
public boolean startInWriteAction() {
return false;
}
private static boolean isBinaryOpApplicable(@Nullable IElementType opSign,
@NotNull PsiExpression lExpr,
@Nullable PsiExpression rExpr,
@NotNull PsiElement varElement,
@NotNull PsiType migrationType) {
if (opSign == null || rExpr == null) return false;
PsiType lType = lExpr == varElement ? migrationType : lExpr.getType();
PsiType rType = rExpr == varElement ? migrationType : rExpr.getType();
return isBinaryOperatorApplicable(opSign, lType, rType, true);
}
}
@@ -4,32 +4,22 @@ import com.intellij.codeInsight.FileModificationService;
import com.intellij.codeInsight.intention.LowPriorityAction;
import com.intellij.codeInsight.intention.PsiElementBaseIntentionAction;
import com.intellij.lang.java.JavaLanguage;
import com.intellij.openapi.application.WriteAction;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Comparing;
import com.intellij.psi.*;
import com.intellij.psi.codeStyle.CodeStyleManager;
import com.intellij.psi.impl.AllowedApiFilterExtension;
import com.intellij.psi.impl.PsiDiamondTypeUtil;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.search.searches.ReferencesSearch;
import com.intellij.psi.util.PsiTreeUtil;
import com.intellij.psi.util.PsiUtil;
import com.intellij.refactoring.typeMigration.*;
import com.intellij.refactoring.typeMigration.rules.ThreadLocalConversionRule;
import com.intellij.refactoring.util.RefactoringUtil;
import com.intellij.refactoring.typeMigration.TypeMigrationVariableTypeFixProvider;
import com.intellij.util.IncorrectOperationException;
import com.intellij.util.Query;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import static com.intellij.util.ObjectUtils.assertNotNull;
public class ConvertFieldToThreadLocalIntention extends PsiElementBaseIntentionAction implements LowPriorityAction {
private static final Logger LOG = Logger.getInstance(ConvertFieldToThreadLocalIntention.class);
@@ -66,101 +56,39 @@ public class ConvertFieldToThreadLocalIntention extends PsiElementBaseIntentionA
public void invoke(@NotNull Project project, Editor editor, @NotNull PsiElement element) throws IncorrectOperationException {
final PsiField psiField = PsiTreeUtil.getParentOfType(element, PsiField.class);
LOG.assertTrue(psiField != null);
final Query<PsiReference> refs = ReferencesSearch.search(psiField);
final Set<PsiElement> elements = new HashSet<>();
elements.add(element);
for (PsiReference reference : refs) {
elements.add(reference.getElement());
}
if (!FileModificationService.getInstance().preparePsiElementsForWrite(elements)) return;
WriteAction.run(() -> {
psiField.normalizeDeclaration();
final PsiType fromType = psiField.getType();
final JavaPsiFacade psiFacade = JavaPsiFacade.getInstance(project);
final PsiElementFactory factory = JavaPsiFacade.getElementFactory(project);
final PsiType fromType = psiField.getType();
final PsiClassType toType = getMigrationTargetType(fromType, project, element);
if (toType == null) return;
final PsiClass threadLocalClass = psiFacade.findClass(ThreadLocal.class.getName(), GlobalSearchScope.allScope(project));
if (threadLocalClass == null) {//show warning
return;
}
final Map<PsiTypeParameter, PsiType> substitutor = ContainerUtil.newHashMap();
final PsiTypeParameter[] typeParameters = threadLocalClass.getTypeParameters();
if (typeParameters.length == 1) {
PsiType type = fromType;
if (fromType instanceof PsiPrimitiveType) type = ((PsiPrimitiveType)fromType).getBoxedType(element);
substitutor.put(typeParameters[0], type);
}
final PsiClassType toType = factory.createType(threadLocalClass, factory.createSubstitutor(substitutor));
try {
final TypeMigrationRules rules = new TypeMigrationRules();
rules.setBoundScope(GlobalSearchScope.fileScope(element.getContainingFile()));
final TypeMigrationLabeler labeler = new TypeMigrationLabeler(rules, toType);
labeler.getMigratedUsages(false, psiField);
for (PsiReference reference : refs) {
PsiElement psiElement = reference.getElement();
if (psiElement instanceof PsiExpression) {
final PsiElement parent = psiElement.getParent();
if (parent instanceof PsiExpression && !(parent instanceof PsiReferenceExpression || parent instanceof PsiPolyadicExpression)) {
psiElement = parent;
}
final TypeConversionDescriptor conversion =
ThreadLocalConversionRule.findDirectConversion(psiElement, toType, fromType, labeler);
if (conversion != null) {
TypeMigrationReplacementUtil.replaceExpression((PsiExpression)psiElement, project, conversion, new TypeEvaluator(null, null));
}
}
}
PsiExpression initializer = psiField.getInitializer();
if (initializer == null &&
!psiField.hasModifierProperty(PsiModifier.FINAL)) {
final PsiType type = psiField.getType();
String initializerText = null;
if (PsiType.BOOLEAN.equals(type)) {
initializerText = "false";
}
else if (type instanceof PsiPrimitiveType) {
initializerText = "0";
}
if (initializerText != null) {
psiField.setInitializer(factory.createExpressionFromText(initializerText, psiField));
initializer = psiField.getInitializer();
}
}
if (initializer != null) {
if (initializer instanceof PsiArrayInitializerExpression) {
PsiExpression normalizedExpr =
RefactoringUtil.createNewExpressionFromArrayInitializer((PsiArrayInitializerExpression)initializer, psiField.getType());
initializer = (PsiExpression)initializer.replace(normalizedExpr);
}
final TypeConversionDescriptor conversion = ThreadLocalConversionRule.wrapWithNewExpression(toType, fromType, initializer);
TypeMigrationReplacementUtil.replaceExpression(initializer, project, conversion, new TypeEvaluator(null, null));
CodeStyleManager.getInstance(project).reformat(psiField);
}
else if (!assertNotNull(psiField.getModifierList()).hasModifierProperty(PsiModifier.FINAL)) {
final String text = "new " + PsiDiamondTypeUtil.getCollapsedType(toType, psiField) + "()";
final PsiExpression newInitializer = factory.createExpressionFromText(text, psiField);
psiField.setInitializer(newInitializer);
}
assertNotNull(psiField.getTypeElement()).replace(factory.createTypeElement(toType));
final PsiModifierList modifierList = assertNotNull(psiField.getModifierList());
modifierList.setModifierProperty(PsiModifier.FINAL, true);
modifierList.setModifierProperty(PsiModifier.VOLATILE, false);
}
catch (IncorrectOperationException e) {
LOG.error(e);
}
});
if (!FileModificationService.getInstance().preparePsiElementsForWrite(psiField)) return;
ConvertFieldToAtomicIntention.addExplicitInitializer(psiField);
String toTypeCanonicalText = toType.getCanonicalText();
TypeMigrationVariableTypeFixProvider.runTypeMigrationOnVariable(psiField, toType, editor, false, false);
ConvertFieldToAtomicIntention.postProcessVariable(psiField, toTypeCanonicalText);
}
@Override
public boolean startInWriteAction() {
return false;
}
@Nullable
private static PsiClassType getMigrationTargetType(@NotNull PsiType fromType, @NotNull Project project, @NotNull PsiElement context) {
JavaPsiFacade psiFacade = JavaPsiFacade.getInstance(project);
final PsiClass threadLocalClass = psiFacade.findClass(ThreadLocal.class.getName(), GlobalSearchScope.allScope(project));
if (threadLocalClass == null) {//show warning
return null;
}
final Map<PsiTypeParameter, PsiType> substitutor = ContainerUtil.newHashMap();
final PsiTypeParameter[] typeParameters = threadLocalClass.getTypeParameters();
if (typeParameters.length == 1) {
PsiType type = fromType;
if (fromType instanceof PsiPrimitiveType) type = ((PsiPrimitiveType)fromType).getBoxedType(context);
substitutor.put(typeParameters[0], type);
}
PsiElementFactory factory = psiFacade.getElementFactory();
return factory.createType(threadLocalClass, factory.createSubstitutor(substitutor));
}
}
@@ -87,6 +87,9 @@ public class AtomicConversionRule extends TypeConversionRule {
final PsiClass toTypeClass = PsiUtil.resolveClassInType(to);
LOG.assertTrue(toTypeClass != null);
final String qualifiedName = toTypeClass.getQualifiedName();
if (context instanceof PsiParenthesizedExpression) {
context = ParenthesesUtils.stripParentheses((PsiExpression)context);
}
if (qualifiedName != null) {
if (qualifiedName.equals(AtomicInteger.class.getName()) || qualifiedName.equals(AtomicLong.class.getName())) {
@@ -271,12 +274,33 @@ public class AtomicConversionRule extends TypeConversionRule {
final PsiClassType boxedFromType = ((PsiPrimitiveType)from).getBoxedType(atomicClass);
LOG.assertTrue(boxedFromType != null);
if (!TypeConversionUtil.isAssignable(initial, boxedFromType)) {
return new TypeConversionDescriptor("$val$", "new " + typeText + "((" + unboxedInitialType.getCanonicalText() + ")$val$)", expression);
return new ArrayInitializerAwareConversionDescriptor("$val$", "new " + typeText + "((" + unboxedInitialType.getCanonicalText() + ")$val$)", expression);
}
}
}
}
return new TypeConversionDescriptor("$val$", "new " + typeText + "($val$)", expression);
return new ArrayInitializerAwareConversionDescriptor("$val$", "new " + typeText + "($val$)", expression);
}
static class ArrayInitializerAwareConversionDescriptor extends TypeConversionDescriptor {
public ArrayInitializerAwareConversionDescriptor(String stringToReplace,
String replaceByString,
PsiExpression expression) {
super(stringToReplace, replaceByString, expression);
}
@NotNull
@Override
protected PsiExpression adjustExpressionBeforeReplacement(@NotNull PsiExpression expression) {
if (expression instanceof PsiArrayInitializerExpression) {
PsiElementFactory elementFactory = JavaPsiFacade.getInstance(expression.getProject()).getElementFactory();
return (PsiExpression)expression.replace(elementFactory.createExpressionFromText("new " +
TypeConversionUtil.erasure(expression.getType()).getCanonicalText() +
expression.getText(),
expression));
}
return expression;
}
}
@Nullable
@@ -286,6 +310,9 @@ public class AtomicConversionRule extends TypeConversionRule {
final PsiElement parent = context.getParent();
final PsiElement parentParent = parent.getParent();
if (context instanceof PsiReferenceExpression && isReferenceToLengthField((PsiReferenceExpression)context)) {
return new TypeConversionDescriptor("$qualifier$.length", "$qualifier$.length()");
}
if (parent instanceof PsiAssignmentExpression) {
final PsiAssignmentExpression assignmentExpression = (PsiAssignmentExpression)parent;
final IElementType operationSign = assignmentExpression.getOperationTokenType();
@@ -344,9 +371,6 @@ public class AtomicConversionRule extends TypeConversionRule {
if (context instanceof PsiArrayAccessExpression) {
return new TypeConversionDescriptor("$qualifier$[$idx$]", "$qualifier$.get($idx$)", (PsiExpression)context);
}
if (parent instanceof PsiReferenceExpression && isReferenceToLengthField((PsiReferenceExpression)parent)) {
return new TypeConversionDescriptor("$qualifier$.length", "$qualifier$.length()", (PsiExpression)parent);
}
return null;
}
@@ -91,6 +91,10 @@ public class ThreadLocalConversionRule extends TypeConversionRule {
return new TypeConversionDescriptor("$qualifier$" + sign + "$val$", toPrimitive("$qualifier$.get()", from, context) + " " + sign + " $val$");
}
if (parent instanceof PsiVariable && ((PsiVariable)parent).getInitializer() == context) {
return wrapWithNewExpression(to, from, (PsiExpression)context);
}
if (parent instanceof PsiExpressionStatement) {
if (context instanceof PsiPostfixExpression) {
final PsiPostfixExpression postfixExpression = (PsiPostfixExpression)context;
@@ -169,12 +173,12 @@ public class ThreadLocalConversionRule extends TypeConversionRule {
PsiExpression initializer,
String boxedTypeName) {
if (PsiUtil.isLanguageLevel8OrHigher(initializer)) {
return "java.lang.ThreadLocal.withInitial(() -> " + initializer.getText() + ")";
return "java.lang.ThreadLocal.withInitial(() -> $qualifier$)";
}
return "new " +
to.getCanonicalText() +
"() {\n" +
"@Override \n" +
"@Override\n" +
"protected " +
boxedTypeName +
" initialValue() {\n" +
@@ -183,9 +187,7 @@ public class ThreadLocalConversionRule extends TypeConversionRule {
? initializer.getText()
: (from instanceof PsiPrimitiveType ? "new " +
((PsiPrimitiveType)from).getBoxedTypeName() +
"(" +
initializer.getText() +
")" : initializer.getText())) +
"($qualifier$)" : "$qualifier$")) +
";\n" +
"}\n" +
"}";
@@ -242,7 +244,7 @@ public class ThreadLocalConversionRule extends TypeConversionRule {
return toBoxed(arg, from, context);
}
private static class WrappingWithInnerClassOrLambdaDescriptor extends TypeConversionDescriptor {
private static class WrappingWithInnerClassOrLambdaDescriptor extends AtomicConversionRule.ArrayInitializerAwareConversionDescriptor {
private final List<PsiVariable> myVariablesToMakeFinal;
private WrappingWithInnerClassOrLambdaDescriptor(@NonNls final String stringToReplace,
@@ -1,6 +1,7 @@
package com.intellij.codeInsight;
import com.intellij.codeInsight.daemon.quickFix.LightQuickFixParameterizedTestCase;
import com.intellij.refactoring.typeMigration.TypeMigrationProcessor;
import com.intellij.testFramework.PlatformTestUtil;
import org.jetbrains.annotations.NotNull;
@@ -24,6 +25,18 @@ public class ConvertToAtomicIntentionTest extends LightQuickFixParameterizedTest
return PlatformTestUtil.getCommunityPath() + "/java/typeMigration/testData";
}
@Override
public void setUp() throws Exception {
super.setUp();
TypeMigrationProcessor.ourSkipFailedConversionInTestMode = true;
}
@Override
public void tearDown() throws Exception {
TypeMigrationProcessor.ourSkipFailedConversionInTestMode = false;
super.tearDown();
}
public void test() throws Exception {
doAllTests();
}
@@ -52,7 +52,8 @@ public class ChangeTypeSignatureTest extends LightCodeInsightTestCase {
new TypeMigrationProcessor(getProject(),
new PsiElement[]{parameterList},
Functions.constant(PsiSubstitutor.EMPTY.put(superClass.getTypeParameters()[0], migrationType).substitute(new PsiImmediateClassType(superClass, PsiSubstitutor.EMPTY))),
rules).run();
rules,
true).run();
if (success) {
checkResultByFile(dataPath + getTestName(false) + ".java.after");
} else {
@@ -229,7 +229,7 @@ public abstract class TypeMigrationTestBase extends MultiFileTestCase {
private static class TestTypeMigrationProcessor extends TypeMigrationProcessor {
public TestTypeMigrationProcessor(final Project project, final PsiElement[] roots, final PsiType migrationType, final TypeMigrationRules rules) {
super(project, roots, Functions.constant(migrationType), rules);
super(project, roots, Functions.constant(migrationType), rules, true);
}
}
}
@@ -2,7 +2,7 @@ import java.util.concurrent.atomic.AtomicIntegerArray;
// "Convert to atomic" "true"
class Test {
final AtomicIntegerArray field= new AtomicIntegerArray(foo());
final AtomicIntegerArray field = new AtomicIntegerArray(foo());
int[] foo() {
return null;
}
@@ -2,7 +2,7 @@ import java.util.concurrent.atomic.AtomicInteger;
// "Convert to atomic" "true"
class Test {
final AtomicInteger o = new AtomicInteger(0);
final AtomicInteger o = new AtomicInteger(0);
void foo() {
boolean b = this.o.get() == 1;
@@ -2,5 +2,5 @@ import java.util.concurrent.atomic.AtomicInteger;
// "Convert to atomic" "true"
class Test {
final AtomicInteger i = new AtomicInteger(0);
final AtomicInteger i = new AtomicInteger(0);
}
@@ -2,7 +2,7 @@ import java.util.concurrent.atomic.AtomicLong;
// "Convert to atomic" "true"
class T {
private final AtomicLong l = new AtomicLong(10L);
private final AtomicLong l = new AtomicLong(10L);
public synchronized void update(long m) {
l.set(m);
@@ -2,7 +2,7 @@ import java.util.concurrent.atomic.AtomicReferenceArray;
// "Convert to atomic" "true"
class Test {
final AtomicReferenceArray<Object> field= new AtomicReferenceArray<>(foo());
final AtomicReferenceArray<Object> field = new AtomicReferenceArray<>(foo());
Object[] foo() {
return null;
}
@@ -2,6 +2,6 @@ import java.util.concurrent.atomic.AtomicIntegerArray;
// "Convert to atomic" "true"
class Test {
final AtomicIntegerArray field= new AtomicIntegerArray(new int[]{1});
final AtomicIntegerArray field = new AtomicIntegerArray(new int[]{1});
}
@@ -2,8 +2,8 @@ import java.util.concurrent.atomic.AtomicInteger;
// "Convert to atomic" "true"
class Test {
final AtomicInteger i = new AtomicInteger(0);
final AtomicInteger i = new AtomicInteger(0);
int j = i.get() + 5;
String s = "i = " + i.get();
String s = "i = " + i;
}
@@ -2,8 +2,8 @@ import java.util.concurrent.atomic.AtomicInteger;
// "Convert to atomic" "true"
class Test {
final AtomicInteger i = new AtomicInteger(0 + 8);
final AtomicInteger i = new AtomicInteger(0 + 8);
int j = i.get() + 5;
String s = "i = " + i.get();
String s = "i = " + i;
}
@@ -2,5 +2,5 @@ import java.util.concurrent.atomic.AtomicIntegerArray;
// "Convert to atomic" "true"
class Test {
final AtomicIntegerArray i = new AtomicIntegerArray(new int[0]);
final AtomicIntegerArray i = new AtomicIntegerArray(new int[0]);
}
@@ -2,10 +2,10 @@ import java.util.concurrent.atomic.AtomicInteger;
// "Convert to atomic" "true"
class Test {
final AtomicInteger o = new AtomicInteger(0);
final AtomicInteger o = new AtomicInteger(0);
int j = o.get();
void foo() {
while ((o = j) != 0) {}
while ((o.set(j)) != 0) {}
}
}
@@ -2,10 +2,10 @@ import java.util.concurrent.atomic.AtomicInteger;
// "Convert to atomic" "true"
class Test {
final AtomicInteger o = new AtomicInteger();
final AtomicInteger o = new AtomicInteger(0);
int j = o.get();
void foo() {
while ((o = j) != 0) {}
while ((o.set(j)) != 0) {}
}
}
@@ -2,14 +2,14 @@ import java.util.concurrent.atomic.AtomicInteger;
// "Convert to atomic" "true"
class Test {
final AtomicInteger o;
final AtomicInteger o = new AtomicInteger(0);
int j = o.get();
Test(int o) {
this.o = new AtomicInteger(o);
this.o.set(o);
}
void foo() {
while ((o = j) != 0) {}
while ((o.set(j)) != 0) {}
}
}
@@ -2,5 +2,5 @@ import java.util.concurrent.atomic.AtomicReferenceArray;
// "Convert to atomic" "true"
class Test {
final AtomicReferenceArray<String> field= new AtomicReferenceArray<>(new String[]{});
final AtomicReferenceArray<String> field = new AtomicReferenceArray<>(new String[]{});
}
@@ -2,7 +2,7 @@ import java.util.concurrent.atomic.AtomicLong;
// "Convert to atomic" "true"
class A {
final AtomicLong x = new AtomicLong(0);
final AtomicLong x = new AtomicLong(0);
public void testAtomicLong() {
x.getAndIncrement();
@@ -2,7 +2,7 @@ import java.util.concurrent.atomic.AtomicIntegerArray;
// "Convert to atomic" "true"
class Test {
final AtomicIntegerArray ii = new AtomicIntegerArray(new int[12]);
final AtomicIntegerArray ii = new AtomicIntegerArray(new int[12]);
void m() {
int k = ii.length();
@@ -2,7 +2,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
// "Convert to atomic" "true"
class Test {
final AtomicBoolean field= new AtomicBoolean(false);
final AtomicBoolean field = new AtomicBoolean(false);
{
boolean b = !field.get();
}
@@ -2,7 +2,7 @@ import java.util.concurrent.atomic.AtomicReference;
// "Convert to atomic" "true"
class X {
private final AtomicReference<String> s = new AtomicReference<>("");
private final AtomicReference<String> s = new AtomicReference<>("");
private String t;
private String u;
}
@@ -1,7 +1,7 @@
// "Convert to ThreadLocal" "true"
class Test {
static final ThreadLocal<Integer> field;
static final ThreadLocal<Integer> field = new ThreadLocal<Integer>();
static {
field = ThreadLocal.withInitial(() -> new Integer(0));
field.set(new Integer(0));
}
}
@@ -1,12 +1,11 @@
// "Convert to ThreadLocal" "true"
class Main {
private final ThreadLocal<Boolean> property;
private final ThreadLocal<Boolean> property = ThreadLocal.withInitial(() -> false);
Main3(boolean property) {
if (property) {
property = false;
}
boolean finalProperty = property;
this.property = ThreadLocal.withInitial(() -> finalProperty);
this.property.set(property);
}
}
@@ -1,8 +1,8 @@
// "Convert to ThreadLocal" "true"
class Foo {
private final ThreadLocal<Boolean> property;
private final ThreadLocal<Boolean> property = ThreadLocal.withInitial(() -> false);
Foo(boolean property) {
this.property = ThreadLocal.withInitial(() -> property);
this.property.set(property);
}
}
@@ -1,13 +1,13 @@
// "Convert to ThreadLocal" "true"
class Main {
private final ThreadLocal<Boolean> property;
Main3(final boolean property) {
this.property = new ThreadLocal<Boolean>() {
private final ThreadLocal<Boolean> property = new ThreadLocal<Boolean>() {
@Override
protected Boolean initialValue() {
return property;
return false;
}
};
Main3(boolean property) {
this.property.set(property);
}
}
@@ -6,6 +6,7 @@ PsiReferenceExpression:s : java.util.concurrent.atomic.AtomicReference<java.lang
Conversions:
"" -> new java.util.concurrent.atomic.AtomicReference<java.lang.String>($val$) $val$
s -> $qualifier$.get() $qualifier$ s
s -> $qualifier$.get() $qualifier$ s
New expression type changes:
Fails:
@@ -4,7 +4,7 @@ class Test {
AtomicReference<String> s = new AtomicReference<String>("");
void foo() {
if (s == null) {
if (s.get() == null) {
System.out.println(s.get());
}
}
@@ -4,8 +4,14 @@ PsiReferenceExpression:myS : java.lang.ThreadLocal<java.lang.String>
PsiReferenceExpression:myS : java.lang.ThreadLocal<java.lang.String>
Conversions:
"" -> new java.lang.ThreadLocal<java.lang.String>() {
@Override
protected java.lang.String initialValue() {
return "";
}
} $qualifier$ ""
myS -> $qualifier$.get() $qualifier$ myS
myS -> $qualifier$.get() $qualifier$ myS
New expression type changes:
Fails:
""->java.lang.ThreadLocal<java.lang.String>
@@ -1,8 +1,13 @@
class Test {
ThreadLocal<String> myS = "";
ThreadLocal<String> myS = new ThreadLocal<String>() {
@Override
protected String initialValue() {
return "";
}
};
void foo() {
if (myS == null) {
if (myS.get() == null) {
System.out.println(myS.get());
}
}