package com.redhat.ceylon.eclipse.code.correct; import static com.redhat.ceylon.eclipse.code.correct.CorrectionUtil.asIntersectionTypeString; import static com.redhat.ceylon.eclipse.code.correct.ImportProposals.importProposals; import static com.redhat.ceylon.eclipse.java2ceylon.Java2CeylonProxies.utilJ2C; import static com.redhat.ceylon.eclipse.ui.CeylonResources.ADD_CORR; import static com.redhat.ceylon.eclipse.util.EditorUtil.getDocument; import static com.redhat.ceylon.eclipse.util.Nodes.findDeclaration; import static com.redhat.ceylon.eclipse.util.Nodes.findDeclarationWithBody; import static com.redhat.ceylon.eclipse.util.Nodes.getContainer; import static com.redhat.ceylon.eclipse.util.Nodes.getIdentifyingNode; import java.util.Collection; import java.util.HashSet; import java.util.List; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IFolder; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.IRegion; import org.eclipse.jface.text.Region; import org.eclipse.jface.text.contentassist.ICompletionProposal; import org.eclipse.ltk.core.refactoring.TextFileChange; import org.eclipse.swt.graphics.Image; import org.eclipse.text.edits.InsertEdit; import org.eclipse.text.edits.MultiTextEdit; import com.redhat.ceylon.compiler.typechecker.tree.Tree; import com.redhat.ceylon.compiler.typechecker.tree.Tree.CompilationUnit; import com.redhat.ceylon.compiler.typechecker.tree.Visitor; import com.redhat.ceylon.eclipse.platform.platformJ2C; import com.redhat.ceylon.ide.common.model.ModifiableSourceFile; import com.redhat.ceylon.ide.common.platform.TextChange; import com.redhat.ceylon.ide.common.typechecker.ModifiablePhasedUnit; import com.redhat.ceylon.model.typechecker.model.ClassOrInterface; import com.redhat.ceylon.model.typechecker.model.Declaration; import com.redhat.ceylon.model.typechecker.model.Function; import com.redhat.ceylon.model.typechecker.model.Generic; import com.redhat.ceylon.model.typechecker.model.Type; import com.redhat.ceylon.model.typechecker.model.TypeDeclaration; import com.redhat.ceylon.model.typechecker.model.TypeParameter; import com.redhat.ceylon.model.typechecker.model.Unit; class CreateTypeParameterProposal extends CorrectionProposal { CreateTypeParameterProposal(String desc, Image image, int offset, int length, TextFileChange change) { super(desc, change, new Region(offset, length), image); } private static void addProposal( Collection<ICompletionProposal> proposals, boolean wasNotGeneric, String def, String name, Image image, Declaration dec, ModifiablePhasedUnit<IProject,IResource,IFolder,IFile> unit, Tree.Declaration decNode, int offset, String constraints) { IFile file = unit.getResourceFile(); if (file == null) { return; } TextFileChange change = new TextFileChange("Add Type Parameter", file); change.setEdit(new MultiTextEdit()); IDocument doc = getDocument(change); HashSet<Declaration> decs = new HashSet<Declaration>(); CompilationUnit cu = unit.getCompilationUnit(); TextChange chg2 = new platformJ2C().newChange(change.getName(), change); int il = (int) importProposals() .applyImports(chg2, decs, cu, chg2.getDocument()); change.addEdit(new InsertEdit(offset, def)); if (constraints!=null) { int loc = getConstraintLoc(decNode); if (loc>=0) { String text = constraints; try { IRegion li = doc.getLineInformationOfOffset(loc); int start = li.getOffset(); String string = doc.get(start, loc-start); if (!string.trim().isEmpty()) { text = utilJ2C().indents().getDefaultLineDelimiter(doc) + utilJ2C().indents().getIndent(decNode, doc) + utilJ2C().indents().getDefaultIndent() + utilJ2C().indents().getDefaultIndent() + constraints; } } catch (BadLocationException e) { e.printStackTrace(); } change.addEdit(new InsertEdit(loc, text)); } } String desc = "Add type parameter '" + name + "'" + " to '" + dec.getName() + "'"; int off = wasNotGeneric?1:2; proposals.add(new CreateTypeParameterProposal(desc, image, offset+il+off, name.length(), change)); } private static int getConstraintLoc(Tree.Declaration decNode) { if( decNode instanceof Tree.ClassDefinition ) { Tree.ClassDefinition classDefinition = (Tree.ClassDefinition) decNode; return classDefinition.getClassBody() .getStartIndex(); } else if( decNode instanceof Tree.InterfaceDefinition ) { Tree.InterfaceDefinition interfaceDefinition = (Tree.InterfaceDefinition) decNode; return interfaceDefinition.getInterfaceBody() .getStartIndex(); } else if( decNode instanceof Tree.MethodDefinition ) { Tree.MethodDefinition methodDefinition = (Tree.MethodDefinition) decNode; return methodDefinition.getBlock() .getStartIndex(); } else if( decNode instanceof Tree.ClassDeclaration ) { Tree.ClassDeclaration classDefinition = (Tree.ClassDeclaration) decNode; Tree.ClassSpecifier s = classDefinition.getClassSpecifier(); return s==null ? decNode.getEndIndex() : s.getStartIndex(); } else if( decNode instanceof Tree.InterfaceDeclaration ) { Tree.InterfaceDeclaration interfaceDefinition = (Tree.InterfaceDeclaration) decNode; Tree.TypeSpecifier s = interfaceDefinition.getTypeSpecifier(); return s==null ? decNode.getEndIndex() : s.getStartIndex(); } else if( decNode instanceof Tree.MethodDeclaration ) { Tree.MethodDeclaration methodDefinition = (Tree.MethodDeclaration) decNode; Tree.SpecifierExpression s = methodDefinition.getSpecifierExpression(); return s==null ? decNode.getEndIndex() : s.getStartIndex(); } else { return -1; } } static void addCreateTypeParameterProposal( Collection<ICompletionProposal> proposals, Tree.CompilationUnit rootNode, final Tree.BaseType type, String brokenName) { if (type.getTypeArgumentList()!=null) { return; } class FilterExtendsSatisfiesVisitor extends Visitor { boolean filter = false; @Override public void visit(Tree.ExtendedType that) { super.visit(that); if (that.getType()==type) { filter = true; } } @Override public void visit(Tree.SatisfiedTypes that) { super.visit(that); for (Tree.Type t: that.getTypes()) { if (t==type) { filter = true; } } } @Override public void visit(Tree.CaseTypes that) { super.visit(that); for (Tree.Type t: that.getTypes()) { if (t==type) { filter = true; } } } } FilterExtendsSatisfiesVisitor v = new FilterExtendsSatisfiesVisitor(); v.visit(rootNode); if (v.filter) { return; } Tree.Declaration decl = findDeclarationWithBody(rootNode, type); if (decl==null) { decl = findDeclaration(rootNode, type); if (!(decl instanceof Tree.AnyMethod || decl instanceof Tree.ClassOrInterface)) { decl = getContainer(rootNode, decl.getDeclarationModel()); } } Declaration d = decl==null ? null : decl.getDeclarationModel(); if (d == null || d.isActual() || !(d instanceof Function || d instanceof ClassOrInterface)) { return; } Tree.TypeParameterList paramList = getTypeParameters(decl); String paramDef; int offset; //TODO: add bounds as default type arg? if (paramList != null) { paramDef = ", " + brokenName; offset = paramList.getEndIndex()-1; } else { paramDef = "<" + brokenName + ">"; offset = getIdentifyingNode(decl).getEndIndex(); } class FindTypeParameterConstraintVisitor extends Visitor { List<Type> result; @Override public void visit(Tree.SimpleType that) { super.visit(that); TypeDeclaration dm = that.getDeclarationModel(); if (dm!=null) { List<TypeParameter> tps = dm.getTypeParameters(); Tree.TypeArgumentList tal = that.getTypeArgumentList(); if (tal!=null) { List<Tree.Type> tas = tal.getTypes(); for (int i=0; i<tas.size(); i++) { if (tas.get(i)==type) { result = tps.get(i).getSatisfiedTypes(); } } } } } @Override public void visit(Tree.StaticMemberOrTypeExpression that) { super.visit(that); Declaration d = that.getDeclaration(); if (d instanceof Generic) { Generic g = (Generic) d; List<TypeParameter> tps = g.getTypeParameters(); Tree.TypeArguments tas = that.getTypeArguments(); if (tas instanceof Tree.TypeArgumentList) { Tree.TypeArgumentList tal = (Tree.TypeArgumentList) tas; List<Tree.Type> ts = tal.getTypes(); for (int i=0; i<ts.size(); i++) { if (ts.get(i)==type) { result = tps.get(i) .getSatisfiedTypes(); } } } } } } FindTypeParameterConstraintVisitor ftpcv = new FindTypeParameterConstraintVisitor(); ftpcv.visit(rootNode); String constraints; if (ftpcv.result==null) { constraints = null; } else { String bounds = asIntersectionTypeString(ftpcv.result); if (bounds.isEmpty()) { constraints = null; } else { constraints = "given " + brokenName + " satisfies " + bounds + " "; } } Unit u = rootNode.getUnit(); if (u instanceof ModifiableSourceFile) { ModifiableSourceFile msf = (ModifiableSourceFile) u; addProposal(proposals, paramList==null, paramDef, brokenName, ADD_CORR, d, msf.getPhasedUnit(), decl, offset, constraints); } } private static Tree.TypeParameterList getTypeParameters( Tree.Declaration decl) { if (decl instanceof Tree.ClassOrInterface) { Tree.ClassOrInterface ci = (Tree.ClassOrInterface) decl; return ci.getTypeParameterList(); } else if (decl instanceof Tree.AnyMethod) { Tree.AnyMethod am = (Tree.AnyMethod) decl; return am.getTypeParameterList(); } return null; } }