package com.redhat.ceylon.eclipse.code.refactor; import static com.redhat.ceylon.eclipse.code.refactor.MoveUtil.addImportEdits; import static com.redhat.ceylon.eclipse.code.refactor.MoveUtil.createEditorChange; import static com.redhat.ceylon.eclipse.code.refactor.MoveUtil.getImports; import static com.redhat.ceylon.eclipse.code.refactor.MoveUtil.isUnsharedUsedLocally; import static com.redhat.ceylon.eclipse.code.refactor.MoveUtil.refactorDocLinks; import static com.redhat.ceylon.eclipse.code.refactor.MoveUtil.refactorImports; import static com.redhat.ceylon.eclipse.code.refactor.MoveUtil.refactorProjectImportsAndDocLinks; import static com.redhat.ceylon.eclipse.code.refactor.MoveUtil.removeImport; import static com.redhat.ceylon.eclipse.core.builder.CeylonBuilder.getProjectTypeChecker; import static com.redhat.ceylon.eclipse.java2ceylon.Java2CeylonProxies.utilJ2C; import static com.redhat.ceylon.eclipse.util.EditorUtil.getFile; import java.util.HashSet; import java.util.Map; import java.util.Set; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IProject; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.OperationCanceledException; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.IDocument; import org.eclipse.ltk.core.refactoring.Change; import org.eclipse.ltk.core.refactoring.CompositeChange; import org.eclipse.ltk.core.refactoring.RefactoringStatus; 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 com.redhat.ceylon.compiler.typechecker.tree.Node; import com.redhat.ceylon.compiler.typechecker.tree.Tree; import com.redhat.ceylon.eclipse.code.editor.CeylonEditor; import com.redhat.ceylon.model.typechecker.model.Declaration; import com.redhat.ceylon.model.typechecker.model.Package; public class MoveToUnitRefactoring extends Refactoring { private final CeylonEditor editor; private final Tree.CompilationUnit rootNode; private final Tree.Declaration node; private final IFile originalFile; private final IDocument document; private IFile targetFile; public void setTargetFile(IFile targetFile) { this.targetFile = targetFile; } public IFile getOriginalFile() { return originalFile; } public Tree.Declaration getNode() { return node; } public MoveToUnitRefactoring(CeylonEditor ceylonEditor) { editor = ceylonEditor; rootNode = editor.getParseController() .getTypecheckedRootNode(); document = editor.getDocumentProvider() .getDocument(editor.getEditorInput()); originalFile = getFile(editor.getEditorInput()); if (rootNode!=null) { Node node = editor.getSelectedNode(); if (node instanceof Tree.Declaration) { this.node = (Tree.Declaration) node; } else { this.node = null; } } else { this.node = null; } } @Override public boolean getEnabled() { return node!=null; } @Override public String getName() { return "Move to Source File"; } @Override public RefactoringStatus checkInitialConditions(IProgressMonitor pm) throws CoreException, OperationCanceledException { return new RefactoringStatus(); } @Override public RefactoringStatus checkFinalConditions(IProgressMonitor pm) throws CoreException, OperationCanceledException { RefactoringStatus refactoringStatus = new RefactoringStatus(); if (!targetFile.exists()) { refactoringStatus.addError("source file does not exist"); } Tree.CompilationUnit targetRootNode = getTargetRootNode(); Package targetPackage = targetRootNode.getUnit() .getPackage(); Package originalPackage = rootNode.getUnit() .getPackage(); String originalPackageName = originalPackage.getNameAsString(); String targetPackageName = targetPackage.getNameAsString(); HashSet<String> packages = new HashSet<String>(); Map<Declaration, String> imports = getImports(node, targetPackageName, targetRootNode, packages); for (Declaration d: imports.keySet()) { Package p = d.getUnit().getPackage(); String packageName = p.getNameAsString(); if (packageName.isEmpty()) { refactoringStatus.addWarning( "moved declaration depends on declaration in the default package: " + d.getName()); } else { if (!d.isShared() && !packageName.equals(targetPackageName)) { refactoringStatus.addWarning( "moved declaration depends on unshared declaration: " + d.getName()); } if (targetPackage.getModule() .getPackage(packageName) ==null) { refactoringStatus.addWarning( "moved declaration depends on declaration in unimported module: " + d.getName() + " in module " + p.getModule().getNameAsString()); } } } if (isUnsharedUsedLocally(node, originalFile, originalPackageName, targetPackageName)) { if (targetPackageName.isEmpty()) { refactoringStatus.addWarning( "moving declaration used locally to default package"); } else if (originalPackage.getModule().getPackage(targetPackageName)==null) { refactoringStatus.addWarning( "moving declaration used locally to unimported module"); } } return refactoringStatus; } @Override public Change createChange(IProgressMonitor pm) throws CoreException, OperationCanceledException { Tree.CompilationUnit targetRootNode = getTargetRootNode(); String originalPackageName = rootNode.getUnit() .getPackage() .getNameAsString(); String targetPackageName = targetRootNode.getUnit() .getPackage() .getNameAsString(); Declaration dec = node.getDeclarationModel(); int start = node.getStartIndex(); int length = node.getDistance(); CompositeChange change = new CompositeChange("Move to Source File"); TextChange targetUnitChange = new TextFileChange("Move to Source File", targetFile); targetUnitChange.setEdit(new MultiTextEdit()); IDocument targetUnitDocument = targetUnitChange.getCurrentDocument(null); String contents; try { contents = document.get(start, length); } catch (BadLocationException e) { e.printStackTrace(); throw new OperationCanceledException(); } if (isUnsharedUsedLocally(node, originalFile, originalPackageName, targetPackageName)) { contents = "shared " + contents; } String delim = utilJ2C().indents() .getDefaultLineDelimiter(targetUnitDocument); String text = delim + contents; Set<String> packages = new HashSet<String>(); addImportEdits(node, targetUnitChange, targetUnitDocument, targetRootNode, packages, dec); removeImport(originalPackageName, dec, targetRootNode, targetUnitChange, packages); targetUnitChange.addEdit(new InsertEdit( targetUnitDocument.getLength(), text)); targetUnitChange.setTextType("ceylon"); change.add(targetUnitChange); TextChange originalUnitChange = createEditorChange(editor, document); originalUnitChange.setEdit(new MultiTextEdit()); refactorImports(node, originalPackageName, targetPackageName, rootNode, originalUnitChange); refactorDocLinks(node, targetPackageName, rootNode, originalUnitChange); originalUnitChange.addEdit(new DeleteEdit(start, length)); originalUnitChange.setTextType("ceylon"); change.add(originalUnitChange); refactorProjectImportsAndDocLinks(node, originalFile, targetFile, change, originalPackageName, targetPackageName); //TODO: DocLinks return change; } public Tree.CompilationUnit getTargetRootNode() { IProject project = targetFile.getProject(); String path = targetFile.getProjectRelativePath() .removeFirstSegments(1) .toPortableString(); return getProjectTypeChecker(project) .getPhasedUnitFromRelativePath(path) .getCompilationUnit(); } public int getOffset() { return 0; //TODO!!! } public IPath getTargetPath() { return targetFile.getFullPath(); } }