package com.redhat.ceylon.eclipse.code.refactor; import static com.redhat.ceylon.eclipse.java2ceylon.Java2CeylonProxies.utilJ2C; import static com.redhat.ceylon.eclipse.util.EditorUtil.getDocument; import static com.redhat.ceylon.eclipse.util.Nodes.getContainer; import static com.redhat.ceylon.eclipse.util.Nodes.text; import java.util.List; import org.antlr.runtime.CommonToken; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.OperationCanceledException; import org.eclipse.jface.text.IDocument; import org.eclipse.ltk.core.refactoring.CompositeChange; import org.eclipse.ltk.core.refactoring.RefactoringStatus; import org.eclipse.ltk.core.refactoring.TextChange; import org.eclipse.text.edits.DeleteEdit; import org.eclipse.text.edits.InsertEdit; import org.eclipse.text.edits.MultiTextEdit; import org.eclipse.text.edits.ReplaceEdit; import org.eclipse.ui.IEditorPart; import com.redhat.ceylon.compiler.typechecker.tree.Node; import com.redhat.ceylon.compiler.typechecker.tree.Tree; import com.redhat.ceylon.compiler.typechecker.tree.Visitor; import com.redhat.ceylon.model.typechecker.model.Class; import com.redhat.ceylon.model.typechecker.model.Declaration; import com.redhat.ceylon.model.typechecker.model.Function; import com.redhat.ceylon.model.typechecker.model.FunctionOrValue; import com.redhat.ceylon.model.typechecker.model.Interface; import com.redhat.ceylon.model.typechecker.model.Parameter; import com.redhat.ceylon.model.typechecker.model.TypeDeclaration; import com.redhat.ceylon.model.typechecker.model.Value; public class MakeReceiverRefactoring extends AbstractRefactoring { private boolean leaveDelegate = false; private final class MoveVisitor extends Visitor { private final TypeDeclaration newOwner; private final IDocument doc; private final Tree.Term defaultArg; private final Tree.Declaration fun; private final Declaration parameter; private final TextChange tfc; private List<CommonToken> localTokens; private MoveVisitor(TypeDeclaration newOwner, Declaration parameter, Tree.Declaration fun, Tree.Term defaultArg, TextChange tfc, List<CommonToken> tokens) { this.newOwner = newOwner; this.doc = getDocument(tfc); this.parameter = parameter; this.fun = fun; this.defaultArg = defaultArg; this.tfc = tfc; localTokens = tokens; } private String getDefinition() { final StringBuilder def = new StringBuilder(text(fun, tokens)); new Visitor() { int offset=0; public void visit(Tree.Declaration that) { if (that.getDeclarationModel() .equals(parameter)) { int len = node.getDistance(); int start = node.getStartIndex() - fun.getStartIndex() + offset; def.replace(start, start+len, ""); offset-=len; boolean deleted=false; for (int i=start-1; i>=0; i--) { if (!Character.isWhitespace(def.charAt(i))) { if (def.charAt(i)==',') { def.delete(i, start); deleted = true; offset-=start-i; } break; } } if (!deleted) { boolean found=false; for (int i=start; i<def.length(); i++) { if (!Character.isWhitespace(def.charAt(i))) { if (!found && def.charAt(i)==',') { found = true; } else { def.delete(start, i); deleted = true; offset-=i-start; break; } } } } } super.visit(that); } public void visit( Tree.BaseMemberOrTypeExpression that) { if (that.getDeclaration() .equals(parameter)) { int len = that.getDistance(); int start = that.getStartIndex() - fun.getStartIndex() + offset; String outerRef = fun.getDeclarationModel() instanceof Class ? "outer" : "this"; def.replace(start, start+len, outerRef); offset += outerRef.length()-len; } super.visit(that); } }.visit(fun); if (!fun.getDeclarationModel().isShared()) { def.insert(0, "shared "); } return def.toString(); } private void insert(Tree.Body body, Tree.Declaration that) { String delim = utilJ2C().indents() .getDefaultLineDelimiter(document); String originalIndent = delim + utilJ2C().indents() .getIndent(fun, document); String text; List<Tree.Statement> sts = body.getStatements(); int loc; if (sts.isEmpty()) { String outerIndent = delim + utilJ2C().indents() .getIndent(that, doc); String newIndent = outerIndent + utilJ2C().indents() .getDefaultIndent(); String def = getDefinition() .replaceAll(originalIndent, newIndent); text = newIndent + def + outerIndent; loc = body.getEndIndex()-1; } else { Tree.Statement st = sts.get(sts.size()-1); String newIndent = delim + utilJ2C().indents() .getIndent(st, doc); String def = getDefinition() .replaceAll(originalIndent, newIndent); text = newIndent + def; loc = st.getEndIndex(); } tfc.addEdit(new InsertEdit(loc, text)); } @Override public void visit(Tree.ClassDefinition that) { super.visit(that); if (that.getDeclarationModel() .equals(newOwner)) { insert(that.getClassBody(), that); } } @Override public void visit(Tree.InterfaceDefinition that) { super.visit(that); if (that.getDeclarationModel() .equals(newOwner)) { insert(that.getInterfaceBody(), that); } } @Override public void visit(Tree.SimpleType that) { super.visit(that); TypeDeclaration d = that.getDeclarationModel(); if (d!=null && d.equals(fun.getDeclarationModel())) { tfc.addEdit(new InsertEdit( that.getIdentifier().getStartIndex(), newOwner.getName(that.getUnit()) + ".")); } } @Override public void visit(Tree.InvocationExpression that) { super.visit(that); if (leaveDelegate) return; Tree.Primary p = that.getPrimary(); if (p instanceof Tree.BaseMemberOrTypeExpression) { Tree.BaseMemberOrTypeExpression bmte = (Tree.BaseMemberOrTypeExpression) p; Declaration d = bmte.getDeclaration(); if (d!=null && d.equals(fun.getDeclarationModel())) { Tree.PositionalArgumentList pal = that.getPositionalArgumentList(); Tree.NamedArgumentList nal = that.getNamedArgumentList(); if (pal!=null) { List<Tree.PositionalArgument> pas = pal.getPositionalArguments(); for (int i=0; i<pas.size(); i++) { Tree.PositionalArgument arg = pas.get(i); if (arg.getParameter() .getModel() .equals(parameter)) { tfc.addEdit(new InsertEdit( p.getStartIndex(), text(arg, localTokens) + ".")); int start, stop; if (i>0) { start = pas.get(i-1).getEndIndex(); stop = arg.getEndIndex(); } else if (i<pas.size()-1) { start = arg.getStartIndex(); stop = pas.get(i+1).getStartIndex(); } else { start = arg.getStartIndex(); stop = arg.getEndIndex(); } tfc.addEdit(new DeleteEdit(start, stop-start)); return; //NOTE: early exit!!! } } if (defaultArg!=null) { tfc.addEdit(new InsertEdit( p.getStartIndex(), text(defaultArg, tokens) + ".")); } } if (nal!=null) { List<Tree.NamedArgument> nas = nal.getNamedArguments(); for (int i=0; i<nas.size(); i++) { Tree.NamedArgument arg = nas.get(i); Parameter param = arg.getParameter(); if (param!=null && param.getModel() .equals(parameter)) { if (arg instanceof Tree.SpecifiedArgument) { Tree.SpecifiedArgument sa = (Tree.SpecifiedArgument) arg; Tree.Expression e = sa.getSpecifierExpression() .getExpression(); tfc.addEdit(new InsertEdit( p.getStartIndex(), text(e, localTokens) + ".")); } else { String name = arg.getIdentifier() .getText(); tfc.addEdit(new InsertEdit( p.getStartIndex(), text(arg, localTokens) + utilJ2C().indents().getDefaultLineDelimiter(doc) + utilJ2C().indents().getIndent(that, doc) + name + ".")); } int start, stop; if (i>0) { start = nas.get(i-1).getEndIndex(); stop = arg.getEndIndex(); } else if (i<nas.size()-1) { start = arg.getStartIndex(); stop = nas.get(i+1).getStartIndex(); } else { start = arg.getStartIndex(); stop = arg.getEndIndex(); } tfc.addEdit(new DeleteEdit(start, stop-start)); return; //NOTE: early exit!! } } if (defaultArg!=null) { tfc.addEdit(new InsertEdit( p.getStartIndex(), text(defaultArg, tokens) + ".")); } } } } } } public MakeReceiverRefactoring(IEditorPart editor) { super(editor); } @Override public boolean getEnabled() { if (node instanceof Tree.AttributeDeclaration && project != null) { Tree.AttributeDeclaration ad = (Tree.AttributeDeclaration) node; Value param = ad.getDeclarationModel(); if (param!=null && param.isParameter() && param.getInitializerParameter() .getDeclaration() .isToplevel()) { TypeDeclaration target = param.getTypeDeclaration(); return target!=null && inSameProject(target) && (target instanceof Class || target instanceof Interface); } } return false; } public String getName() { return "Make Receiver"; } @Override protected boolean isAffectingOtherFiles() { return true; //TODO!!! } public RefactoringStatus checkInitialConditions (IProgressMonitor pm) throws CoreException, OperationCanceledException { return new RefactoringStatus(); } public RefactoringStatus checkFinalConditions (IProgressMonitor pm) throws CoreException, OperationCanceledException { return new RefactoringStatus(); } @Override protected void refactorInFile(TextChange tc, CompositeChange cc, Tree.CompilationUnit root, List<CommonToken> tokens) { Tree.AttributeDeclaration decNode = (Tree.AttributeDeclaration) node; Value param = decNode.getDeclarationModel(); TypeDeclaration target = param.getTypeDeclaration(); Tree.Declaration fun = getContainer(rootNode, param); Tree.SpecifierOrInitializerExpression sie = decNode.getSpecifierOrInitializerExpression(); Tree.Term defaultArg = null; if (sie!=null && sie.getExpression()!=null) { defaultArg = sie.getExpression().getTerm(); } tc.setEdit(new MultiTextEdit()); if (fun.getUnit() .equals(root.getUnit())) { if (leaveDelegate) { leaveOriginal(tc, fun, param); } else { deleteOld(tc, fun); } } new MoveVisitor(target, param, fun, defaultArg, tc, tokens) .visit(root); if (tc.getEdit().hasChildren()) { cc.add(tc); } } private void leaveOriginal(TextChange tfc, Tree.Declaration declaration, Value param) { StringBuilder params = new StringBuilder(); Declaration dec = declaration.getDeclarationModel(); String outer = param.getName() + "."; String semi = ";"; Node body; Tree.ParameterList parameterList; if (declaration instanceof Tree.AnyMethod) { Tree.AnyMethod m = (Tree.AnyMethod) declaration; parameterList = m.getParameterLists().get(0); if (declaration instanceof Tree.MethodDeclaration) { Tree.MethodDeclaration md = (Tree.MethodDeclaration) declaration; body = md.getSpecifierExpression(); semi = ""; } else if (declaration instanceof Tree.MethodDefinition) { Tree.MethodDefinition md = (Tree.MethodDefinition) declaration; body = md.getBlock(); } else { return; //impossible! } } /*else if (declaration instanceof Tree.AnyClass) { Tree.AnyClass m = (Tree.AnyClass) declaration; parameterList = m.getParameterList(); if (declaration instanceof Tree.ClassDefinition) { Tree.ClassDefinition md = (Tree.ClassDefinition) declaration; body = md.getClassBody(); } else { return; //impossible! } }*/ else { return; //impossible! } for (Tree.Parameter parameter: parameterList.getParameters()) { Parameter p = parameter.getParameterModel(); FunctionOrValue model = p.getModel(); if (model==null || !model.equals(param)) { if (params.length()>0) { params.append(", "); } params.append(p.getName()); } } tfc.addEdit(new ReplaceEdit( body.getStartIndex(), body.getDistance(), "=> " + outer + dec.getName() + "(" + params + ")" + semi)); } private void deleteOld(TextChange tfc, Tree.Declaration fun) { tfc.addEdit(new DeleteEdit(fun.getStartIndex(), fun.getDistance())); } public void setLeaveDelegate() { leaveDelegate = !leaveDelegate; } public boolean isMethod() { Tree.AttributeDeclaration ad = (Tree.AttributeDeclaration) node; Value dec = ad.getDeclarationModel(); return dec.getContainer() instanceof Function; } }