package com.redhat.ceylon.eclipse.code.refactor;
import static com.redhat.ceylon.eclipse.core.builder.CeylonBuilder.getProjectTypeChecker;
import static com.redhat.ceylon.eclipse.util.Nodes.getReferencedExplicitDeclaration;
import java.util.ArrayList;
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.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.MultiTextEdit;
import org.eclipse.ui.IEditorPart;
import com.redhat.ceylon.compiler.typechecker.context.PhasedUnit;
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.search.CeylonSearchMatch;
import com.redhat.ceylon.model.typechecker.model.Declaration;
import com.redhat.ceylon.model.typechecker.model.FunctionOrValue;
import com.redhat.ceylon.model.typechecker.model.Parameter;
import com.redhat.ceylon.model.typechecker.model.Referenceable;
import com.redhat.ceylon.model.typechecker.model.Type;
import com.redhat.ceylon.model.typechecker.model.TypedDeclaration;
public class DeleteRefactoring extends AbstractRefactoring {
private boolean deleteRefinements;
private class FindDeletedReferencesVisitor
extends FindReferencesVisitor {
private FindDeletedReferencesVisitor(Declaration declaration) {
super(declaration);
}
@Override
protected boolean isReference(Declaration ref) {
Declaration declaration =
(Declaration) getDeclaration();
if (ref==null) {
return false;
}
else if (ref.equals(declaration)) {
if (!declaration.isActual() ||
declaration.equals(refinedDeclaration)) {
return true;
}
else {
if (declaration instanceof TypedDeclaration &&
refinedDeclaration instanceof TypedDeclaration) {
//if it's a reference to a refining method or value
//we can safely delete unless it refines the return type
TypedDeclaration typedDeclaration =
(TypedDeclaration) declaration;
TypedDeclaration refinedTypedDeclaration =
(TypedDeclaration) refinedDeclaration;
Type type =
typedDeclaration.getType();
Type refinedType =
refinedTypedDeclaration.getType();
return type!=null && refinedType!=null &&
!type.isExactly(refinedType);
}
else {
return true;
}
}
}
else {
return deleteRefinements &&
ref.refines(declaration);
}
}
@Override
public void visit(Tree.InitializerParameter that) {
Tree.SpecifierExpression sie =
that.getSpecifierExpression();
if (sie!=null) {
sie.visit(this);
}
}
@Override
public void visit(Tree.Declaration that) {
Declaration dec = that.getDeclarationModel();
if (!dec.equals(declarationToDelete) &&
(!deleteRefinements ||
!dec.refines(declarationToDelete))) {
super.visit(that);
}
}
@Override
public void visit(Tree.NamedArgument that) {
if (!deleteRefinements &&
isReference(that.getParameter())) {
getNodes().add(that);
}
else {
//the supertype doesn't test deleteRefinements
getNodes().remove(that);
}
super.visit(that);
}
@Override
public void visit(Tree.PositionalArgument that) {
if (!deleteRefinements &&
isReference(that.getParameter())) {
getNodes().add(that);
}
super.visit(that);
}
@Override
public void visit(Tree.SequencedArgument that) {
if (!deleteRefinements &&
isReference(that.getParameter())) {
getNodes().add(that);
}
super.visit(that);
}
@Override
public void visit(Tree.AnyClass that) {
super.visit(that);
handleParameterRefinement(that);
}
@Override
public void visit(Tree.AnyMethod that) {
super.visit(that);
handleParameterRefinement(that);
}
private void handleParameterRefinement(Tree.Declaration that) {
Declaration declaration =
(Declaration) getDeclaration();
if (declaration.isParameter()) {
Declaration parameterized =
(Declaration)
declaration.getContainer();
Declaration current = that.getDeclarationModel();
if (!parameterized.equals(current)) {
if (parameterized.getRefinedDeclaration()
.equals(current)) {
getNodes().add(that);
}
if (current.getRefinedDeclaration()
.equals(parameterized)) {
getNodes().add(that);
}
}
}
}
@Override
public void visit(Tree.Import that) {}
}
//TODO: copy/pasted from RenameRefactoring!
class FindDocLinkReferencesVisitor extends Visitor {
private Declaration declaration;
private List<Tree.DocLink> links =
new ArrayList<Tree.DocLink>();
List<Tree.DocLink> getLinks() {
return links;
}
FindDocLinkReferencesVisitor(Declaration declaration) {
this.declaration = declaration;
}
@Override
public void visit(Tree.DocLink that) {
if (that.getBase()!=null) {
if (that.getBase().equals(declaration)) {
links.add(that);
}
else if (that.getQualified()!=null) {
if (that.getQualified().contains(declaration)) {
links.add(that);
}
}
}
}
@Override
public void visit(Tree.Declaration that) {
if (!that.getDeclarationModel()
.equals(declarationToDelete)) {
super.visit(that);
}
}
}
private class FindDeletedRefinementsVisitor
extends FindRefinementsVisitor {
public FindDeletedRefinementsVisitor(Declaration declaration) {
super(declaration);
}
@Override
protected boolean isRefinement(Declaration dec) {
return !dec.equals(declarationToDelete) &&
(super.isRefinement(dec) &&
!deleteRefinements ||
dec.equals(refinedDeclaration) &&
declarationToDelete.isActual() &&
!declarationToDelete.isFormal() &&
refinedDeclaration.isFormal());
}
}
private final Declaration refinedDeclaration;
private final Declaration declarationToDelete;
public Node getNode() {
return node;
}
public DeleteRefactoring(IEditorPart editor) {
super(editor);
if (rootNode!=null) {
Referenceable ref =
getReferencedExplicitDeclaration(node, rootNode);
if (ref instanceof Declaration) {
declarationToDelete = (Declaration) ref;
if (declarationToDelete!=null) {
refinedDeclaration =
declarationToDelete.getRefinedDeclaration();
}
else {
refinedDeclaration = null;
}
}
else {
declarationToDelete = null;
refinedDeclaration = null;
}
}
else {
declarationToDelete = null;
refinedDeclaration = null;
}
}
@Override
public boolean getEnabled() {
return declarationToDelete!=null &&
project != null &&
inSameProject(declarationToDelete);
}
public int getCount() {
return declarationToDelete==null ?
0 : countDeclarationOccurrences();
}
int countRefinements() {
int count = 0;
if (isAffectingOtherFiles()) {
for (PhasedUnit pu: getAllUnits()) {
if (searchInFile(pu)) {
count += countRefinements(pu.getCompilationUnit());
}
}
}
if (!isAffectingOtherFiles() || searchInEditor()) {
count += countRefinements(rootNode);
}
return count;
}
private int countRefinements(Tree.CompilationUnit cu) {
FindDeletedRefinementsVisitor fdv =
new FindDeletedRefinementsVisitor(declarationToDelete);
cu.visit(fdv);
return fdv.getDeclarationNodes().size();
}
int countUsages() {
int count = 0;
if (isAffectingOtherFiles()) {
for (PhasedUnit pu: getAllUnits()) {
if (searchInFile(pu)) {
count += countUsages(pu.getCompilationUnit());
}
}
}
if (!isAffectingOtherFiles() || searchInEditor()) {
count += countUsages(rootNode);
}
return count;
}
private int countUsages(Tree.CompilationUnit cu) {
FindDeletedReferencesVisitor frv =
new FindDeletedReferencesVisitor(declarationToDelete);
cu.visit(frv);
return frv.getNodes().size();
}
@Override
int countReferences(Tree.CompilationUnit cu) {
FindDeletedReferencesVisitor frv =
new FindDeletedReferencesVisitor(declarationToDelete);
Declaration declaration =
(Declaration) frv.getDeclaration();
FindRefinementsVisitor fdv =
new FindDeletedRefinementsVisitor(declaration);
FindDocLinkReferencesVisitor fdlrv =
new FindDocLinkReferencesVisitor(declaration);
cu.visit(frv);
cu.visit(fdv);
cu.visit(fdlrv);
return frv.getNodes().size() +
fdv.getDeclarationNodes().size() +
fdlrv.getLinks().size();
}
public String getName() {
return "Safe Delete";
}
public RefactoringStatus checkInitialConditions(IProgressMonitor pm)
throws CoreException, OperationCanceledException {
return new RefactoringStatus();
}
public RefactoringStatus checkFinalConditions(IProgressMonitor pm)
throws CoreException, OperationCanceledException {
return new RefactoringStatus();
}
public Declaration getDeclaration() {
return declarationToDelete;
}
public Declaration getRefinedDeclaration() {
return refinedDeclaration;
}
@Override
protected void refactorInFile(final TextChange tfc,
CompositeChange change,
Tree.CompilationUnit cu,
List<CommonToken> tokens) {
tfc.setEdit(new MultiTextEdit());
new Visitor() {
private void deleteArg(final TextChange tfc, Node that,
Declaration d, int start, int stop) {
if (deleteRefinements &&
d.equals(declarationToDelete)) {
tfc.addEdit(new DeleteEdit(start, stop-start));
}
}
@Override
public void visit(Tree.NamedArgument that) {
Parameter parameter = that.getParameter();
if (parameter!=null) {
deleteArg(tfc, that, parameter.getModel(),
that.getStartIndex(),
that.getEndIndex());
super.visit(that);
}
}
@Override
public void visit(Tree.SequencedArgument that) {
Parameter parameter = that.getParameter();
if (parameter!=null) {
deleteArg(tfc, that, parameter.getModel(),
that.getStartIndex(),
that.getEndIndex());
super.visit(that);
}
}
@Override
public void visit(Tree.PositionalArgumentList that) {
List<Tree.PositionalArgument> args =
that.getPositionalArguments();
for (int i=0; i<args.size(); i++) {
Tree.PositionalArgument arg = args.get(i);
Parameter parameter = arg.getParameter();
if (parameter!=null) {
int start, stop;
if (i>0) {
start = args.get(i-1).getEndIndex();
stop = arg.getEndIndex();
}
else if (i<args.size()-1) {
start = arg.getStartIndex();
stop = args.get(i+1).getStartIndex();
}
else {
start = arg.getStartIndex();
stop = arg.getEndIndex();
}
deleteArg(tfc, that, parameter.getModel(),
start, stop);
}
}
super.visit(that);
}
private void deleteDec(final TextChange tfc, Node that,
Declaration d) {
if (d.equals(declarationToDelete) ||
(deleteRefinements &&
d.refines(declarationToDelete))) {
tfc.addEdit(new DeleteEdit(that.getStartIndex(),
that.getDistance()));
}
}
@Override
public void visit(Tree.Declaration that) {
deleteDec(tfc, that, that.getDeclarationModel());
super.visit(that);
}
@Override
public void visit(Tree.SpecifierStatement that) {
if (that.getRefinement()) {
deleteDec(tfc, that, that.getDeclaration());
}
super.visit(that);
}
@Override
public void visit(Tree.ParameterList that) {
List<Tree.Parameter> parameters =
that.getParameters();
for (int i=0; i<parameters.size(); i++) {
Tree.Parameter param = parameters.get(i);
Declaration d =
param.getParameterModel()
.getModel();
if (d.equals(declarationToDelete)) {
int start, stop;
if (i>0) {
Tree.Parameter previous =
parameters.get(i-1);
start = previous.getEndIndex();
stop = param.getEndIndex();
}
else if (i<parameters.size()-1) {
Tree.Parameter next =
parameters.get(i+1);
start = param.getStartIndex();
stop = next.getStartIndex();
}
else {
start = param.getStartIndex();
stop = param.getEndIndex();
}
tfc.addEdit(new DeleteEdit(start, stop-start));
return;
}
}
super.visit(that);
}
@Override
public void visit(Tree.Import that) {
Tree.ImportMemberOrTypeList list =
that.getImportMemberOrTypeList();
if (list!=null &&
list.getImportMemberOrTypes().size()==1) {
Tree.ImportMemberOrType imp =
list.getImportMemberOrTypes()
.get(0);
Declaration d = imp.getDeclarationModel();
if (d.equals(declarationToDelete)) {
tfc.addEdit(new DeleteEdit(that.getStartIndex(),
that.getDistance()));
return;
}
}
super.visit(that);
}
@Override
public void visit(Tree.ImportMemberOrTypeList that) {
List<Tree.ImportMemberOrType> imports =
that.getImportMemberOrTypes();
for (int i=0; i<imports.size(); i++) {
Tree.ImportMemberOrType imp =
imports.get(i);
Declaration d = imp.getDeclarationModel();
if (d.equals(declarationToDelete)) {
int start, stop;
if (i>0) {
Tree.ImportMemberOrType previous =
imports.get(i-1);
start = previous.getEndIndex();
stop = imp.getEndIndex();
}
else if (i<imports.size()-1) {
Tree.ImportMemberOrType next =
imports.get(i+1);
start = imp.getStartIndex();
stop = next.getStartIndex();
}
else {
start = imp.getStartIndex();
stop = imp.getEndIndex();
}
tfc.addEdit(new DeleteEdit(start, stop-start));
return;
}
}
super.visit(that);
}
}.visit(cu);
if (tfc.getEdit().hasChildren()) {
change.add(tfc);
}
}
List<CeylonSearchMatch> getReferences() {
List<CeylonSearchMatch> list =
new ArrayList<CeylonSearchMatch>();
if (isAffectingOtherFiles()) {
for (PhasedUnit pu: getAllUnits()) {
if (searchInFile(pu)) {
addReferences(pu.getCompilationUnit(), list, pu);
}
}
}
if (!isAffectingOtherFiles() || searchInEditor()) {
String relpath =
editor.getParseController()
.getLastPhasedUnit()
.getPathRelativeToSrcDir();
addReferences(rootNode, list,
getProjectTypeChecker(project)
.getPhasedUnitFromRelativePath(relpath));
}
return list;
}
private void addReferences(Tree.CompilationUnit cu,
List<CeylonSearchMatch> list, PhasedUnit pu) {
FindDeletedReferencesVisitor frv =
new FindDeletedReferencesVisitor(declarationToDelete);
Declaration declaration =
(Declaration) frv.getDeclaration();
FindDeletedRefinementsVisitor fdv =
new FindDeletedRefinementsVisitor(declaration);
FindDocLinkReferencesVisitor fdlrv =
new FindDocLinkReferencesVisitor(declaration);
cu.visit(frv);
cu.visit(fdv);
cu.visit(fdlrv);
for (Node node: frv.getNodes()) {
list.add(findContainer(node, cu, pu));
}
for (Node node: fdv.getDeclarationNodes()) {
list.add(findContainer(node, cu, pu));
}
for (Node node: fdlrv.getLinks()) {
list.add(findContainer(node, cu, pu));
}
}
private CeylonSearchMatch findContainer(Node node,
Tree.CompilationUnit cu, PhasedUnit pu) {
return CeylonSearchMatch.create(node, cu, pu.getUnitFile());
}
public void setDeleteRefinements() {
deleteRefinements = !deleteRefinements;
}
@Override
public boolean isAffectingOtherFiles() {
if (declarationToDelete==null) {
return false;
}
if (declarationToDelete.isToplevel() ||
declarationToDelete.isShared()) {
return true;
}
if (declarationToDelete.isParameter()) {
FunctionOrValue fov =
(FunctionOrValue)
declarationToDelete;
Declaration container =
fov.getInitializerParameter()
.getDeclaration();
if (container.isToplevel() ||
container.isShared()) {
return true;
}
}
return false;
}
}