package com.redhat.ceylon.eclipse.code.correct;
import org.eclipse.jface.text.Region;
import org.eclipse.ltk.core.refactoring.Change;
import com.redhat.ceylon.model.typechecker.model.TypeDeclaration;
/**
* Add generic type constraints proposal for following code:
* <pre>
* interface Foo<T> given T satisfies Comparable<T> {}
*
* class Bar<T>() satisfies Foo<T> {}
*
* ------
*
* void foo<T>(T t) {
* Bar b = t;
* }
*
* ------
*
* void foo<T>(T t) {
* Entry<Integer, T> e;
* }
* </pre>
*/
public class AddSatisfiesProposal extends CorrectionProposal {
/*
@Deprecated
public static void addSatisfiesProposals(
Tree.CompilationUnit rootNode, Node node,
Collection<ICompletionProposal> proposals,
IProject project) {
node = determineNode(node);
if (node == null) {
return;
}
TypeDeclaration typeDec =
determineTypeDeclaration(node);
if (typeDec == null) {
return;
}
boolean isTypeParam =
typeDec instanceof TypeParameter;
List<Type> missingSatisfiedTypes =
determineMissingSatisfiedTypes(rootNode,
node, typeDec);
if (!isTypeParam) {
for (Iterator<Type> it =
missingSatisfiedTypes.iterator();
it.hasNext();) {
if (!it.next().isInterface()) {
it.remove();
}
//TODO: add extends clause if the type is a
// Class which extends Basic or Object
}
}
if (missingSatisfiedTypes.isEmpty()) {
return;
}
String changeText =
asIntersectionTypeString(missingSatisfiedTypes);
Unit unit = typeDec.getUnit();
if (unit instanceof ModifiableSourceFile) {
ModifiableSourceFile msf =
(ModifiableSourceFile) unit;
ModifiablePhasedUnit<IProject,IResource,IFolder,IFile> phasedUnit =
msf.getPhasedUnit();
Tree.CompilationUnit decRootNode =
phasedUnit.getCompilationUnit();
Node declaration =
determineContainer(decRootNode, typeDec);
if (declaration!=null) {
IFile file = phasedUnit.getResourceFile();
if (file != null) {
createProposals(proposals, typeDec,
isTypeParam, changeText, file,
declaration,
node.getUnit().equals(unit));
}
}
}
}
@Deprecated
private static void createProposals(
Collection<ICompletionProposal> proposals,
TypeDeclaration typeDec, boolean isTypeParam,
String changeText, IFile file, Node declaration,
boolean sameFile) {
if (isTypeParam) {
if (declaration instanceof Tree.ClassDefinition) {
Tree.ClassDefinition classDefinition =
(Tree.ClassDefinition) declaration;
addConstraintSatisfiesProposals(typeDec, changeText,
file, proposals,
classDefinition.getTypeConstraintList(),
classDefinition.getClassBody().getStartIndex(),
sameFile);
}
else if (declaration instanceof Tree.InterfaceDefinition) {
Tree.InterfaceDefinition interfaceDefinition =
(Tree.InterfaceDefinition) declaration;
addConstraintSatisfiesProposals(typeDec, changeText,
file, proposals,
interfaceDefinition.getTypeConstraintList(),
interfaceDefinition.getInterfaceBody().getStartIndex(),
sameFile);
}
else if (declaration instanceof Tree.MethodDefinition) {
Tree.MethodDefinition methodDefinition =
(Tree.MethodDefinition) declaration;
addConstraintSatisfiesProposals(typeDec, changeText,
file, proposals,
methodDefinition.getTypeConstraintList(),
methodDefinition.getBlock().getStartIndex(),
sameFile);
}
else if (declaration instanceof Tree.ClassDeclaration) {
Tree.ClassDeclaration classDefinition =
(Tree.ClassDeclaration) declaration;
addConstraintSatisfiesProposals(typeDec, changeText,
file, proposals,
classDefinition.getTypeConstraintList(),
classDefinition.getClassSpecifier().getStartIndex(),
sameFile);
}
else if (declaration instanceof Tree.InterfaceDefinition) {
Tree.InterfaceDeclaration interfaceDefinition =
(Tree.InterfaceDeclaration) declaration;
addConstraintSatisfiesProposals(typeDec, changeText,
file, proposals,
interfaceDefinition.getTypeConstraintList(),
interfaceDefinition.getTypeSpecifier().getStartIndex(),
sameFile);
}
else if (declaration instanceof Tree.MethodDeclaration) {
Tree.MethodDeclaration methodDefinition =
(Tree.MethodDeclaration) declaration;
addConstraintSatisfiesProposals(typeDec, changeText,
file, proposals,
methodDefinition.getTypeConstraintList(),
methodDefinition.getSpecifierExpression().getStartIndex(),
sameFile);
}
}
else {
if (declaration instanceof Tree.ClassDefinition) {
Tree.ClassDefinition classDefinition =
(Tree.ClassDefinition) declaration;
addSatisfiesProposals(typeDec, changeText, file, proposals,
classDefinition.getSatisfiedTypes(),
classDefinition.getTypeConstraintList()==null ?
classDefinition.getClassBody().getStartIndex() :
classDefinition.getTypeConstraintList().getStartIndex(),
sameFile);
}
else if (declaration instanceof Tree.ObjectDefinition) {
Tree.ObjectDefinition objectDefinition =
(Tree.ObjectDefinition) declaration;
addSatisfiesProposals(typeDec, changeText, file, proposals,
objectDefinition.getSatisfiedTypes(),
objectDefinition.getClassBody().getStartIndex(),
sameFile);
}
else if (declaration instanceof Tree.InterfaceDefinition) {
Tree.InterfaceDefinition interfaceDefinition =
(Tree.InterfaceDefinition) declaration;
addSatisfiesProposals(typeDec, changeText, file, proposals,
interfaceDefinition.getSatisfiedTypes(),
interfaceDefinition.getTypeConstraintList()==null ?
interfaceDefinition.getInterfaceBody().getStartIndex() :
interfaceDefinition.getTypeConstraintList().getStartIndex(),
sameFile);
}
}
}
@Deprecated
private static void addConstraintSatisfiesProposals(
TypeDeclaration typeParam,
String missingSatisfiedType, IFile file,
Collection<ICompletionProposal> proposals,
TypeConstraintList typeConstraints,
Integer typeContainerBodyStartIndex,
boolean sameFile) {
String changeText = null;
Integer changeIndex = null;
if (typeConstraints != null) {
for (TypeConstraint typeConstraint:
typeConstraints.getTypeConstraints()) {
if (typeConstraint.getDeclarationModel()
.equals(typeParam)) {
changeText = " & " + missingSatisfiedType;
changeIndex = typeConstraint.getEndIndex();
break;
}
}
}
if (changeText == null) {
changeText =
"given "+ typeParam.getName() +
" satisfies " + missingSatisfiedType + " ";
changeIndex = typeContainerBodyStartIndex;
}
if (changeText != null) {
TextFileChange tfc =
new TextFileChange(
"Add Type Constraint", file);
tfc.setEdit(new InsertEdit(changeIndex, changeText));
String desc =
"Add generic type constraint '" + typeParam.getName() +
" satisfies " + missingSatisfiedType + "'";
Region region = sameFile ?
new Region(changeIndex, changeText.length()) : null;
CompositeChange change =
new CompositeChange(
"Add Type Constraint");
change.add(tfc);
AddSatisfiesProposal proposal =
new AddSatisfiesProposal(
typeParam, desc,
missingSatisfiedType,
change, region);
if ( !proposals.contains(proposal)) {
proposals.add(proposal);
}
}
}
@Deprecated
private static void addSatisfiesProposals(
TypeDeclaration typeParam,
String missingSatisfiedType, IFile file,
Collection<ICompletionProposal> proposals,
Tree.SatisfiedTypes typeConstraints,
Integer typeContainerBodyStartIndex,
boolean sameFile) {
String changeText = null;
Integer changeIndex = null;
if (typeConstraints != null) {
changeText = " & " + missingSatisfiedType;
changeIndex = typeConstraints.getEndIndex();
}
else if (changeText == null) {
changeText = "satisfies " + missingSatisfiedType + " ";
changeIndex = typeContainerBodyStartIndex;
}
if (changeText != null) {
TextFileChange tfc =
new TextFileChange(
"Add Inherited Interface", file);
tfc.setEdit(new InsertEdit(changeIndex, changeText));
String desc =
"Add inherited interface '" + typeParam.getName() +
" satisfies " + missingSatisfiedType + "'";
Region region = sameFile ?
new Region(changeIndex, changeText.length()) : null;
CompositeChange change =
new CompositeChange(
"Add Inherited Interface");
change.add(tfc);
AddSatisfiesProposal proposal =
new AddSatisfiesProposal(
typeParam, desc,
missingSatisfiedType,
change, region);
if (!proposals.contains(proposal)) {
proposals.add(proposal);
}
}
}
@Deprecated
private static Node determineNode(Node node) {
if (node instanceof Tree.SpecifierExpression) {
Tree.SpecifierExpression specifierExpression =
(Tree.SpecifierExpression) node;
node = specifierExpression.getExpression();
}
if (node instanceof Tree.Expression) {
Tree.Expression expression =
(Tree.Expression) node;
node = expression.getTerm();
}
return node;
}
@Deprecated
private static TypeDeclaration determineTypeDeclaration(Node node) {
TypeDeclaration typeDec = null;
if (node instanceof Tree.ClassOrInterface ||
node instanceof Tree.TypeParameterDeclaration) {
Tree.Declaration d = (Tree.Declaration) node;
Declaration declaration =
d.getDeclarationModel();
if (declaration instanceof ClassOrInterface) {
typeDec = (TypeDeclaration) declaration;
}
}
else if (node instanceof Tree.ObjectDefinition) {
Tree.ObjectDefinition od =
(Tree.ObjectDefinition) node;
Value val = od.getDeclarationModel();
return val.getType().getDeclaration();
}
else if (node instanceof Tree.BaseType) {
Tree.BaseType bt = (Tree.BaseType) node;
TypeDeclaration baseTypeDecl =
bt.getDeclarationModel();
if (baseTypeDecl instanceof TypeDeclaration) {
typeDec = baseTypeDecl;
}
}
else if (node instanceof Tree.Term) {
Tree.Term t = (Tree.Term) node;
Type type = t.getTypeModel();
if (type != null) {
typeDec = type.getDeclaration();
}
}
return typeDec;
}
@Deprecated
private static Node determineContainer(
Tree.CompilationUnit rootNode,
final TypeDeclaration typeDec) {
FindDeclarationNodeVisitor fdv =
new FindDeclarationNodeVisitor(typeDec) {
@Override
public void visit(Tree.ObjectDefinition that) {
if (that.getDeclarationModel().getType()
.getDeclaration().equals(typeDec)) {
setDeclarationNode(that);
}
super.visit(that);
}
};
fdv.visit(rootNode);
Tree.Declaration dec =
(Tree.Declaration) fdv.getDeclarationNode();
if (dec != null) {
FindContainerVisitor fcv =
new FindContainerVisitor(dec);
fcv.visit(rootNode);
return fcv.getStatementOrArgument();
}
return null;
}
@Deprecated
private static List<Type> determineMissingSatisfiedTypes(
Tree.CompilationUnit rootNode, Node node,
TypeDeclaration typeDec) {
List<Type> missingSatisfiedTypes =
new ArrayList<Type>();
if (node instanceof Tree.Term) {
FindInvocationVisitor fav =
new FindInvocationVisitor(node);
fav.visit(rootNode);
if (fav.parameter != null) {
Type type = fav.parameter.getType();
if (type!=null && type.getDeclaration()!=null) {
if (type.isClassOrInterface()) {
missingSatisfiedTypes.add(type);
}
else if (type.isIntersection()) {
for (Type it: type.getSatisfiedTypes()) {
if (!typeDec.inherits(it.getDeclaration())) {
missingSatisfiedTypes.add(it);
}
}
}
}
}
}
else {
List<TypeParameter> stTypeParams =
determineSatisfiedTypesTypeParams(
rootNode, node, typeDec);
if (!stTypeParams.isEmpty()) {
Type typeParamType = typeDec.getType();
Map<TypeParameter, Type> substitutions =
new HashMap<TypeParameter, Type>();
for (TypeParameter stTypeParam : stTypeParams) {
substitutions.put(stTypeParam, typeParamType);
}
for (TypeParameter stTypeParam : stTypeParams) {
for (Type stTypeParamSatisfiedType:
stTypeParam.getSatisfiedTypes()) {
stTypeParamSatisfiedType =
stTypeParamSatisfiedType.substitute(
substitutions, null);
boolean isMissing = true;
for (Type typeParamSatisfiedType:
typeDec.getSatisfiedTypes()) {
if (stTypeParamSatisfiedType.isSupertypeOf(
typeParamSatisfiedType)) {
isMissing = false;
break;
}
}
if (isMissing) {
for(Type missingSatisfiedType:
missingSatisfiedTypes) {
if( missingSatisfiedType.isExactly(
stTypeParamSatisfiedType) ) {
isMissing = false;
break;
}
}
}
if (isMissing) {
missingSatisfiedTypes.add(
stTypeParamSatisfiedType);
}
}
}
}
}
return missingSatisfiedTypes;
}
@Deprecated
private static List<TypeParameter> determineSatisfiedTypesTypeParams(
Tree.CompilationUnit rootNode, final Node typeParamNode,
final TypeDeclaration typeDec) {
final List<TypeParameter> stTypeParams =
new ArrayList<TypeParameter>();
new Visitor() {
private void determineSatisfiedTypesTypeParams(
TypeDeclaration typeParam,
Declaration stDecl,
Tree.TypeArguments args,
List<TypeParameter> stTypeParams,
Node typeParamNode) {
if (args instanceof Tree.TypeArgumentList) {
Tree.TypeArgumentList tal =
(Tree.TypeArgumentList) args;
List<Tree.Type> stTypeArguments = tal.getTypes();
if (stTypeArguments.contains(typeParamNode) &&
stDecl instanceof Generic) {
for (int i=0; i<stTypeArguments.size(); i++) {
Tree.Type type = stTypeArguments.get(i);
if (type instanceof Tree.SimpleType) {
Tree.SimpleType st =
(Tree.SimpleType) type;
TypeDeclaration td =
st.getDeclarationModel();
if (td!=null &&
typeParam.equals(td)) {
Generic g = (Generic) stDecl;
List<TypeParameter> typeParameters =
g.getTypeParameters();
if (typeParameters!=null &&
typeParameters.size()>i) {
stTypeParams.add(typeParameters.get(i));
}
}
}
}
}
}
}
@Override
public void visit(Tree.SimpleType that) {
super.visit(that);
determineSatisfiedTypesTypeParams(typeDec,
that.getDeclarationModel(),
that.getTypeArgumentList(),
stTypeParams, typeParamNode);
}
@Override
public void visit(Tree.StaticMemberOrTypeExpression that) {
super.visit(that);
determineSatisfiedTypesTypeParams(typeDec,
that.getDeclaration(),
that.getTypeArguments(),
stTypeParams, typeParamNode);
}
}.visit(rootNode);
// if (soa instanceof Tree.ClassOrInterface) {
// Tree.ClassOrInterface coi = (Tree.ClassOrInterface) soa;
// if (coi.getSatisfiedTypes() != null) {
// for (Tree.StaticType st: coi.getSatisfiedTypes().getTypes()) {
// // FIXME: gavin this needs checking
// if (st instanceof Tree.SimpleType) {
// }
// }
// }
// }
// else if (soa instanceof Tree.AttributeDeclaration) {
// Tree.AttributeDeclaration ad = (Tree.AttributeDeclaration) soa;
// Tree.Type at = ad.getType();
// if (at instanceof Tree.SimpleType) {
// determineSatisfiedTypesTypeParams(typeDec,
// (Tree.SimpleType) at, stTypeParams);
// }
// }
return stTypeParams;
}*/
private final TypeDeclaration typeParam;
private final String missingSatisfiedTypeText;
AddSatisfiesProposal(TypeDeclaration typeParam,
String description,
String missingSatisfiedTypeText,
Change change, Region selection) {
super(description, change, selection);
this.typeParam = typeParam;
this.missingSatisfiedTypeText = missingSatisfiedTypeText;
}
@Override
public boolean equals(Object obj) {
if (obj instanceof AddSatisfiesProposal) {
AddSatisfiesProposal that =
(AddSatisfiesProposal) obj;
return that.typeParam.equals(typeParam) &&
that.missingSatisfiedTypeText
.equals(missingSatisfiedTypeText);
}
return false;
}
@Override
public int hashCode() {
return typeParam.hashCode() +
missingSatisfiedTypeText.hashCode();
}
}