/**
* 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.jdt.command.refactoring;
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.IResource;
import org.eclipse.core.resources.IResourceChangeEvent;
import org.eclipse.core.resources.IResourceChangeListener;
import org.eclipse.core.resources.IResourceDelta;
import org.eclipse.core.resources.IResourceDeltaVisitor;
import org.eclipse.core.resources.IWorkspace;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
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.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";
protected ThreadLocal<ResourceChangeListener> listener =
new ThreadLocal<ResourceChangeListener>();
/**
* {@inheritDoc}
* @see org.eclim.command.Command#execute(CommandLine)
*/
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();
Refactoring refactoring = createRefactoring(commandLine);
RefactoringStatus status = refactoring.checkAllConditions(
new SubProgressMonitor(monitor, 4));
int stopSeverity = RefactoringCore.getConditionCheckingFailedSeverity();
if (status.getSeverity() >= stopSeverity) {
return status.getEntryWithHighestSeverity().getMessage();
}
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();
listener.set(rcl);
workspace.addResourceChangeListener(rcl);
try{
PerformChangeOperation changeOperation = new PerformChangeOperation(change);
changeOperation.setUndoManager(
RefactoringCore.getUndoManager(), refactoring.getName());
changeOperation.run(new SubProgressMonitor(monitor, 4));
return getChangedFiles();
}finally{
workspace.removeResourceChangeListener(rcl);
}
}catch(RefactorException re){
return re.getMessage();
}
}
/**
* 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 Refactoring createRefactoring(CommandLine commandLine)
throws Exception;
/**
* Builds a list of changed files.
*
* @return The list of changed files.
*/
protected ArrayList<HashMap<String,String>> getChangedFiles()
throws Exception
{
ArrayList<HashMap<String,String>> results =
new ArrayList<HashMap<String,String>>();
for (IResourceDelta delta : listener.get().getResourceDeltas()){
int flags = delta.getFlags();
// the moved_from entry should handle this
if ((flags & IResourceDelta.MOVED_TO) != 0){
continue;
}
HashMap<String,String> result = new HashMap<String,String>();
IResource resource = delta.getResource();
String file = resource.getLocation().toOSString().replace('\\', '/');
if ((flags & IResourceDelta.MOVED_FROM) != 0){
String path = ProjectUtils.getFilePath(
resource.getProject(),
delta.getMovedFromPath().toOSString());
result.put("from", path);
result.put("to", file);
}else{
result.put("file", file);
}
results.add(result);
}
return results;
}
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;
}
/**
* Resource change listener use to collect a list of relevant resource deltas.
*/
protected class ResourceChangeListener
implements IResourceChangeListener, IResourceDeltaVisitor
{
private List<IResourceDelta> deltas = new ArrayList<IResourceDelta>();
/**
* {@inheritDoc}
* @see IResourceChangeListener#resourceChanged(IResourceChangeEvent)
*/
public void resourceChanged(IResourceChangeEvent event)
{
try{
event.getDelta().accept(this);
}catch(CoreException ce){
throw new RuntimeException(ce);
}
}
/**
* {@inheritDoc}
* @see IResourceDeltaVisitor#visit(IResourceDelta)
*/
public boolean visit(IResourceDelta delta)
throws CoreException
{
IResource resource = delta.getResource();
if (delta.getKind() != IResourceDelta.NO_CHANGE && (
resource.getType() == IResource.FILE ||
resource.getType() == IResource.FOLDER))
{
deltas.add(delta);
}
return true;
}
/**
* Gets a list of relevant leaf node resource deltas.
*
* @return list of IResourceDelta.
*/
public List<IResourceDelta> getResourceDeltas()
{
return deltas;
}
}
}