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.getProjectTypeChecker; import static com.redhat.ceylon.eclipse.java2ceylon.Java2CeylonProxies.vfsJ2C; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IFolder; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.OperationCanceledException; import org.eclipse.jdt.core.ICompilationUnit; import org.eclipse.jdt.core.IJavaElement; import org.eclipse.jdt.core.IMember; import org.eclipse.jdt.core.IType; import org.eclipse.jdt.core.JavaCore; import org.eclipse.jdt.core.JavaModelException; 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.TextFileChange; import org.eclipse.ltk.core.refactoring.participants.CheckConditionsContext; import org.eclipse.ltk.core.refactoring.participants.MoveParticipant; import org.eclipse.ltk.core.refactoring.participants.MoveProcessor; import org.eclipse.ltk.core.refactoring.participants.RefactoringProcessor; 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.Tree; import com.redhat.ceylon.compiler.typechecker.tree.Tree.BaseMemberOrTypeExpression; import com.redhat.ceylon.compiler.typechecker.tree.Tree.BaseType; import com.redhat.ceylon.compiler.typechecker.tree.Tree.ImportMemberOrType; import com.redhat.ceylon.compiler.typechecker.tree.Visitor; import com.redhat.ceylon.eclipse.code.correct.correctJ2C; import com.redhat.ceylon.eclipse.core.builder.CeylonNature; import com.redhat.ceylon.eclipse.util.EditorUtil; import com.redhat.ceylon.ide.common.model.ProjectSourceFile; import com.redhat.ceylon.ide.common.vfs.FileVirtualFile; import com.redhat.ceylon.model.typechecker.model.Declaration; import com.redhat.ceylon.model.typechecker.model.Unit; public class MoveFileRefactoringParticipant extends MoveParticipant { private IFile file; private static Map<String,TextFileChange> fileChanges = new HashMap<String,TextFileChange>(); private static List<IResource> movingFiles = new ArrayList<IResource>(); @Override protected boolean initialize(Object element) { file = (IFile) element; try { if (!file.getProject().hasNature(CeylonNature.NATURE_ID)) { return false; } } catch (CoreException e) { e.printStackTrace(); return false; } RefactoringProcessor processor = getProcessor(); if (processor instanceof MoveProcessor) { MoveProcessor moveProcessor = (MoveProcessor) processor; for (Object e: moveProcessor.getElements()) { IResource r = null; if (e instanceof IResource) { r = (IResource) e; } else if (e instanceof ICompilationUnit) { r = ((ICompilationUnit) e).getResource(); } if (r!=null) { movingFiles.add(r); } } return file.getFileExtension()!=null && (file.getFileExtension().equals("ceylon") || file.getFileExtension().equals("java")); } else { return false; } } @Override public String getName() { return "Move file participant for Ceylon source"; } @Override public RefactoringStatus checkConditions(IProgressMonitor pm, CheckConditionsContext context) throws OperationCanceledException { return new RefactoringStatus(); } @Override public Change createChange(IProgressMonitor pm) throws CoreException, OperationCanceledException { return null; } @Override public Change createPreChange(IProgressMonitor pm) throws CoreException, OperationCanceledException { try { IProject project = file.getProject(); Object destination = getArguments().getDestination(); if (! (destination instanceof IFolder)) { return null; } IFolder folder = (IFolder) destination; String newName = folder.getProjectRelativePath() .removeFirstSegments(1) .toPortableString() .replace('/', '.'); String movedRelFilePath = file.getProjectRelativePath() .removeFirstSegments(1) .toPortableString(); String movedRelPath = file.getParent() .getProjectRelativePath() .removeFirstSegments(1) .toPortableString(); String oldName = movedRelPath.replace('/', '.'); List<Change> changes = new ArrayList<Change>(); if (file.getFileExtension().equals("java")) { updateRefsToMovedJavaFile(project, newName, oldName, changes); } else { TypeChecker tc = getProjectTypeChecker(project); if (tc==null) return null; PhasedUnit movedPhasedUnit = tc.getPhasedUnitFromRelativePath(movedRelFilePath); if (movedPhasedUnit==null) { return null; } List<Declaration> declarations = movedPhasedUnit.getDeclarations(); if (newName.equals(oldName)) return null; updateRefsFromMovedCeylonFile(project, newName, oldName, changes, movedPhasedUnit, declarations); updateRefsToMovedCeylonFile(project, newName, oldName, changes, movedPhasedUnit, declarations); } if (changes.isEmpty()) return null; CompositeChange result = new CompositeChange("Ceylon source changes") { @Override public Change perform(IProgressMonitor pm) throws CoreException { fileChanges.clear(); movingFiles.clear(); return super.perform(pm); } }; for (Change change: changes) { result.add(change); } return result; } catch (Exception e) { e.printStackTrace(); return null; } } protected void updateRefsFromMovedCeylonFile( final IProject project, final String newName, final String oldName, final List<Change> changes, final PhasedUnit movedPhasedUnit, final List<Declaration> declarations) { final Map<Declaration,String> imports = new HashMap<Declaration,String>(); movedPhasedUnit.getCompilationUnit().visit(new Visitor() { @Override public void visit(ImportMemberOrType that) { super.visit(that); visitIt(that.getIdentifier(), that.getDeclarationModel()); } // @Override // public void visit(QualifiedMemberOrTypeExpression that) { // super.visit(that); // visitIt(that.getIdentifier(), that.getDeclaration()); // } @Override public void visit(BaseMemberOrTypeExpression that) { super.visit(that); visitIt(that.getIdentifier(), that.getDeclaration()); } @Override public void visit(BaseType that) { super.visit(that); visitIt(that.getIdentifier(), that.getDeclarationModel()); } // @Override // public void visit(QualifiedType that) { // super.visit(that); // visitIt(that.getIdentifier(), that.getDeclarationModel()); // } protected void visitIt(Tree.Identifier id, Declaration dec) { if (dec!=null && !declarations.contains(dec)) { Unit unit = dec.getUnit(); if (unit instanceof ProjectSourceFile && movingFiles.contains(((ProjectSourceFile<IProject, IResource, IFolder, IFile>) unit).getResourceFile())) { //also moving } else if (unit.getPackage().equals(movedPhasedUnit.getPackage())) { imports.put(dec, id.getText()); } } } //TODO: DocLinks!! }); collectEditsToMovedFile(newName, oldName, changes, movedPhasedUnit, imports); } protected void updateRefsToMovedCeylonFile(final IProject project, final String newName, final String oldName, final List<Change> changes, PhasedUnit movedPhasedUnit, final List<Declaration> declarations) { if (!getArguments().getUpdateReferences()) return; TypeChecker tc = getProjectTypeChecker(project); if (tc==null) return; for (PhasedUnit phasedUnit: tc.getPhasedUnits().getPhasedUnits()) { if (phasedUnit==movedPhasedUnit || phasedUnit.getUnit() instanceof ProjectSourceFile && movingFiles.contains(((ProjectSourceFile) phasedUnit.getUnit()).getResourceFile())) { continue; } final Map<Declaration,String> imports = new HashMap<Declaration,String>(); phasedUnit.getCompilationUnit().visit(new Visitor() { @Override public void visit(ImportMemberOrType that) { super.visit(that); visitIt(that.getIdentifier(), that.getDeclarationModel()); } // @Override // public void visit(QualifiedMemberOrTypeExpression that) { // super.visit(that); // visitIt(that.getIdentifier(), that.getDeclaration()); // } @Override public void visit(BaseMemberOrTypeExpression that) { super.visit(that); visitIt(that.getIdentifier(), that.getDeclaration()); } @Override public void visit(BaseType that) { super.visit(that); visitIt(that.getIdentifier(), that.getDeclarationModel()); } // @Override // public void visit(QualifiedType that) { // super.visit(that); // visitIt(that.getIdentifier(), that.getDeclarationModel()); // } protected void visitIt(Tree.Identifier id, Declaration dec) { if (dec!=null && declarations.contains(dec)) { imports.put(dec, id.getText()); } } //TODO: DocLinks!! }); collectEdits(newName, oldName, changes, phasedUnit, imports); } } protected void updateRefsToMovedJavaFile(final IProject project, final String newName, final String oldName, final List<Change> changes) throws JavaModelException { if (!getArguments().getUpdateReferences()) return; ICompilationUnit jcu = (ICompilationUnit) JavaCore.create(file); final IType[] types = jcu.getTypes(); TypeChecker tc = getProjectTypeChecker(project); if (tc==null) return; for (PhasedUnit phasedUnit: tc.getPhasedUnits().getPhasedUnits()) { final Map<Declaration,String> imports = new HashMap<Declaration,String>(); phasedUnit.getCompilationUnit().visit(new Visitor() { @Override public void visit(ImportMemberOrType that) { super.visit(that); visitIt(that.getIdentifier(), that.getDeclarationModel()); } // @Override // public void visit(QualifiedMemberOrTypeExpression that) { // super.visit(that); // visitIt(that.getIdentifier(), that.getDeclaration()); // } @Override public void visit(BaseMemberOrTypeExpression that) { super.visit(that); visitIt(that.getIdentifier(), that.getDeclaration()); } @Override public void visit(BaseType that) { super.visit(that); visitIt(that.getIdentifier(), that.getDeclarationModel()); } // @Override // public void visit(QualifiedType that) { // super.visit(that); // visitIt(that.getIdentifier(), that.getDeclarationModel()); // } protected void visitIt(Tree.Identifier id, Declaration dec) { for (IType type: types) { if (dec!=null && dec.getQualifiedNameString() .equals(getQualifiedName(type))) { imports.put(dec, id.getText()); } } } protected String getQualifiedName(IMember dec) { IJavaElement parent = dec.getParent(); if (parent instanceof ICompilationUnit) { return parent.getParent().getElementName() + "::" + dec.getElementName(); } else if (dec.getDeclaringType()!=null) { return getQualifiedName(dec.getDeclaringType()) + "." + dec.getElementName(); } else { return "@"; } } }); collectEdits(newName, oldName, changes, phasedUnit, imports); } } private void collectEditsToMovedFile(String newName, String oldName, List<Change> changes, PhasedUnit movedPhasedUnit, Map<Declaration, String> imports) { try { FileVirtualFile<IProject, IResource, IFolder, IFile> virtualFile = vfsJ2C().getIFileVirtualFile( movedPhasedUnit.getUnitFile()); IFile file = virtualFile.getNativeResource(); String path = file.getProjectRelativePath().toPortableString(); TextFileChange change = fileChanges.get(path); if (change==null) { change = new TextFileChange(file.getName(), file); change.setEdit(new MultiTextEdit()); changes.add(change); fileChanges.put(path, change); } Tree.CompilationUnit cu = movedPhasedUnit.getCompilationUnit(); if (!imports.isEmpty()) { new correctJ2C().importEdits(change, cu, imports.keySet(), imports.values(), EditorUtil.getDocument(change)); } Tree.Import toDelete = importProposals().findImportNode(cu, newName); if (toDelete!=null) { change.addEdit(new DeleteEdit(toDelete.getStartIndex(), toDelete.getDistance())); } } catch (Exception e) { e.printStackTrace(); } } private void collectEdits(String newName, String oldName, List<Change> changes, PhasedUnit phasedUnit, Map<Declaration, String> imports) { try { Tree.CompilationUnit cu = phasedUnit.getCompilationUnit(); if (!imports.isEmpty()) { FileVirtualFile<IProject, IResource, IFolder, IFile> virtualFile = vfsJ2C().getIFileVirtualFile(phasedUnit.getUnitFile()); IFile file = virtualFile.getNativeResource(); String path = file.getProjectRelativePath().toPortableString(); TextFileChange change = fileChanges.get(path); if (change==null) { change = new TextFileChange(file.getName(), file); change.setEdit(new MultiTextEdit()); changes.add(change); fileChanges.put(path, change); } new correctJ2C().importEditForMove(change, cu, imports.keySet(), imports.values(), newName, oldName, EditorUtil.getDocument(change)); } } catch (Exception e) { e.printStackTrace(); } } }