package com.redhat.ceylon.eclipse.code.refactor; import static com.redhat.ceylon.eclipse.code.correct.ImportProposals.importProposals; import static com.redhat.ceylon.eclipse.core.builder.CeylonBuilder.getUnits; import static com.redhat.ceylon.eclipse.java2ceylon.Java2CeylonProxies.utilJ2C; import static com.redhat.ceylon.eclipse.java2ceylon.Java2CeylonProxies.vfsJ2C; import static com.redhat.ceylon.eclipse.util.EditorUtil.getCurrentEditor; import static com.redhat.ceylon.eclipse.util.EditorUtil.getDocument; import static com.redhat.ceylon.eclipse.util.EditorUtil.getFile; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.StringTokenizer; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IProject; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.Region; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.jface.viewers.StructuredSelection; import org.eclipse.ltk.core.refactoring.CompositeChange; import org.eclipse.ltk.core.refactoring.DocumentChange; import org.eclipse.ltk.core.refactoring.TextChange; import org.eclipse.ltk.core.refactoring.TextFileChange; 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.IEditorInput; import org.eclipse.ui.IEditorPart; import org.eclipse.ui.IFileEditorInput; 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.editor.CeylonEditor; import com.redhat.ceylon.eclipse.platform.platformJ2C; import com.redhat.ceylon.eclipse.util.DocLinks; import com.redhat.ceylon.ide.common.util.escaping_; import com.redhat.ceylon.model.typechecker.model.Declaration; import com.redhat.ceylon.model.typechecker.model.Module; import com.redhat.ceylon.model.typechecker.model.Package; import com.redhat.ceylon.model.typechecker.model.Referenceable; import com.redhat.ceylon.model.typechecker.model.TypeDeclaration; import ceylon.interop.java.CeylonMap; public class MoveUtil { public static int addImportEdits(Tree.Declaration node, TextChange fc, IDocument doc, Tree.CompilationUnit ncu, Set<String> packages, Declaration declaration) { Package p = ncu.getUnit().getPackage(); Map<Declaration, String> imports = getImports(node, p.getNameAsString(), ncu, packages); com.redhat.ceylon.ide.common.platform.TextChange change = new platformJ2C().newChange("", fc); return (int) importProposals() .applyImportsWithAliases(change, new CeylonMap<>(null, null, imports), ncu, change.getDocument(), declaration); } public static Map<Declaration,String> getImports(Node node, final String packageName, final Tree.CompilationUnit ncu, final Set<String> packages) { final Map<Declaration, String> imports = new HashMap<Declaration, String>(); node.visit(new Visitor() { private void add(Declaration d, Tree.Identifier id) { if (d!=null && id!=null) { String pn = d.getUnit() .getPackage() .getNameAsString(); if (d.isToplevel() && !pn.equals(packageName) && !pn.equals(Module.LANGUAGE_MODULE_NAME) && (ncu==null || !importProposals() .isImported(d, ncu))) { imports.put(d, id.getText()); packages.add(pn); } } } @Override public void visit(Tree.BaseType that) { super.visit(that); add(that.getDeclarationModel(), that.getIdentifier()); } @Override public void visit(Tree.BaseMemberOrTypeExpression that) { super.visit(that); add(that.getDeclaration(), that.getIdentifier()); } @Override public void visit(Tree.MemberLiteral that) { add(that.getDeclaration(), that.getIdentifier()); } }); return imports; } public static String getImportText(Tree.Declaration node, String targetPackage, String delim) { HashSet<String> packages = new HashSet<String>(); Map<Declaration, String> imports = getImports(node, targetPackage, null, packages); return getImportText(packages, imports, delim); } public static String getImportText(Set<String> packages, Map<Declaration, String> imports, String delim) { StringBuilder sb = new StringBuilder(); for (String pkg: packages) { if (pkg.isEmpty()) { //can't import from default package continue; } sb.append("import ") .append(escapePackageName(pkg)) .append(" {") .append(delim); boolean first = true; for (Map.Entry<Declaration, String> entry: imports.entrySet()) { Declaration d = entry.getKey(); String pname = d.getUnit() .getPackage() .getQualifiedNameString(); if (pname.equals(pkg)) { if (!first) { sb.append(",").append(delim); } sb.append(utilJ2C().indents() .getDefaultIndent()); String name = d.getName(); String alias = entry.getValue(); if (!name.equals(alias)) { sb.append(alias).append("="); } sb.append(name); first = false; } } sb.append(delim) .append("}") .append(delim); } return sb.toString(); } public static void refactorProjectImportsAndDocLinks( Tree.Declaration node, IFile originalFile, IFile targetFile, CompositeChange change, String originalPackage, String targetPackage) { if (!originalPackage.equals(targetPackage)) { List<PhasedUnit> units = getAllUnits(originalFile.getProject()); for (PhasedUnit pu: units) { // if (!node.getUnit().equals(pu.getUnit())) { IFile file = vfsJ2C() .getIFileVirtualFile(pu.getUnitFile()) .getNativeResource(); if (!file.equals(originalFile) && !file.equals(targetFile)) { TextFileChange tfc = new TextFileChange("Fix Import", file); tfc.setEdit(new MultiTextEdit()); Tree.CompilationUnit rootNode = pu.getCompilationUnit(); refactorImports(node, originalPackage, targetPackage, rootNode, tfc); refactorDocLinks(node, targetPackage, rootNode, tfc); if (tfc.getEdit().hasChildren()) { change.add(tfc); } } // } } } } public static boolean isUnsharedUsedLocally( Tree.Declaration node, IFile originalFile, String originalPackage, String targetPackage) { Declaration dec = node.getDeclarationModel(); if (!dec.isShared() && !originalPackage.equals(targetPackage)) { List<PhasedUnit> units = getAllUnits(originalFile.getProject()); for (PhasedUnit pu: units) { Tree.CompilationUnit cu = pu.getCompilationUnit(); String pn = cu.getUnit() .getPackage() .getNameAsString(); if (pn.equals(originalPackage) && isUsedInUnit(cu, dec)) { return true; } } } return false; } public static void refactorImports(Tree.Declaration node, String originalPackage, String targetPackage, Tree.CompilationUnit cu, TextChange tc) { Declaration dec = node.getDeclarationModel(); String pname = cu.getUnit() .getPackage() .getNameAsString(); boolean inOriginalPackage = pname.equals(originalPackage); boolean inNewPackage = pname.equals(targetPackage); boolean foundOriginal = removeImport(originalPackage, dec, cu, tc, Collections.<String>emptySet()); if (foundOriginal && !inNewPackage || inOriginalPackage && !inNewPackage && isUsedInUnit(cu, dec)) { if (!targetPackage.isEmpty()) { addImport(targetPackage, dec, cu, tc); } } } public static void refactorDocLinks(Tree.Declaration node, final String targetPackage, Tree.CompilationUnit cu, final TextChange tc) { final Declaration dec = node.getDeclarationModel(); cu.visit(new Visitor() { @Override public void visit(Tree.DocLink that) { super.visit(that); if (that.getBase()!=null && that.getBase().equals(dec)) { boolean inTargetPackage = that.getUnit().getPackage() .getQualifiedNameString() .equals(targetPackage); if (that.getPkg() == null) { if (!inTargetPackage) { Region region = DocLinks.nameRegion(that,0); tc.addEdit(new InsertEdit( region.getOffset(), targetPackage + "::")); } } else { Region region = DocLinks.packageRegion(that); if (inTargetPackage) { tc.addEdit(new DeleteEdit( region.getOffset(), region.getLength()+2)); } else { tc.addEdit(new ReplaceEdit( region.getOffset(), region.getLength(), targetPackage)); } } } } }); } public static boolean isUsedInUnit(Tree.CompilationUnit rootNode, final Declaration dec) { class DetectUsageVisitor extends Visitor { boolean detected; @Override public void visit(Tree.Declaration that) { if (!that.getDeclarationModel().equals(dec)) { super.visit(that); } } @Override public void visit(Tree.BaseType that) { TypeDeclaration d = that.getDeclarationModel(); if (d!=null && d.equals(dec)) { detected = true; } } @Override public void visit(Tree.BaseMemberOrTypeExpression that) { Declaration d = that.getDeclaration(); if (d!=null && d.equals(dec)) { detected = true; } } } DetectUsageVisitor duv = new DetectUsageVisitor(); duv.visit(rootNode); boolean used = duv.detected; return used; } public static boolean removeImport(String originalPackage, Declaration dec, Tree.CompilationUnit cu, TextChange tc, Set<String> packages) { boolean foundOriginal = false; Tree.ImportList il = cu.getImportList(); for (Tree.Import imp: il.getImports()) { Referenceable model = imp.getImportPath() .getModel(); if (model!=null) { if (model.getNameAsString() .equals(originalPackage)) { Tree.ImportMemberOrTypeList imtl = imp.getImportMemberOrTypeList(); if (imtl!=null) { List<Tree.ImportMemberOrType> imts = imtl.getImportMemberOrTypes(); for (int j=0; j<imts.size(); j++) { Tree.ImportMemberOrType imt = imts.get(j); Declaration d = imt.getDeclarationModel(); if (d!=null && d.equals(dec)) { int offset; int length; if (j>0) { offset = imts.get(j-1) .getEndIndex(); length = imt.getEndIndex()-offset; } else if (j<imts.size()-1) { offset = imt.getStartIndex(); length = imts.get(j+1) .getStartIndex()-offset; } else { if (packages.contains(originalPackage)) { //we're adding to this import statement, //so don't delete the whole import offset = imt.getStartIndex(); length = imt.getDistance(); } else { offset = imp.getStartIndex(); length = imp.getDistance(); } } tc.addEdit(new DeleteEdit(offset,length)); foundOriginal = true; break; //TODO: return the alias! } } } break; } } } return foundOriginal; } private static void addImport(String targetPackage, Declaration dec, Tree.CompilationUnit cu, TextChange tc) { String name = dec.getName(); String delim = utilJ2C().indents() .getDefaultLineDelimiter( getDocument(tc)); String indent = utilJ2C().indents() .getDefaultIndent(); Tree.ImportList il = cu.getImportList(); for (Tree.Import i: il.getImports()) { Referenceable model = i.getImportPath() .getModel(); if (model!=null) { if (model.getNameAsString() .equals(targetPackage)) { //add to the existing import statement Tree.ImportMemberOrTypeList imtl = i.getImportMemberOrTypeList(); if (imtl!=null) { String addition; int offset; List<Tree.ImportMemberOrType> imts = imtl.getImportMemberOrTypes(); if (imts.isEmpty()) { offset = imtl.getStartIndex()+1; addition = delim + indent + name; int len = imtl.getDistance(); if (len==2) { addition += delim; } } else { offset = imts.get(imts.size()-1) .getEndIndex(); addition = "," + delim + indent + name; } //TODO: the alias! tc.addEdit(new InsertEdit(offset, addition)); } return; } } } //else create a whole new import statement StringBuilder sb = new StringBuilder(); sb.append("import ") .append(escapePackageName(targetPackage)) .append(" {") .append(delim) .append(indent) .append(name) .append(delim) .append("}") .append(delim); tc.addEdit(new InsertEdit(0, sb.toString())); } static TextChange createEditorChange(CeylonEditor editor, IDocument document) { if (editor.isDirty()) { return new DocumentChange("Move from Source File", document); } else { return new TextFileChange("Move from Source File", getFile(editor.getEditorInput())); } } public static boolean canMoveDeclaration(CeylonEditor editor) { Node node = editor.getSelectedNode(); if (node instanceof Tree.Declaration) { Tree.Declaration dn = (Tree.Declaration) node; Declaration d = dn.getDeclarationModel(); return d!=null && d.isToplevel(); } else { return false; } } public static String getDeclarationName(CeylonEditor editor) { Node node = editor.getSelectedNode(); if (node instanceof Tree.Declaration) { Tree.Declaration dn = (Tree.Declaration) node; return dn.getIdentifier().getText(); } else { return null; } } static List<PhasedUnit> getAllUnits(IProject project) { List<PhasedUnit> units = new ArrayList<PhasedUnit>(); units.addAll(getUnits(project)); for (IProject p: project.getReferencingProjects()) { units.addAll(getUnits(p)); } return units; } static IStructuredSelection getSelection() { IEditorPart ed = getCurrentEditor(); if (ed!=null) { IEditorInput input = ed.getEditorInput(); if (input instanceof IFileEditorInput) { IFileEditorInput fei = (IFileEditorInput) input; IFile file = fei.getFile(); return new StructuredSelection(file); } } return null; } //TODO: move to escaping! public static String escapePackageName(String newName) { StringTokenizer tokenizer = new StringTokenizer(newName, "."); StringBuilder builder = new StringBuilder(); while (tokenizer.hasMoreTokens()) { if (builder.length()!=0) { builder.append('.'); } builder.append(escaping_.get_() .escape(tokenizer.nextToken())); } return builder.toString(); } }