package com.redhat.ceylon.eclipse.code.refactor;
import static com.redhat.ceylon.eclipse.java2ceylon.Java2CeylonProxies.utilJ2C;
import static com.redhat.ceylon.eclipse.util.EditorUtil.getSelection;
import static com.redhat.ceylon.eclipse.util.Nodes.getContainer;
import static com.redhat.ceylon.eclipse.util.Nodes.text;
import static org.eclipse.ltk.core.refactoring.RefactoringStatus.createErrorStatus;
import static org.eclipse.ltk.core.refactoring.RefactoringStatus.createWarningStatus;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
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.jface.text.ITextSelection;
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.eclipse.code.editor.CeylonEditor;
import com.redhat.ceylon.ide.common.util.escaping_;
import com.redhat.ceylon.model.typechecker.model.Class;
import com.redhat.ceylon.model.typechecker.model.ClassOrInterface;
import com.redhat.ceylon.model.typechecker.model.Declaration;
import com.redhat.ceylon.model.typechecker.model.Functional;
import com.redhat.ceylon.model.typechecker.model.ModelUtil;
import com.redhat.ceylon.model.typechecker.model.Parameter;
import com.redhat.ceylon.model.typechecker.model.ParameterList;
import com.redhat.ceylon.model.typechecker.model.Scope;
import com.redhat.ceylon.model.typechecker.model.TypeDeclaration;
import com.redhat.ceylon.model.typechecker.model.Unit;
public class MoveOutRefactoring extends AbstractRefactoring {
private Tree.Declaration declaration;
private boolean makeShared=true;
private boolean leaveDelegate=false;
private String newName;
public MoveOutRefactoring(IEditorPart editor) {
super(editor);
if (editor instanceof CeylonEditor) {
CeylonEditor ce = (CeylonEditor) editor;
if (ce.getSelectionProvider()!=null) {
init(getSelection(ce));
}
}
}
private void init(ITextSelection selection) {
if (node instanceof Tree.Declaration) {
declaration = (Tree.Declaration) node;
Declaration dec = declaration.getDeclarationModel();
if (dec instanceof Functional) {
newName =
defaultName((Functional) dec,
getContainer(rootNode, dec));
}
}
}
@Override
public boolean getEnabled() {
if (node instanceof Tree.AnyMethod ||
node instanceof Tree.ClassDefinition) {
Tree.Declaration decNode = (Tree.Declaration) node;
Declaration dec = decNode.getDeclarationModel();
if (dec==null || !dec.isClassOrInterfaceMember()) {
return false;
}
if (decNode instanceof Tree.ClassDefinition) {
Tree.ClassDefinition cd = (Tree.ClassDefinition) decNode;
if (cd.getParameterList()==null) {
return false;
}
}
else if (decNode instanceof Tree.AnyMethod) {
Tree.AnyMethod am = (Tree.AnyMethod) decNode;
if (am.getParameterLists().isEmpty()) {
return false;
}
}
return true;
}
else {
return false;
}
}
public String getName() {
return "Move Out";
}
public boolean isMethod() {
return declaration instanceof Tree.AnyMethod;
}
@Override
protected boolean isAffectingOtherFiles() {
return true; //TODO!!!
}
public RefactoringStatus checkInitialConditions(IProgressMonitor pm)
throws CoreException, OperationCanceledException {
RefactoringStatus result = new RefactoringStatus();
Tree.Declaration decNode = (Tree.Declaration) node;
Declaration dec = decNode.getDeclarationModel();
if (!(dec instanceof Functional) ||
((Functional) dec).getParameterLists().isEmpty()) {
result.merge(createErrorStatus("Selected declaration has no parameter list"));
}
if (!dec.isClassOrInterfaceMember()) {
result.merge(createErrorStatus("Selected declaration is not a member of a class or interface"));
}
if (dec.isFormal()) {
result.merge(createWarningStatus("Selected declaration is annotated formal"));
}
if (dec.isDefault()) {
result.merge(createWarningStatus("Selected declaration is annotated default"));
}
if (dec.isActual()) {
result.merge(createWarningStatus("Selected declaration is annotated actual"));
}
//TODO: check if there are method references to this declaration
return result;
}
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) {
Declaration dec = declaration.getDeclarationModel();
Tree.TypeDeclaration owner =
(Tree.TypeDeclaration)
getContainer(rootNode, dec);
tc.setEdit(new MultiTextEdit());
if (declaration.getUnit()
.equals(root.getUnit())) {
move(owner, tc);
if (makeShared) {
addSharedAnnotations(tc, owner);
}
}
if (!leaveDelegate) {
fixInvocations(dec, root, tokens, tc);
}
if (tc.getEdit().hasChildren()) {
cc.add(tc);
}
}
private String renderText(Tree.TypeDeclaration owner,
String indent, String originalIndent, String delim) {
Unit unit = declaration.getUnit();
String qtype =
owner.getDeclarationModel()
.getType()
.asSourceCodeString(unit);
StringBuilder sb = new StringBuilder();
if (declaration instanceof Tree.AnyMethod) {
Tree.AnyMethod am = (Tree.AnyMethod) declaration;
appendAnnotations(sb, am, owner.getDeclarationModel());
String typeDec;
Tree.Type mdt = am.getType();
if (mdt instanceof Tree.FunctionModifier &&
!ModelUtil.isTypeUnknown(mdt.getTypeModel())) {
typeDec =
mdt.getTypeModel()
.asSourceCodeString(unit);
}
else {
typeDec = text(mdt, tokens);
}
sb.append(typeDec).append(" ")
.append(text(am.getIdentifier(), tokens));
if (am.getTypeParameterList()!=null)
sb.append(text(am.getTypeParameterList(), tokens));
List<Tree.ParameterList> parameterLists =
am.getParameterLists();
Tree.ParameterList first = parameterLists.get(0);
sb.append(text(first, tokens));
if (!first.getParameters().isEmpty()) {
sb.insert(sb.length()-1, ", ");
}
sb.insert(sb.length()-1, qtype+ " " + newName);
for (int i=1; i<parameterLists.size(); i++) {
sb.append(text(parameterLists.get(i), tokens));
}
if (am.getTypeConstraintList()!=null) {
appendConstraints(indent, delim, sb,
am.getTypeConstraintList());
}
sb.append(" ");
if (am instanceof Tree.MethodDefinition) {
Tree.MethodDefinition md =
(Tree.MethodDefinition) am;
Tree.Block block = md.getBlock();
if (block!=null) {
appendBody(owner.getDeclarationModel(),
indent, originalIndent,
delim, sb, block);
}
}
if (am instanceof Tree.MethodDeclaration) {
Tree.MethodDeclaration md =
(Tree.MethodDeclaration) am;
Tree.SpecifierExpression se =
md.getSpecifierExpression();
if (se!=null) {
appendBody(owner.getDeclarationModel(),
indent, originalIndent,
delim, sb, se);
sb.append(";");
}
}
}
else if (declaration instanceof Tree.ClassDefinition) {
Tree.ClassDefinition cd = (Tree.ClassDefinition) declaration;
appendAnnotations(sb, cd, owner.getDeclarationModel());
sb.append("class ")
.append(text(cd.getIdentifier(), tokens));
if (cd.getTypeParameterList()!=null)
sb.append(text(cd.getTypeParameterList(), tokens));
Tree.ParameterList first = cd.getParameterList();
sb.append(text(first, tokens));
if (!first.getParameters().isEmpty()) {
sb.insert(sb.length()-1, ", ");
}
sb.insert(sb.length()-1, qtype+ " " + newName);
if (cd.getCaseTypes()!=null) {
appendClause(indent, delim, sb, cd.getCaseTypes());
}
if (cd.getExtendedType()!=null) {
appendClause(indent, delim, sb, cd.getExtendedType());
}
if (cd.getSatisfiedTypes()!=null) {
appendClause(indent, delim, sb, cd.getSatisfiedTypes());
}
if (cd.getTypeConstraintList()!=null) {
appendConstraints(indent, delim, sb,
cd.getTypeConstraintList());
}
sb.append(" ");
if (cd.getClassBody()!=null) {
appendBody(owner.getDeclarationModel(),
indent, originalIndent,
delim, sb,
cd.getClassBody());
}
}
return sb.toString();
}
private void move(Tree.TypeDeclaration owner, TextChange tfc) {
String indent =
utilJ2C().indents()
.getIndent(owner, document);
String originalIndent =
utilJ2C().indents()
.getIndent(declaration, document);
String delim =
utilJ2C().indents()
.getDefaultLineDelimiter(document);
String text = renderText(owner, indent, originalIndent, delim);
tfc.addEdit(new InsertEdit(owner.getEndIndex(),
delim+indent+delim+indent+text));
if (leaveDelegate) {
leaveOriginal(tfc);
}
else {
tfc.addEdit(new DeleteEdit(
declaration.getStartIndex(),
declaration.getDistance()));
}
}
private void leaveOriginal(TextChange tfc) {
StringBuilder params = new StringBuilder();
String outer;
Declaration dec = declaration.getDeclarationModel();
ClassOrInterface container =
(ClassOrInterface) dec.getContainer();
if (container.isToplevel()) {
outer = "package.";
}
else if (container.isClassOrInterfaceMember()) {
outer = "outer.";
}
else {
outer = ""; //let the user deal with it!
}
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()) {
params.append(parameter.getParameterModel().getName())
.append(", ");
}
params.append("this");
tfc.addEdit(new ReplaceEdit(
body.getStartIndex(),
body.getDistance(),
"=> " + outer +
dec.getName() +
"(" + params + ")" + semi));
}
private void addSharedAnnotations(final TextChange tfc,
final Tree.TypeDeclaration owner) {
final Set<Declaration> decs = new HashSet<Declaration>();
new Visitor() {
private void add(Declaration d) {
if (d!=null && !d.isShared() &&
d.getContainer().equals(
owner.getDeclarationModel())) {
decs.add(d);
}
}
public void visit(Tree.BaseMemberOrTypeExpression that) {
super.visit(that);
add(that.getDeclaration());
}
public void visit(Tree.QualifiedMemberOrTypeExpression that) {
super.visit(that);
if (that.getPrimary() instanceof Tree.This) {
add(that.getDeclaration());
}
}
}.visit(declaration);
new Visitor() {
public void visit(Tree.Declaration that) {
if (decs.contains(that.getDeclarationModel())) {
tfc.addEdit(new InsertEdit(that.getStartIndex(), "shared "));
}
super.visit(that);
}
}.visit(owner);
}
private static String defaultName(Functional dec, Tree.Declaration owner) {
if (owner==null || owner.getIdentifier()==null) {
return "it";
}
String name = owner.getIdentifier().getText();
String paramName = escaping_.get_().toInitialLowercase(name);
if (escaping_.get_().isKeyword(paramName)) {
return "it";
}
ParameterList firstParameterList =
dec.getFirstParameterList();
if (firstParameterList!=null) {
for (Parameter p:
firstParameterList.getParameters()) {
if (p!=null) {
if (paramName.equals(p.getName())) {
return "it";
}
}
}
}
return paramName;
}
private void fixInvocations(final Declaration dec,
Tree.CompilationUnit cu,
final List<CommonToken> tokens,
final TextChange tc) {
new Visitor() {
public void visit(Tree.QualifiedType that) {
TypeDeclaration d = that.getDeclarationModel();
if (d!=null && d.equals(dec)) {
Tree.StaticType qt = that.getOuterType();
tc.addEdit(new DeleteEdit(qt.getStartIndex(),
that.getIdentifier().getStartIndex()-qt.getStartIndex()));
}
}
public void visit(Tree.InvocationExpression that) {
super.visit(that);
Tree.PositionalArgumentList pal =
that.getPositionalArgumentList();
Tree.NamedArgumentList nal =
that.getNamedArgumentList();
Tree.Primary primary = that.getPrimary();
if (primary instanceof Tree.BaseMemberOrTypeExpression) {
Tree.BaseMemberOrTypeExpression bmte =
(Tree.BaseMemberOrTypeExpression)
primary;
if (bmte.getDeclaration().equals(dec)) {
if (pal!=null) {
String arg =
pal.getPositionalArguments()
.isEmpty() ?
"this" : ", this";
tc.addEdit(new InsertEdit(pal.getEndIndex()-1, arg));
}
if (nal!=null) {
try {
IDocument doc =
tc.getCurrentDocument(null);
String arg =
namedArgIndent(nal, doc) +
newName + " = this;";
List<Tree.NamedArgument> args =
nal.getNamedArguments();
int offset =
args.isEmpty() ?
nal.getStartIndex()+1 :
args.get(args.size()-1).getEndIndex();
tc.addEdit(new InsertEdit(offset, arg));
}
catch (CoreException e) {
e.printStackTrace();
}
}
}
}
if (primary instanceof Tree.QualifiedMemberOrTypeExpression) {
Tree.QualifiedMemberOrTypeExpression qmte =
(Tree.QualifiedMemberOrTypeExpression)
primary;
if (qmte.getDeclaration().equals(dec)) {
Tree.Primary p = qmte.getPrimary();
String pt = text(p, tokens);
tc.addEdit(new DeleteEdit(p.getStartIndex(),
qmte.getIdentifier().getStartIndex()-p.getStartIndex()));
if (pal!=null) {
String arg =
pal.getPositionalArguments()
.isEmpty() ?
pt : ", " + pt;
tc.addEdit(new InsertEdit(pal.getEndIndex()-1, arg));
}
if (nal!=null) {
try {
IDocument doc =
tc.getCurrentDocument(null);
String arg =
namedArgIndent(nal, doc) +
newName + " = " + pt + ";";
List<Tree.NamedArgument> args =
nal.getNamedArguments();
int offset = args.isEmpty() ?
nal.getStartIndex()+1 :
args.get(args.size()-1).getEndIndex();
tc.addEdit(new InsertEdit(offset, arg));
}
catch (CoreException e) {
e.printStackTrace();
}
}
}
}
}
private String namedArgIndent(
Tree.NamedArgumentList nal,
IDocument doc) {
return utilJ2C().indents().getDefaultLineDelimiter(doc) +
utilJ2C().indents().getIndent(nal, doc) +
utilJ2C().indents().getDefaultIndent();
}
}.visit(cu);
}
private void appendAnnotations(StringBuilder sb,
Tree.Declaration d, TypeDeclaration od) {
if (!d.getAnnotationList().getAnnotations().isEmpty()) {
String annotations = text(d.getAnnotationList(), tokens);
if (!od.isShared()) {
annotations = annotations.replaceAll("shared", "");
}
annotations = annotations.replaceAll("default|formal|actual", "");
sb.append(annotations.trim());
if (sb.length()!=0) {
sb.append(" ");
}
}
}
private void appendConstraints(String indent, String delim,
StringBuilder sb, Tree.TypeConstraintList tcl) {
for (Tree.TypeConstraint tc: tcl.getTypeConstraints()) {
appendClause(indent, delim, sb, tc);
}
}
private void appendClause(String indent, String delim,
StringBuilder sb, Node clause) {
sb.append(delim).append(indent)
.append(utilJ2C().indents().getDefaultIndent())
.append(utilJ2C().indents().getDefaultIndent())
.append(text(clause, tokens));
}
private void appendBody(final Scope container,
String indent, String originalIndent,
String delim, StringBuilder sb, final Node body) {
final StringBuilder stb =
new StringBuilder(text(body, tokens));
body.visit(new Visitor() {
int offset = 0;
private int startIndex(Node that) {
return that.getStartIndex()
- body.getStartIndex()
+ offset;
}
@Override
public void visit(Tree.BaseMemberOrTypeExpression that) {
super.visit(that);
if (that.getDeclaration().getContainer()
.equals(container)) {
int start = startIndex(that);
stb.insert(start, newName + ".");
offset+=newName.length()+1;
}
}
@Override
public void visit(Tree.This that) {
super.visit(that);
int start = startIndex(that);
boolean isClass =
declaration.getDeclarationModel()
instanceof Class;
if (!isClass) {
stb.replace(start, start+4, newName);
offset+=newName.length()-4;
}
}
@Override
public void visit(Tree.Outer that) {
super.visit(that);
int start = startIndex(that);
boolean isClass =
declaration.getDeclarationModel()
instanceof Class;
if (isClass) {
stb.replace(start, start+5, newName);
offset+=newName.length()-5;
}
else {
stb.replace(start, start+5, "this");
offset+=4-5;
}
}
});
sb.append(stb.toString().replaceAll(delim+originalIndent, delim+indent));
}
public void setMakeShared() {
makeShared=!makeShared;
}
public void setLeaveDelegate() {
leaveDelegate = !leaveDelegate;
}
void setNewName(String name) {
newName = name;
}
String getNewName() {
return newName;
}
}