/** * Copyright (C) 2005 - 2012 Eric Van Dewoestine * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.eclim.plugin.core.command.refactoring; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import org.eclim.command.CommandLine; import org.eclim.command.Options; import org.eclim.plugin.core.command.AbstractCommand; import org.eclim.plugin.core.project.ProjectManagement; import org.eclim.plugin.core.util.ProjectUtils; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IWorkspace; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.core.runtime.SubProgressMonitor; import org.eclipse.ltk.core.refactoring.Change; import org.eclipse.ltk.core.refactoring.CompositeChange; import org.eclipse.ltk.core.refactoring.PerformChangeOperation; import org.eclipse.ltk.core.refactoring.Refactoring; import org.eclipse.ltk.core.refactoring.RefactoringCore; import org.eclipse.ltk.core.refactoring.RefactoringStatus; import org.eclipse.ltk.core.refactoring.RefactoringStatusEntry; import org.eclipse.ltk.core.refactoring.TextFileChange; /** * Abstract super command for refactoring commands. * * @author Eric Van Dewoestine */ public abstract class AbstractRefactorCommand extends AbstractCommand { private static final String PREVIEW_OPTION = "v"; private static final String DIFF_OPTION = "d"; @Override public Object execute(CommandLine commandLine) throws Exception { // refresh the projects that may be affected to ensure eclipse is aware of // the current state of every file that could potentially be changed. if (!commandLine.hasOption(PREVIEW_OPTION)){ String projectName = commandLine.getValue(Options.PROJECT_OPTION); IProject project = ProjectUtils.getProject(projectName, true); ProjectManagement.refresh(project, commandLine); IProject[] references = project.getReferencingProjects(); for (IProject p : references){ ProjectManagement.refresh(p, commandLine); } } try{ NullProgressMonitor monitor = new NullProgressMonitor(); Refactor refactor = createRefactoring(commandLine); Refactoring refactoring = refactor.refactoring; RefactoringStatus status = refactoring.checkAllConditions( new SubProgressMonitor(monitor, 4)); int stopSeverity = RefactoringCore.getConditionCheckingFailedSeverity(); if (status.getSeverity() >= stopSeverity) { throw new RefactorException(status); } Change change = refactoring.createChange(new SubProgressMonitor(monitor, 2)); change.initializeValidationData(new SubProgressMonitor(monitor, 1)); // preview if (commandLine.hasOption(PREVIEW_OPTION)){ // preview a specific file if (commandLine.hasOption(DIFF_OPTION)){ return previewChange(change, commandLine.getValue("d")); } HashMap<String,Object> preview = new HashMap<String,Object>(); String previewOpt = "-" + PREVIEW_OPTION; String[] args = commandLine.getArgs(); StringBuffer apply = new StringBuffer(); for (String arg : args){ if (arg.equals(previewOpt)){ continue; } if (apply.length() > 0){ apply.append(' '); } if (arg.startsWith("-")){ apply.append(arg); }else{ apply.append('"').append(arg).append('"'); } } // the command to apply the change minus the editor + pretty options. preview.put("apply", apply.toString() .replaceFirst("-" + Options.EDITOR_OPTION + "\\s\"\\w+\" ", "") .replaceFirst("-" + Options.PRETTY_OPTION + ' ', "")); preview.put("changes", previewChanges(change)); return preview; } IWorkspace workspace = ResourcesPlugin.getWorkspace(); ResourceChangeListener rcl = new ResourceChangeListener(); workspace.addResourceChangeListener(rcl); try{ PerformChangeOperation changeOperation = new PerformChangeOperation(change); // passing in refactor.name to the change op doesn't seem to do the // trick, so lets force our name on the change since the undo manager // will use the change's name if label is null (which it shouldn't be, // but is, hence this hack). if (change instanceof CompositeChange){ try{ Field fName = CompositeChange.class.getDeclaredField("fName"); fName.setAccessible(true); fName.set(change, refactor.name); }catch(NoSuchFieldException nsfe){ // change doesn't have the expected fName field. } } changeOperation.setUndoManager( RefactoringCore.getUndoManager(), change.getName()); changeOperation.run(new SubProgressMonitor(monitor, 4)); return rcl.getChangedFiles(); }finally{ workspace.removeResourceChangeListener(rcl); } }catch(RefactorException re){ HashMap<String,List<String>> result = new HashMap<String,List<String>>(); List<String> errors = new ArrayList<String>(); if (re.getMessage() != null){ errors.add(re.getMessage()); } RefactoringStatus status = re.getStatus(); if (status != null){ for (RefactoringStatusEntry entry : status.getEntries()){ String message = entry.getMessage(); if (!errors.contains(message) && !message.startsWith("Found potential matches")) { errors.add(message); } } } result.put("errors", errors); return result; } } /** * Method to be ovrriden by subclasses to get the Change representing the * refactoring. * * @param commandLine The original command line. * @return The refactoring change. */ public abstract Refactor createRefactoring(CommandLine commandLine) throws Exception; private ArrayList<HashMap<String,String>> previewChanges(Change change) throws Exception { ArrayList<HashMap<String,String>> results = new ArrayList<HashMap<String,String>>(); if (change instanceof CompositeChange){ for (Change c : ((CompositeChange)change).getChildren()){ results.addAll(previewChanges(c)); } }else{ HashMap<String,String> result = new HashMap<String,String>(); if (change instanceof TextFileChange){ TextFileChange text = (TextFileChange)change; result.put("type", "diff"); result.put("file", text.getFile().getLocation().toOSString().replace('\\', '/')); }else{ result.put("type", "other"); result.put("message", change.toString()); } results.add(result); } return results; } private String previewChange(Change change, String file) throws Exception { if (change instanceof CompositeChange){ for (Change c : ((CompositeChange)change).getChildren()){ String preview = previewChange(c, file); if(preview != null){ return preview; } } }else{ if (change instanceof TextFileChange){ TextFileChange text = (TextFileChange)change; String path = text.getFile().getLocation() .toOSString().replace('\\', '/'); if (path.equals(file)){ return text.getPreviewContent(new NullProgressMonitor()); } } } return null; } }