/*******************************************************************************
* Copyright (c) 2005, 2015, 2016 Zend Technologies and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Zend Technologies - initial API and implementation
*******************************************************************************/
package org.eclipse.php.refactoring.core.rename;
import java.io.File;
import java.util.*;
import java.util.Map.Entry;
import org.eclipse.core.resources.*;
import org.eclipse.core.runtime.*;
import org.eclipse.dltk.core.*;
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.participants.CheckConditionsContext;
import org.eclipse.ltk.core.refactoring.resource.RenameResourceChange;
import org.eclipse.osgi.util.NLS;
import org.eclipse.php.core.PHPVersion;
import org.eclipse.php.core.ast.nodes.ASTParser;
import org.eclipse.php.core.ast.nodes.Program;
import org.eclipse.php.core.libfolders.LibraryFolderManager;
import org.eclipse.php.core.libfolders.RenameLibraryFolderChange;
import org.eclipse.php.core.project.ProjectOptions;
import org.eclipse.php.internal.core.includepath.IncludePath;
import org.eclipse.php.internal.core.includepath.IncludePathManager;
import org.eclipse.php.refactoring.core.PhpRefactoringCoreMessages;
import org.eclipse.php.refactoring.core.changes.*;
import org.eclipse.php.refactoring.core.move.MoveUtils;
import org.eclipse.php.refactoring.core.rename.logic.RenameIncludeFolder;
import org.eclipse.php.refactoring.core.utils.RefactoringUtility;
import org.eclipse.text.edits.MultiTextEdit;
public class RenameFolderProcessor extends AbstraceRenameResourceProcessor implements IReferenceUpdating {
public static final String RENAME_FOLDER_PROCESSOR_NAME = PhpRefactoringCoreMessages
.getString("RenameResourceProcessor.0"); //$NON-NLS-1$
private static final String REFACTORING_ACTION_INTERNAL_ERROR = PhpRefactoringCoreMessages
.getString("RenameProcessorBase.internalerror"); //$NON-NLS-1$
private static final String ID_RENAME_FOLDER = "php.refactoring.ui.rename.folder"; //$NON-NLS-1$
private Map<String, String> attributes = new HashMap<String, String>();
/**
* holds wether or not we want to change also the inlined text
*/
private boolean isUpdateTextualMatches;
private ArrayList<IBuildpathEntry> newBuildEntries;
private ArrayList<IBuildpathEntry> newIncludePathEntries;
private List<IBuildpathEntry> oldBuildEntries;
private List<IBuildpathEntry> oldIncludePath;
public RenameFolderProcessor(IContainer container) {
super(container);
attributes.put(IReferenceUpdating.NEEDUPDATECLASSNAME, Boolean.FALSE.toString());
}
@Override
public RefactoringStatus getRefactoringStatus(IFile key, Program program) {
return null;
}
/*
* (non-Javadoc)
*
* @seeorg.eclipse.ltk.core.refactoring.participants.RefactoringProcessor#
* createChange(org.eclipse.core.runtime.IProgressMonitor)
*/
@Override
public Change createChange(IProgressMonitor pm) throws CoreException, OperationCanceledException {
CompositeChange rootChange = new NoReverseCompositeChange(RENAME_FOLDER_PROCESSOR_NAME);
rootChange.markAsSynthetic();
try {
pm.beginTask(PhpRefactoringCoreMessages.getString("RenameFolderProcessor.RenamingFile"), 100); //$NON-NLS-1$
if (pm.isCanceled())
throw new OperationCanceledException();
if (getUpdateReferences()) {
createRenameTextChanges(pm, rootChange);
createRenameReferenceChange(pm, rootChange);
createRenameLibraryFolderChange(rootChange);
} else {
createFileRenameChange(rootChange);
}
} finally {
pm.done();
}
return rootChange;
}
private void createRenameLibraryFolderChange(CompositeChange rootChange) {
LibraryFolderManager lfm = LibraryFolderManager.getInstance();
if (lfm.isInLibraryFolder(resource)) {
IFolder folder = (IFolder) resource;
IFolder newFolder = resource.getParent().getFolder(Path.fromPortableString(fNewElementName));
RenameLibraryFolderChange change = new RenameLibraryFolderChange(folder, newFolder);
rootChange.add(change);
}
}
private void createRenameReferenceChange(IProgressMonitor pm, CompositeChange rootChange) throws CoreException {
pm.beginTask(PhpRefactoringCoreMessages.getString("RenameFolderProcessor.0"), 0); //$NON-NLS-1$
pm.setTaskName(PhpRefactoringCoreMessages.getString("RenameFolderProcessor.1")); //$NON-NLS-1$
IPath source = resource.getFullPath().removeLastSegments(1);
String oldName = resource.getName();
IPath dest = getNewFilePath();
RenameConfigurationChange confChange = new RenameConfigurationChange(source.removeLastSegments(0),
dest.removeLastSegments(0), oldName, fNewElementName);
rootChange.add(confChange);
if (pm.isCanceled())
throw new OperationCanceledException();
createFileRenameChange(rootChange);
if (resource instanceof IProject) {
IProject[] referencing = ((IProject) resource).getReferencingProjects();
if (referencing != null && referencing.length > 0) {
ProjectReferenceChange change = new ProjectReferenceChange(resource.getName(), getNewElementName(),
referencing);
rootChange.add(change);
}
}
collectBuildPath();
RenameBuildAndIcludePathChange biChange = new RenameBuildAndIcludePathChange(source, dest, oldName,
fNewElementName, oldBuildEntries, newBuildEntries, oldIncludePath, newIncludePathEntries);
if (newBuildEntries.size() > 0 || newIncludePathEntries.size() > 0) {
rootChange.add(biChange);
}
collectBrakePoint();
if (fBreakpoints.getKeys().size() > 0) {
RenameBreackpointChange breakePointchange = new RenameBreackpointChange(source, dest, oldName,
fNewElementName, fBreakpoints, fBreakpointAttributes);
rootChange.add(breakePointchange);
}
}
private void collectBuildPath() throws ModelException {
IProject project = resource.getProject();
IScriptProject projrct = DLTKCore.create(project);
IPath filePath = resource.getFullPath();
oldBuildEntries = Arrays.asList(projrct.readRawBuildpath());
newBuildEntries = new ArrayList<IBuildpathEntry>();
newBuildEntries.addAll(oldBuildEntries);
for (int i = 0; i < oldBuildEntries.size(); i++) {
IBuildpathEntry fEntryToChange = oldBuildEntries.get(i);
IPath entryPath = fEntryToChange.getPath();
int mattchedPath = entryPath.matchingFirstSegments(filePath);
IPath truncatedPath = entryPath.uptoSegment(mattchedPath);
IPath remaingPath = entryPath.removeFirstSegments(mattchedPath);
IPath newPath;
if (mattchedPath == filePath.segmentCount()) {
newPath = truncatedPath.removeLastSegments(1).append(fNewElementName).append(remaingPath);
IBuildpathEntry newEntry = RefactoringUtility.createNewBuildpathEntry(fEntryToChange, newPath, filePath,
fNewElementName);
newBuildEntries.remove(fEntryToChange);
newBuildEntries.add(newEntry);
} else {
IBuildpathEntry newEntry = RefactoringUtility.createNewBuildpathEntry(fEntryToChange,
fEntryToChange.getPath(), filePath, fNewElementName);
newBuildEntries.remove(fEntryToChange);
newBuildEntries.add(newEntry);
}
}
oldIncludePath = new ArrayList<IBuildpathEntry>();
newIncludePathEntries = new ArrayList<IBuildpathEntry>();
List<IncludePath> includePathEntries = Arrays.asList(IncludePathManager.getInstance().getIncludePaths(project));
for (IncludePath entry : includePathEntries) {
Object includePathEntry = entry.getEntry();
IResource resource = null;
if (!(includePathEntry instanceof IBuildpathEntry)) {
resource = (IResource) includePathEntry;
IPath entryPath = resource.getFullPath();
IBuildpathEntry oldEntry = RefactoringUtility.createNewBuildpathEntry(IBuildpathEntry.BPE_SOURCE,
entryPath);
oldIncludePath.add((IBuildpathEntry) oldEntry);
if (filePath.isPrefixOf(entryPath) || entryPath.equals(filePath)) {
int mattchedPath = entryPath.matchingFirstSegments(filePath);
IPath truncatedPath = entryPath.uptoSegment(mattchedPath);
IPath remaingPath = entryPath.removeFirstSegments(mattchedPath);
IPath newPath;
if (mattchedPath == filePath.segmentCount()) {
newPath = truncatedPath.removeLastSegments(1).append(fNewElementName).append(remaingPath);
} else {
newPath = truncatedPath.append(fNewElementName).append(remaingPath);
}
IBuildpathEntry newEntry = RefactoringUtility.createNewBuildpathEntry(IBuildpathEntry.BPE_SOURCE,
newPath);
newIncludePathEntries.add(newEntry);
} else {
IBuildpathEntry newEntry = RefactoringUtility.createNewBuildpathEntry(IBuildpathEntry.BPE_SOURCE,
entryPath);
newIncludePathEntries.add(newEntry);
}
} else {
newIncludePathEntries.add((IBuildpathEntry) includePathEntry);
oldIncludePath.add((IBuildpathEntry) includePathEntry);
}
}
}
/**
* Derive the change
*/
private void createRenameTextChanges(IProgressMonitor pm, CompositeChange rootChange)
throws CoreException, OperationCanceledException {
try {
pm.beginTask(RenameClassProcessor.RENAME_IS_PROCESSING, 1);
pm.setTaskName(RenameClassProcessor.CREATING_MODIFICATIONS_LABEL);
if (pm.isCanceled())
throw new OperationCanceledException();
// get target parameters
String newElementName = getNewElementName();
// If update class and update reference are all false, ignore the
// loop.
if (getUpdateReferences()) {
for (Entry<IFile, Program> entry : participantFiles.entrySet()) {
final IFile file = entry.getKey();
final Program program = entry.getValue();
final RenameIncludeFolder rename = new RenameIncludeFolder(file, getCurrentElementName(),
newElementName, this.resource.getFullPath(), false, getUpdateReferences());
// aggregate the changes identifiers
program.accept(rename);
if (pm.isCanceled())
throw new OperationCanceledException();
pm.worked(1);
if (rename.hasChanges()) {
ProgramFileChange change = new ProgramFileChange(file.getName(), file, program);
change.setEdit(new MultiTextEdit());
change.setTextType("php"); //$NON-NLS-1$
rootChange.add(change);
rename.updateChange(change);
}
}
}
} finally {
pm.done();
}
}
private void createFileRenameChange(CompositeChange rootChange) {
RenameResourceChange rmChange = new RenameResourceChange(resource.getFullPath(), fNewElementName);
rootChange.add(rmChange);
}
/*
* (non-Javadoc)
*
* @see org.eclipse.php.refactoring.core.rename.AbstractRenameProcessor#
* checkInitialConditions(org.eclipse.core.runtime.IProgressMonitor)
*/
public RefactoringStatus checkInitialConditions(IProgressMonitor pm) throws OperationCanceledException {
RefactoringStatus status = new RefactoringStatus();
if (!checkReadOnlyAndNull(resource)) {
status.merge(RefactoringStatus.createFatalErrorStatus(
NLS.bind(PhpRefactoringCoreMessages.getString("RenameFileProcessor.7"), resource))); //$NON-NLS-1$
}
try {
boolean hasExternalDependencies = false;
participantFiles = new HashMap<IFile, Program>();
if (resource instanceof IContainer) {
IContainer container = (IContainer) resource;
Set<IFile> phpFilesSet = new HashSet<IFile>();
MoveUtils.getAllPHPFiles(new IResource[] { container }, phpFilesSet);
for (IFile file : phpFilesSet) {
ISourceModule sourceModule = DLTKCore.createSourceModuleFrom(file);
IProject project = file.getProject();
PHPVersion version = ProjectOptions.getPHPVersion(project);
ASTParser newParser = ASTParser.newParser(version, sourceModule);
Program program = newParser.createAST(null);
participantFiles.put(file, program);
collectReferences(program, pm);
}
}
if (hasExternalDependencies) {
final String message = PhpRefactoringCoreMessages.getString("AbstractRenameProcessor.1"); //$NON-NLS-1$
return RefactoringStatus.createWarningStatus(message);
}
return new RefactoringStatus();
} catch (Exception e) {
final String exceptionMessage = e.getMessage();
final String formattedString = REFACTORING_ACTION_INTERNAL_ERROR
.concat(exceptionMessage == null ? "" : exceptionMessage); //$NON-NLS-1$
return RefactoringStatus.createFatalErrorStatus(formattedString);
}
}
/**
* Check if the supplied resource is read only or null. If it is then ask
* the user if they want to continue. Return true if the resource is not
* read only or if the user has given permission.
*
* @return boolean
*/
private boolean checkReadOnlyAndNull(IResource currentResource) {
if (currentResource == null) {
return false;
}
ResourceAttributes attributes = currentResource.getResourceAttributes();
if (attributes == null) {
return false;
}
// Do a quick read only check
if (attributes.isReadOnly()) {
return false;
}
return true;
}
/*
* (non-Javadoc)
*
* @seeorg.eclipse.ltk.core.refactoring.participants.RefactoringProcessor#
* checkFinalConditions(org.eclipse.core.runtime.IProgressMonitor,
* org.eclipse.ltk.core.refactoring.participants.CheckConditionsContext)
*/
public RefactoringStatus checkFinalConditions(IProgressMonitor pm, CheckConditionsContext context)
throws OperationCanceledException {
RefactoringStatus status = new RefactoringStatus();
// Checks if one of the resources already exists with the same name in
// this location
IPath sourcePath = resource.getFullPath().removeLastSegments(1);
String newFilePath = sourcePath.toOSString() + File.separatorChar + getNewElementName();
IResource dest;
if (sourcePath.segmentCount() < 1) {
dest = ResourcesPlugin.getWorkspace().getRoot().getProject(getNewElementName());
} else {
dest = ResourcesPlugin.getWorkspace().getRoot().getFile(new Path(newFilePath));
}
if (dest.exists()) {
status.merge(RefactoringStatus
.createFatalErrorStatus(NLS.bind(PhpRefactoringCoreMessages.getString("RenameFileProcessor.8"), //$NON-NLS-1$
getNewElementName(), sourcePath.toOSString())));
}
return status;
}
private IPath getNewFilePath() {
return resource.getFullPath().removeLastSegments(1);
}
/*
* (non-Javadoc)
*
* @seeorg.eclipse.ltk.core.refactoring.participants.RefactoringProcessor#
* getElements()
*/
@Override
public Object[] getElements() {
return new Object[] { resource };
}
/*
* (non-Javadoc)
*
* @seeorg.eclipse.ltk.core.refactoring.participants.RefactoringProcessor#
* getIdentifier()
*/
@Override
public String getIdentifier() {
return ID_RENAME_FOLDER;
}
/*
* (non-Javadoc)
*
* @seeorg.eclipse.ltk.core.refactoring.participants.RefactoringProcessor#
* getProcessorName()
*/
@Override
public String getProcessorName() {
return RENAME_FOLDER_PROCESSOR_NAME;
}
@Override
public String getCurrentElementName() {
return resource.getName();
}
public void setUpdateRefernces(boolean update) {
isUpdateReferences = update;
}
public boolean canEnableTextUpdating() {
return false;
}
public String getCurrentElementQualifier() {
return resource.getName();
}
public boolean getUpdateTextualMatches() {
return isUpdateTextualMatches;
}
public void setUpdateTextualMatches(boolean update) {
this.isUpdateTextualMatches = update;
}
@Override
public Object getNewElement() throws CoreException {
return null;
}
/*
* (non-Javadoc)
*
* @see
* org.eclipse.php.refactoring.core.rename.IReferenceUpdating#getAttribute(
* java.lang.String)
*/
public String getAttribute(String attribute) {
return attributes.get(attribute);
}
/*
* (non-Javadoc)
*
* @see
* org.eclipse.php.refactoring.core.rename.IReferenceUpdating#setAttribute(
* java.lang.String, java.lang.String)
*/
public void setAttribute(String attribute, String value) {
attributes.put(attribute, value);
}
private class NoReverseCompositeChange extends CompositeChange {
public NoReverseCompositeChange(String name) {
super(name);
}
public NoReverseCompositeChange(String name, Change[] array) {
super(name, array);
}
@Override
protected Change createUndoChange(Change[] childUndos) {
List<Change> undos = Arrays.asList(childUndos);
Collections.reverse(undos);
return new NoReverseCompositeChange(getName(), undos.toArray(new Change[undos.size()]));
}
}
}