package com.redhat.ceylon.eclipse.code.refactor; import static com.redhat.ceylon.eclipse.code.refactor.MoveUtil.createEditorChange; import static com.redhat.ceylon.eclipse.code.refactor.MoveUtil.getImportText; 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.core.builder.CeylonBuilder.getProjectTypeChecker; import static com.redhat.ceylon.eclipse.util.EditorUtil.getFile; import static com.redhat.ceylon.eclipse.java2ceylon.Java2CeylonProxies.utilJ2C; import java.util.HashSet; import java.util.Map; 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.jdt.core.IPackageFragment; 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.text.edits.DeleteEdit; import org.eclipse.text.edits.MultiTextEdit; import com.redhat.ceylon.compiler.typechecker.TypeChecker; 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.eclipse.code.editor.CeylonEditor; import com.redhat.ceylon.model.typechecker.model.Declaration; import com.redhat.ceylon.model.typechecker.model.Package; public class MoveToNewUnitRefactoring 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; private IProject targetProject; private IPackageFragment targetPackage; private boolean includePreamble; private int offset; public void setTargetFile(IFile targetFile) { this.targetFile = targetFile; } public void setTargetPackage(IPackageFragment targetPackage) { this.targetPackage = targetPackage; } public void setIncludePreamble(boolean include) { includePreamble = include; } public void setTargetProject(IProject targetProject) { this.targetProject = targetProject; } public Tree.Declaration getNode() { return node; } public MoveToNewUnitRefactoring(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 New 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 already exists"); } Package originalPackage = rootNode.getUnit().getPackage(); String originalPackageName = originalPackage.getNameAsString(); String targetPackageName = targetPackage.getElementName(); HashSet<String> packages = new HashSet<String>(); Map<Declaration, String> imports = MoveUtil.getImports(node, targetPackage.getElementName(), null, 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()); } TypeChecker tc = getProjectTypeChecker(targetProject); if (tc!=null) { for (PhasedUnit phasedUnit: tc.getPhasedUnits().getPhasedUnits()) { if (phasedUnit.getPackage().getNameAsString() .equals(targetPackage.getElementName())) { if (phasedUnit.getPackage() .getModule() .getPackage(packageName) ==null) { refactoringStatus.addWarning( "moved declaration depends on declaration in unimported module: " + d.getName() + " in module " + p.getModule().getNameAsString()); } break; } } } } } 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 { String originalPackageName = rootNode.getUnit() .getPackage().getNameAsString(); String targetPackageName = targetPackage.getElementName(); int start = node.getStartIndex(); int length = node.getDistance(); String delim = utilJ2C().indents() .getDefaultLineDelimiter(document); CompositeChange change = new CompositeChange("Move to New Source File"); 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; } //TODO: should we use this alternative when original==moved? // String importText = imports(node, cu.getImportList(), document); String importText = getImportText(node, targetPackageName, delim); String text = importText.isEmpty() ? contents : importText + delim + contents; offset = importText.isEmpty() ? 0 : (importText + delim).length(); CreateUnitChange newUnitChange = new CreateUnitChange(targetFile, includePreamble, text, targetProject, "Create source file '" + targetFile.getProjectRelativePath() + "'"); change.add(newUnitChange); // newUnitChange.setTextType("ceylon"); 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 int getOffset() { return offset; } public IPath getTargetPath() { return targetFile.getFullPath(); } }