package com.redhat.ceylon.eclipse.code.refactor;
import static com.redhat.ceylon.eclipse.java2ceylon.Java2CeylonProxies.utilJ2C;
import static com.redhat.ceylon.eclipse.util.Nodes.findToplevelStatement;
import static com.redhat.ceylon.eclipse.util.Nodes.text;
import java.util.ArrayList;
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.IRegion;
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.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.TreeUtil;
import com.redhat.ceylon.compiler.typechecker.tree.Visitor;
import com.redhat.ceylon.ide.common.util.escaping_;
import com.redhat.ceylon.model.typechecker.model.Declaration;
import com.redhat.ceylon.model.typechecker.model.FunctionOrValue;
import com.redhat.ceylon.model.typechecker.model.Parameter;
public class CollectParametersRefactoring extends AbstractRefactoring {
private Declaration declaration;
private int parameterListIndex;
private List<Tree.Parameter> parameters =
new ArrayList<Tree.Parameter>();
private Set<FunctionOrValue> models =
new HashSet<FunctionOrValue>();
private int firstParam=-1;
private int lastParam;
private class FindParametersVisitor extends Visitor {
private void handleParamList(Tree.Declaration that, int i,
Tree.ParameterList pl) {
if (pl==null) return;
IRegion selection = editor.getSelection();
int start = selection.getOffset();
int end = selection.getOffset() + selection.getLength();
if (start>pl.getStartIndex() && start<pl.getEndIndex()) {
parameterListIndex = i;
declaration = that.getDeclarationModel();
for (int j=0;
j<pl.getParameters().size();
j++) {
Tree.Parameter p = pl.getParameters().get(j);
if (p.getStartIndex()>=start && p.getEndIndex()<=end) {
parameters.add(p);
models.add(p.getParameterModel().getModel());
if (firstParam==-1) firstParam=j;
lastParam=j;
}
}
}
}
@Override
public void visit(Tree.AnyMethod that) {
for (int i=0;
i<that.getParameterLists().size();
i++) {
handleParamList(that, i,
that.getParameterLists().get(i));
}
super.visit(that);
}
@Override
public void visit(Tree.ClassDefinition that) {
handleParamList(that, 0,
that.getParameterList());
super.visit(that);
}
}
private static class FindInvocationsVisitor extends Visitor {
private Declaration declaration;
private final Set<Tree.ArgumentList> results =
new HashSet<Tree.ArgumentList>();
Set<Tree.ArgumentList> getResults() {
return results;
}
private FindInvocationsVisitor(Declaration declaration) {
this.declaration=declaration;
}
@Override
public void visit(Tree.InvocationExpression that) {
super.visit(that);
Tree.Primary primary = that.getPrimary();
if (primary instanceof Tree.MemberOrTypeExpression) {
Tree.MemberOrTypeExpression mte =
(Tree.MemberOrTypeExpression)
primary;
if (mte.getDeclaration()
.equals(declaration)) {
Tree.PositionalArgumentList pal =
that.getPositionalArgumentList();
if (pal!=null) {
results.add(pal);
}
Tree.NamedArgumentList nal =
that.getNamedArgumentList();
if (nal!=null) {
results.add(nal);
}
}
}
}
}
private static class FindArgumentsVisitor extends Visitor {
private Declaration declaration;
private final Set<Tree.MethodArgument> results =
new HashSet<Tree.MethodArgument>();
Set<Tree.MethodArgument> getResults() {
return results;
}
private FindArgumentsVisitor(Declaration declaration) {
this.declaration=declaration;
}
@Override
public void visit(Tree.MethodArgument that) {
super.visit(that);
Parameter p = that.getParameter();
if (p!=null && p.getModel().equals(declaration)) {
results.add(that);
}
}
}
private String newName;
public String getNewName() {
return newName;
}
public void setNewName(String newName) {
this.newName = newName;
}
public CollectParametersRefactoring(IEditorPart editor) {
super(editor);
if (rootNode!=null) {
new FindParametersVisitor().visit(rootNode);
if (declaration!=null) {
newName =
escaping_.get_()
.toInitialUppercase(
declaration.getName());
}
}
}
@Override
public boolean getEnabled() {
return declaration!=null &&
!parameters.isEmpty();
}
@Override
int countReferences(Tree.CompilationUnit cu) {
FindInvocationsVisitor frv =
new FindInvocationsVisitor(declaration);
FindRefinementsVisitor fdv =
new FindRefinementsVisitor(declaration);
FindArgumentsVisitor fav =
new FindArgumentsVisitor(declaration);
cu.visit(frv);
cu.visit(fdv);
cu.visit(fav);
return frv.getResults().size() +
fdv.getDeclarationNodes().size() +
fav.getResults().size();
}
public String getName() {
return "Collect Parameters";
}
public RefactoringStatus checkInitialConditions(IProgressMonitor pm)
throws CoreException,
OperationCanceledException {
// Check parameters retrieved from editor context
return new RefactoringStatus();
}
public RefactoringStatus checkFinalConditions(IProgressMonitor pm)
throws CoreException,
OperationCanceledException {
return new RefactoringStatus();
}
@Override
protected void refactorInFile(TextChange tfc,
CompositeChange cc, Tree.CompilationUnit root,
List<CommonToken> tokens) {
tfc.setEdit(new MultiTextEdit());
if (declaration!=null) {
String paramName =
escaping_.get_()
.toInitialLowercase(newName);
FindInvocationsVisitor fiv =
new FindInvocationsVisitor(declaration);
root.visit(fiv);
for (Tree.ArgumentList pal: fiv.getResults()) {
refactorInvocation(tfc, paramName, pal, tokens);
}
FindRefinementsVisitor frv =
new FindRefinementsVisitor(declaration);
root.visit(frv);
for (Tree.StatementOrArgument decNode:
frv.getDeclarationNodes()) {
refactorDeclaration(tfc, paramName, decNode);
}
FindArgumentsVisitor fav =
new FindArgumentsVisitor(declaration);
root.visit(fav);
for (Tree.MethodArgument decNode: fav.getResults()) {
refactorArgument(tfc, paramName, decNode);
}
createNewClassDeclaration(tfc, root);
}
if (tfc.getEdit().hasChildren()) {
cc.add(tfc);
}
}
private void refactorInvocation(TextChange tfc,
String paramName, Tree.ArgumentList al,
List<CommonToken> toks) {
if (al instanceof Tree.PositionalArgumentList) {
Tree.PositionalArgumentList pal =
(Tree.PositionalArgumentList) al;
List<Tree.PositionalArgument> pas =
pal.getPositionalArguments();
if (pas.size()>firstParam) {
Integer startIndex =
pas.get(firstParam)
.getStartIndex();
tfc.addEdit(new InsertEdit(startIndex, newName + "("));
Integer stopIndex =
pas.size()>lastParam &&
!pas.get(lastParam)
.getParameter()
.isSequenced() ?
pas.get(lastParam).getEndIndex():
pas.get(pas.size()-1).getEndIndex();
tfc.addEdit(new InsertEdit(stopIndex, ")"));
}
}
else if (al instanceof Tree.NamedArgumentList) {
Tree.NamedArgumentList nal =
(Tree.NamedArgumentList) al;
List<Tree.NamedArgument> nas =
nal.getNamedArguments();
List<Tree.StatementOrArgument> results =
new ArrayList<Tree.StatementOrArgument>();
Tree.NamedArgument prev = null;
for (Tree.NamedArgument na: nas) {
FunctionOrValue p =
na.getParameter().getModel();
if (models.contains(p)) {
int fromOffset = results.isEmpty() ?
na.getStartIndex() :
prev.getEndIndex();
int toOffset = na.getEndIndex();
tfc.addEdit(new DeleteEdit(fromOffset,
toOffset-fromOffset));
results.add(na);
}
prev = na;
}
Tree.SequencedArgument sa =
nal.getSequencedArgument();
if (sa!=null) {
FunctionOrValue p =
sa.getParameter().getModel();
if (models.contains(p)) {
int fromOffset = sa.getStartIndex();
int toOffset = sa.getEndIndex();
tfc.addEdit(new DeleteEdit(fromOffset,
toOffset-fromOffset));
results.add(sa);
}
}
if (!results.isEmpty()) {
StringBuilder builder = new StringBuilder();
builder.append(paramName).append(" = ")
.append(newName).append(" { ");
for (Tree.StatementOrArgument na: results) {
builder.append(text(na, toks)).append(" ");
}
builder.append("};");
tfc.addEdit(new InsertEdit(
results.get(0).getStartIndex(),
builder.toString()));
}
}
}
private void createNewClassDeclaration(final TextChange tfc,
Tree.CompilationUnit root) {
if (declaration.getUnit().equals(root.getUnit())) {
String delim =
utilJ2C().indents()
.getDefaultLineDelimiter(document);
//TODO: for unshared declarations, we don't
// need to make it toplevel, I guess
int loc = findToplevelStatement(rootNode, node).getStartIndex();
StringBuilder builder = new StringBuilder();
if (declaration.isShared()) {
builder.append("shared ");
}
builder.append("class ").append(newName).append("(");
for (Tree.Parameter p: parameters) {
boolean addShared = true;
if (p instanceof Tree.ParameterDeclaration) {
Tree.ParameterDeclaration pd =
(Tree.ParameterDeclaration) p;
Tree.TypedDeclaration ptd =
pd.getTypedDeclaration();
if (TreeUtil.hasAnnotation(ptd.getAnnotationList(),
"shared", ptd.getUnit())) {
addShared = false;
}
}
if (addShared) {
builder.append("shared ");
}
builder.append(text(p, tokens)).append(", ");
}
if (builder.toString().endsWith(", ")) {
builder.setLength(builder.length()-2);
}
builder.append(") {}").append(delim).append(delim);
tfc.addEdit(new InsertEdit(loc, builder.toString()));
}
}
private void refactorArgument(TextChange tfc,
String paramName, Tree.MethodArgument decNode) {
refactorDec(tfc, paramName,
decNode.getParameterLists()
.get(parameterListIndex),
decNode.getBlock());
}
private void refactorDeclaration(TextChange tfc,
String paramName, Tree.StatementOrArgument decNode) {
Tree.ParameterList pl;
Node body;
if (decNode instanceof Tree.MethodDefinition) {
Tree.MethodDefinition md =
(Tree.MethodDefinition)
decNode;
pl = md.getParameterLists()
.get(parameterListIndex);
body = md.getBlock();
}
else if (decNode instanceof Tree.MethodDeclaration) {
Tree.MethodDeclaration md =
(Tree.MethodDeclaration)
decNode;
pl = md.getParameterLists()
.get(parameterListIndex);
body = md.getSpecifierExpression();
}
else if (decNode instanceof Tree.ClassDefinition) {
Tree.ClassDefinition cd =
(Tree.ClassDefinition) decNode;
pl = cd.getParameterList();
body = cd.getClassBody();
}
else if (decNode instanceof Tree.SpecifierStatement) {
Tree.SpecifierStatement ss =
(Tree.SpecifierStatement)
decNode;
Tree.Term bme = ss.getBaseMemberExpression();
body = ss.getSpecifierExpression();
if (bme instanceof Tree.ParameterizedExpression) {
Tree.ParameterizedExpression pe =
(Tree.ParameterizedExpression) bme;
pl = pe.getParameterLists().get(parameterListIndex);
}
else {
return;
}
}
else {
return;
}
refactorDec(tfc, paramName, pl, body);
}
private void refactorDec(final TextChange tfc,
final String paramName,
Tree.ParameterList pl, Node body) {
List<Tree.Parameter> ps = pl.getParameters();
final Set<FunctionOrValue> params =
new HashSet<FunctionOrValue>();
boolean allDefaulted = true;
for (int i=firstParam;
i<ps.size()&&i<=lastParam;
i++) {
Parameter p = ps.get(i).getParameterModel();
params.add(p.getModel());
if (!p.isDefaulted()) {
allDefaulted = false;
}
}
int startOffset = ps.get(firstParam).getStartIndex();
int endOffset = ps.get(lastParam).getEndIndex();
String text = newName + " " + paramName;
if (allDefaulted) {
text += " = " + newName + "()";
}
tfc.addEdit(new InsertEdit(startOffset, text));
tfc.addEdit(new DeleteEdit(startOffset, endOffset-startOffset));
for (int i=lastParam+1; i<ps.size(); i++) {
refactorLocalRefs(tfc, paramName, params, ps.get(i));
}
refactorLocalRefs(tfc, paramName, params, body);
}
private void refactorLocalRefs(final TextChange tfc,
final String paramName,
final Set<FunctionOrValue> params,
Node node) {
node.visit(new Visitor() {
@Override
public void visit(Tree.BaseMemberExpression that) {
super.visit(that);
if (params.contains(that.getDeclaration())) {
tfc.addEdit(new InsertEdit(that.getStartIndex(),
paramName + "."));
}
}
});
}
@Override
protected boolean isAffectingOtherFiles() {
if (declaration==null) {
return false;
}
if (declaration.isToplevel() ||
declaration.isShared()) {
return true;
}
return false;
}
}