IDEA-151190 Change class signature doesn't support complicated bounds

This commit is contained in:
Dmitry Batkovich
2016-02-10 21:29:40 +03:00
parent 99a6ca1ede
commit f836ad796d
15 changed files with 125 additions and 40 deletions
@@ -24,7 +24,6 @@ import com.intellij.refactoring.changeClassSignature.ChangeClassSignatureDialog;
import com.intellij.refactoring.changeClassSignature.TypeParameterInfo;
import com.intellij.util.IncorrectOperationException;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
@@ -123,26 +122,16 @@ public class ChangeClassSignatureFromUsageFix extends BaseIntentionAction {
else {
suggestedName = suggester.suggestUnusedName("T");
}
final PsiTypeCodeFragment boundFragment = createBoundCodeFragment(boundType, typeElement, factory);
final PsiTypeCodeFragment boundFragment = ChangeClassSignatureDialog.createTableCodeFragment(boundType, typeElement, factory, true);
result.add(new TypeParameterInfoView(new TypeParameterInfo.New(suggestedName, defaultType, null),
boundFragment,
boundType == null ? factory.createTypeCodeFragment(suggestedName, typeElement, true)
: createBoundCodeFragment(boundType, typeElement, factory)));
: ChangeClassSignatureDialog
.createTableCodeFragment(boundType, typeElement, factory, false)));
}
return result;
}
private static PsiTypeCodeFragment createBoundCodeFragment(@Nullable PsiClassType boundType,
@NotNull PsiElement context,
@NotNull JavaCodeFragmentFactory factory) {
final PsiTypeCodeFragment boundFragment =
factory.createTypeCodeFragment(boundType == null ? "" : boundType.getClassName(), context, true);
if (boundType != null) {
boundFragment.addImportsFromString(boundType.getCanonicalText());
}
return boundFragment;
}
private static boolean isAssignable(@NotNull PsiTypeParameter typeParameter, @NotNull PsiType type) {
for (PsiClassType t : typeParameter.getExtendsListTypes()) {
if (!t.isAssignableFrom(type)) {
@@ -207,7 +207,10 @@ public class PsiCodeFragmentImpl extends PsiFileImpl implements JavaCodeFragment
}
IElementType i = myContentElementType;
if (i == JavaElementType.TYPE_TEXT || i == JavaElementType.EXPRESSION_STATEMENT || i == JavaElementType.REFERENCE_TEXT) {
if (i == JavaElementType.TYPE_WITH_CONJUNCTIONS_TEXT ||
i == JavaElementType.TYPE_WITH_DISJUNCTIONS_TEXT ||
i == JavaElementType.EXPRESSION_STATEMENT ||
i == JavaElementType.REFERENCE_TEXT) {
return true;
}
else {
@@ -15,6 +15,7 @@
*/
package com.intellij.psi.impl.source;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.psi.*;
import com.intellij.psi.impl.source.tree.JavaElementType;
@@ -29,8 +30,11 @@ import static com.intellij.util.BitUtil.isSet;
* @author dsl
*/
public class PsiTypeCodeFragmentImpl extends PsiCodeFragmentImpl implements PsiTypeCodeFragment {
private final static Logger LOG = Logger.getInstance(PsiTypeCodeFragmentImpl.class);
private final boolean myAllowEllipsis;
private final boolean myAllowDisjunction;
private final boolean myAllowConjunction;
public PsiTypeCodeFragmentImpl(final Project project,
final boolean isPhysical,
@@ -38,10 +42,17 @@ public class PsiTypeCodeFragmentImpl extends PsiCodeFragmentImpl implements PsiT
final CharSequence text,
final int flags,
PsiElement context) {
super(project, JavaElementType.TYPE_TEXT, isPhysical, name, text, context);
super(project,
isSet(flags, JavaCodeFragmentFactory.ALLOW_INTERSECTION) ? JavaElementType.TYPE_WITH_CONJUNCTIONS_TEXT : JavaElementType.TYPE_WITH_DISJUNCTIONS_TEXT,
isPhysical,
name,
text,
context);
myAllowEllipsis = isSet(flags, JavaCodeFragmentFactory.ALLOW_ELLIPSIS);
myAllowDisjunction = isSet(flags, JavaCodeFragmentFactory.ALLOW_DISJUNCTION);
myAllowConjunction = isSet(flags, JavaCodeFragmentFactory.ALLOW_INTERSECTION);
LOG.assertTrue(!myAllowConjunction || !myAllowDisjunction);
if (isSet(flags, JavaCodeFragmentFactory.ALLOW_VOID)) {
putUserData(PsiUtil.VALID_VOID_TYPE_IN_CODE_FRAGMENT, Boolean.TRUE);
@@ -79,6 +90,9 @@ public class PsiTypeCodeFragmentImpl extends PsiCodeFragmentImpl implements PsiT
else if (type instanceof PsiDisjunctionType && !myAllowDisjunction) {
throw new TypeSyntaxException("Disjunction not allowed: " + type);
}
else if (type instanceof PsiDisjunctionType && !myAllowConjunction) {
throw new TypeSyntaxException("Conjunction not allowed: " + type);
}
return type;
}
@@ -20,6 +20,7 @@ import com.intellij.lang.findUsages.DescriptiveNameUtil;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.project.Project;
import com.intellij.psi.*;
import com.intellij.psi.util.PsiUtil;
import com.intellij.refactoring.HelpID;
import com.intellij.refactoring.RefactoringBundle;
import com.intellij.refactoring.ui.CodeFragmentTableCellRenderer;
@@ -109,11 +110,6 @@ public class ChangeClassSignatureDialog extends RefactoringDialog {
init();
}
private PsiTypeCodeFragment createValueCodeFragment() {
final JavaCodeFragmentFactory factory = JavaCodeFragmentFactory.getInstance(myProject);
return factory.createTypeCodeFragment("", myClass.getLBrace(), true);
}
protected JComponent createNorthPanel() {
return new JLabel(RefactoringBundle.message("changeClassSignature.class.label.text", DescriptiveNameUtil.getDescriptiveName(myClass)));
}
@@ -240,6 +236,16 @@ public class ChangeClassSignatureDialog extends RefactoringDialog {
return null;
}
public static PsiTypeCodeFragment createTableCodeFragment(@Nullable PsiClassType type,
@NotNull PsiElement context,
@NotNull JavaCodeFragmentFactory factory,
boolean allowConjunctions) {
return factory.createTypeCodeFragment(type == null ? "" : type.getCanonicalText(),
context,
true,
(allowConjunctions && PsiUtil.isLanguageLevel8OrHigher(context)) ? JavaCodeFragmentFactory.ALLOW_INTERSECTION : 0);
}
private class MyTableModel extends AbstractTableModel implements EditableModel {
public int getColumnCount() {
return 3;
@@ -301,8 +307,10 @@ public class ChangeClassSignatureDialog extends RefactoringDialog {
public void addRow() {
TableUtil.stopEditing(myTable);
myTypeParameterInfos.add(new TypeParameterInfo.New("", null, null));
myBoundValueTypeCodeFragments.add(createValueCodeFragment());
myDefaultValueTypeCodeFragments.add(createValueCodeFragment());
JavaCodeFragmentFactory codeFragmentFactory = JavaCodeFragmentFactory.getInstance(myProject);
PsiElement context = myClass.getLBrace() != null ? myClass.getLBrace() : myClass;
myBoundValueTypeCodeFragments.add(createTableCodeFragment(null, context, codeFragmentFactory, true));
myDefaultValueTypeCodeFragments.add(createTableCodeFragment(null, context, codeFragmentFactory, false));
final int row = myDefaultValueTypeCodeFragments.size() - 1;
fireTableRowsInserted(row, row);
}
@@ -185,7 +185,9 @@ public class ChangeClassSignatureProcessor extends BaseRefactoringProcessor {
for (final TypeParameterInfo info : myNewSignature) {
newTypeParameters.add(info.getTypeParameter(originalTypeParameters, myProject));
}
ChangeSignatureUtil.synchronizeList(myClass.getTypeParameterList(), newTypeParameters, TypeParameterList.INSTANCE, toRemoveParms);
final PsiTypeParameterList parameterList = myClass.getTypeParameterList();
ChangeSignatureUtil.synchronizeList(parameterList, newTypeParameters, TypeParameterList.INSTANCE, toRemoveParms);
JavaCodeStyleManager.getInstance(myProject).shortenClassReferences(parameterList);
}
private boolean[] detectRemovedParameters(final PsiTypeParameter[] original) {
@@ -22,6 +22,7 @@ import com.intellij.util.IncorrectOperationException;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.TestOnly;
/**
* @author dsl
@@ -42,6 +43,7 @@ public interface TypeParameterInfo {
myBoundValue = boundValue != null ? CanonicalTypes.createTypeWrapper(boundValue) : null;
}
@TestOnly
public New(@NotNull PsiClass aClass,
@NotNull @NonNls String name,
@NotNull @NonNls String defaultValue,
@@ -67,6 +67,10 @@ public abstract class JavaCodeFragmentFactory {
* Flag for {@linkplain #createTypeCodeFragment(String, PsiElement, boolean, int)} - allows disjunctive type.
*/
public static final int ALLOW_DISJUNCTION = 0x04;
/**
* Flag for {@linkplain #createTypeCodeFragment(String, PsiElement, boolean, int)} - allows conjunctive type.
*/
public static final int ALLOW_INTERSECTION = 0x08;
/**
* Creates a Java type code fragment from the text of the name of a Java type (the name
@@ -98,7 +102,7 @@ public abstract class JavaCodeFragmentFactory {
public abstract PsiTypeCodeFragment createTypeCodeFragment(@NotNull String text,
@Nullable PsiElement context,
boolean isPhysical,
@MagicConstant(flags = {ALLOW_VOID, ALLOW_ELLIPSIS, ALLOW_DISJUNCTION}) int flags);
@MagicConstant(flags = {ALLOW_VOID, ALLOW_ELLIPSIS, ALLOW_DISJUNCTION, ALLOW_INTERSECTION}) int flags);
/**
* Creates a Java reference code fragment from the text of a Java reference to a
@@ -127,8 +127,13 @@ public class PsiIntersectionType extends PsiType.Stub {
@NotNull
@Override
public String getCanonicalText(boolean annotated) {
return myConjuncts[0].getCanonicalText(annotated);
public String getCanonicalText(final boolean annotated) {
return StringUtil.join(myConjuncts, new Function<PsiType, String>() {
@Override
public String fun(PsiType psiType) {
return psiType.getCanonicalText(annotated);
}
}, " & ");
}
@NotNull
@@ -240,12 +240,24 @@ public interface JavaElementType {
}
};
IElementType TYPE_TEXT = new ICodeFragmentElementType("TYPE_TEXT", JavaLanguage.INSTANCE) {
IElementType TYPE_WITH_DISJUNCTIONS_TEXT = new TypeTextElementType("TYPE_WITH_DISJUNCTIONS_TEXT", ReferenceParser.DISJUNCTIONS);
IElementType TYPE_WITH_CONJUNCTIONS_TEXT = new TypeTextElementType("TYPE_WITH_CONJUNCTIONS_TEXT", ReferenceParser.CONJUNCTIONS);
class TypeTextElementType extends ICodeFragmentElementType {
private final int myFlags;
public TypeTextElementType(@NonNls String debugName, int flags) {
super(debugName, JavaLanguage.INSTANCE);
myFlags = flags;
}
private final JavaParserUtil.ParserWrapper myParser = new JavaParserUtil.ParserWrapper() {
@Override
public void parse(final PsiBuilder builder) {
JavaParser.INSTANCE.getReferenceParser().parseType(builder, ReferenceParser.EAT_LAST_DOT | ReferenceParser.ELLIPSIS |
ReferenceParser.WILDCARD | ReferenceParser.DISJUNCTIONS);
JavaParser.INSTANCE.getReferenceParser().parseType(builder, ReferenceParser.EAT_LAST_DOT |
ReferenceParser.ELLIPSIS |
ReferenceParser.WILDCARD |
myFlags);
}
};
@@ -254,7 +266,7 @@ public interface JavaElementType {
public ASTNode parseContents(final ASTNode chameleon) {
return JavaParserUtil.parseFragment(chameleon, myParser);
}
};
}
class JavaDummyElementType extends ILazyParseableElementType implements ICompositeElementType {
private JavaDummyElementType() {
@@ -246,11 +246,13 @@ public class CanonicalTypes {
}
}
private static class DisjunctionType extends Type {
private static class LogicalOperationType extends Type {
private final List<Type> myTypes;
private final boolean myDisjunction;
private DisjunctionType(List<Type> types) {
private LogicalOperationType(List<Type> types, boolean disjunction) {
myTypes = types;
myDisjunction = disjunction;
}
@NotNull
@@ -262,7 +264,7 @@ public class CanonicalTypes {
return type.getType(context, manager);
}
});
return new PsiDisjunctionType(types, manager);
return myDisjunction ? new PsiDisjunctionType(types, manager) : PsiIntersectionType.createIntersection(types);
}
@Override
@@ -272,7 +274,7 @@ public class CanonicalTypes {
public String fun(Type type) {
return type.getTypeText();
}
}, "|");
}, myDisjunction ? "|" : "&");
}
@Override
@@ -337,7 +339,19 @@ public class CanonicalTypes {
return type.accept(Creator.this);
}
});
return new DisjunctionType(types);
return new LogicalOperationType(types, true);
}
@Nullable
@Override
public Type visitIntersectionType(PsiIntersectionType type) {
List<Type> types = ContainerUtil.map(type.getConjuncts(), new Function<PsiType, Type>() {
@Override
public Type fun(PsiType type) {
return type.accept(Creator.this);
}
});
return new LogicalOperationType(types, false);
}
}
@@ -0,0 +1,9 @@
import java.io.Serializable;
class <caret>C {}
class Some implements Runnable, Serializable {
void m() {
C c = new C();
}
}
@@ -0,0 +1,9 @@
import java.io.Serializable;
class C<T extends Runnable & Serializable> {}
class Some implements Runnable, Serializable {
void m() {
C<Some> c = new C<Some>();
}
}
@@ -1,8 +1,9 @@
import java.util.Collection;
import java.util.List;
public class Main {
class B<T extends java.util.Collection> {}
class B<T extends Collection> {}
public void someMethod() {
B<List> b = new B<List>();
@@ -1,8 +1,9 @@
import java.util.Collection;
import java.util.List;
public class Main {
class B<T extends java.util.Collection, X extends Long> {}
class B<T extends Collection, X extends Long> {}
public void someMethod() {
B<List, Long> b = new B<>();
@@ -2,8 +2,7 @@ package com.intellij.refactoring.changeClassSignature;
import com.intellij.JavaTestUtil;
import com.intellij.codeInsight.TargetElementUtil;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiElement;
import com.intellij.psi.*;
import com.intellij.refactoring.LightRefactoringTestCase;
import com.intellij.util.IncorrectOperationException;
import org.jetbrains.annotations.NonNls;
@@ -100,6 +99,19 @@ public class ChangeClassSignatureTest extends LightRefactoringTestCase {
});
}
public void testAddBoundWithIntersection() throws Exception {
doTest(aClass -> {
final PsiElementFactory factory = JavaPsiFacade.getElementFactory(aClass.getProject());
final PsiFile context = aClass.getContainingFile();
return new TypeParameterInfo[]{
new TypeParameterInfo.New("T",
factory.createTypeFromText("Some", context),
PsiIntersectionType.createIntersection(factory.createTypeFromText("java.lang.Runnable", context),
factory.createTypeFromText("java.io.Serializable", context)))
};
});
}
private void doTest(Function<PsiClass, TypeParameterInfo[]> gen) throws Exception {
@NonNls final String filePathBefore = getTestName(false) + ".java";
@NonNls final String filePathAfter = getTestName(false) + ".java.after";