package com.redhat.ceylon.eclipse.code.refactor; import static com.redhat.ceylon.compiler.typechecker.tree.TreeUtil.formatPath; import static com.redhat.ceylon.eclipse.code.refactor.MoveUtil.escapePackageName; import static com.redhat.ceylon.eclipse.core.builder.CeylonBuilder.getProjectTypeChecker; import static com.redhat.ceylon.eclipse.java2ceylon.Java2CeylonProxies.vfsJ2C; import static com.redhat.ceylon.eclipse.util.DocLinks.packageName; import static com.redhat.ceylon.eclipse.util.DocLinks.packageRegion; 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.jdt.core.IPackageFragment; import org.eclipse.jdt.internal.corext.refactoring.rename.RenamePackageProcessor; import org.eclipse.jface.text.Region; 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.RefactoringProcessor; import org.eclipse.ltk.core.refactoring.participants.RenameParticipant; import org.eclipse.text.edits.MultiTextEdit; import org.eclipse.text.edits.ReplaceEdit; import com.redhat.ceylon.compiler.typechecker.TypeChecker; import com.redhat.ceylon.compiler.typechecker.context.PhasedUnit; import com.redhat.ceylon.compiler.typechecker.tree.Tree.DocLink; import com.redhat.ceylon.compiler.typechecker.tree.Tree.ImportPath; import com.redhat.ceylon.compiler.typechecker.tree.Visitor; import com.redhat.ceylon.eclipse.core.builder.CeylonNature; import com.redhat.ceylon.ide.common.vfs.FileVirtualFile; public class RenamePackageRefactoringParticipant extends RenameParticipant { private IPackageFragment javaPackageFragment; private static Map<String,TextFileChange> fileChanges = new HashMap<String,TextFileChange>(); @Override protected boolean initialize(Object element) { //Note: When getRenameSubpackages() is true, this participant is // called multiple times, once for each subpackage. This is, // naturally, a massive PITA. javaPackageFragment = (IPackageFragment) element; IProject project = javaPackageFragment.getJavaProject() .getProject(); try { if (!project.hasNature(CeylonNature.NATURE_ID)) { return false; } } catch (CoreException e) { e.printStackTrace(); return false; } RefactoringProcessor processor = getProcessor(); if (processor instanceof RenamePackageProcessor) { RenamePackageProcessor renamePackageProcessor = (RenamePackageProcessor) processor; String patterns = renamePackageProcessor.getFilePatterns(); boolean messingWithCeylonFiles = renamePackageProcessor.getUpdateQualifiedNames() && (patterns.equals("*") || patterns.contains("*.ceylon")); return !messingWithCeylonFiles; } else { //can't happen, I assume return false; } } @Override public String getName() { return "Rename participant for Ceylon source"; } @Override public RefactoringStatus checkConditions(IProgressMonitor pm, CheckConditionsContext context) { return new RefactoringStatus(); } @Override public Change createChange(IProgressMonitor pm) throws CoreException { return null; } @Override public Change createPreChange(IProgressMonitor pm) throws CoreException { //TODO: don't ignore ((RenamePackageProcessor) getProcessor()).getUpdateTextualMatches() //TODO: don't ignore ((RenamePackageProcessor) processor).getUpdateReferences() try { final String newName = getArguments().getNewName(); final String escapedName = escapePackageName(newName); final String oldName = javaPackageFragment.getElementName(); final IProject project = javaPackageFragment.getJavaProject() .getProject(); final List<Change> changes = new ArrayList<Change>(); TypeChecker tc = getProjectTypeChecker(project); if (tc==null) return null; for (PhasedUnit phasedUnit: tc.getPhasedUnits().getPhasedUnits()) { final List<ReplaceEdit> edits = new ArrayList<ReplaceEdit>(); phasedUnit.getCompilationUnit().visit(new Visitor() { @Override public void visit(ImportPath that) { super.visit(that); if (formatPath(that.getIdentifiers()) .equals(oldName)) { edits.add(new ReplaceEdit(that.getStartIndex(), that.getDistance(), escapedName)); } } @Override public void visit(DocLink that) { super.visit(that); String packageName = packageName(that); if (packageName!=null && packageName.equals(oldName)) { Region region = packageRegion(that); edits.add(new ReplaceEdit(region.getOffset(), region.getLength(), newName)); } } }); if (!edits.isEmpty()) { try { 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); } for (ReplaceEdit edit: edits) { change.addEdit(edit); } } catch (Exception e) { e.printStackTrace(); } } } if (changes.isEmpty()) { return null; } else { CompositeChange result = new CompositeChange("Ceylon source changes") { @Override public Change perform(IProgressMonitor pm) throws CoreException { fileChanges.clear(); return super.perform(pm); } }; for (Change change: changes) { result.add(change); } return result; } } catch (Exception e) { e.printStackTrace(); return null; } } }