/**
* CertWare Project
* Copyright (c) 2010 National Aeronautics and Space Administration. All rights reserved.
*/
package net.certware.export.jobs;
import java.io.File;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import net.certware.core.ui.log.CertWareLog;
import net.certware.export.ExportContributions;
import net.certware.export.ExportOperation;
import net.certware.export.wizards.Messages;
import org.eclipse.core.resources.IContainer;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.MultiStatus;
import org.eclipse.core.runtime.Path;
import org.eclipse.core.runtime.Status;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.jface.operation.ModalContext;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.dialogs.IOverwriteQuery;
/**
* Operation for exporting the contents of a resource to the local file system.
* @author mrb
* See IBM original for data transfer internal use
* @since 1.0
*/
public class ExportResourceOperation implements IRunnableWithProgress { // $codepro.audit.disable declareDefaultConstructors
/** destination path */
private IPath path;
/** progress monitor */
private IProgressMonitor monitor;
/** list of resources to export */
private List<IResource> resourcesToExport;
/** whether to overwrite existing resources */
private IOverwriteQuery overwriteCallback;
/** resource */
private IResource resource;
/** list of errors status */
private final List<IStatus> errorTable = new ArrayList<IStatus>(1);
/** constant for overwrite not set */
private static final int OVERWRITE_NOT_SET = 0;
/** constant for overwrite none */
private static final int OVERWRITE_NONE = 1;
/** constant for overwrite all */
private static final int OVERWRITE_ALL = 2;
/** current value of overwrite state */
private int overwriteState = OVERWRITE_NOT_SET;
/** whether to create the lead-up path structure */
private boolean createLeadupStructure = true;
/** whether to create container directories */
private boolean createContainerDirectories = true;
/** plugin extension point contributions */
private ExportContributions ec = null;
/**
* Create an instance of this class. Use this constructor if you wish to
* recursively export a single resource
* @param res IResource
* @param destinationPath String
* @param overwriteImplementor IOverwriteQuery
*/
public ExportResourceOperation(IResource res, String destinationPath, IOverwriteQuery overwriteImplementor) {
super();
resource = res;
path = new Path(destinationPath);
overwriteCallback = overwriteImplementor;
ec = new ExportContributions();
ec.initialize();
}
/**
* Create an instance of this class. Use this constructor if you wish to
* export specific resources with a common parent resource (affects container directory creation)
* @param res IResource
* @param resources List<IResource>
* @param destinationPath String
* @param overwriteImplementor IOverwriteQuery
*/
public ExportResourceOperation(IResource res, List<IResource> resources,
String destinationPath, IOverwriteQuery overwriteImplementor) {
this(res, destinationPath, overwriteImplementor);
resourcesToExport = resources;
}
/**
* Creates the specified file system directory at <code>destinationPath.
* This creates a new file system directory.
* @param destinationPath location to which files will be written
*/
public void createFolder(IPath destinationPath) {
boolean rv = new File(destinationPath.toPortableString()).mkdir();
if ( !(rv) ) {
CertWareLog.logWarning("Exporting path not created");
}
}
/**
* Answer the total number of file resources that exist at or below self in the resources hierarchy.
* @param parentResource parent whose children we are counting
* @return number of children
* @throws CoreException
*/
protected int countChildrenOf(IResource parentResource)
throws CoreException {
if (parentResource.getType() == IResource.FILE) {
return 1;
}
int count = 0;
if (parentResource.isAccessible()) {
final IResource[] children = ((IContainer) parentResource).members();
for (int i = 0; i < children.length; i++) {
count += countChildrenOf(children[i]);
}
}
return count;
}
/**
* Answer indicating the number of file resources that were specified for export.
* @return number of selected resources
* @throws CoreException
*/
protected int countSelectedResources() throws CoreException {
int result = 0;
final Iterator<IResource> resources = resourcesToExport.iterator();
while (resources.hasNext()) {
result += countChildrenOf(resources.next());
}
return result;
}
/**
* Create the directories required for exporting the passed resource,
* based upon its container hierarchy
* @param childResource child resource for path lead-up
*/
protected void createLeadupDirectoriesFor(IResource childResource) {
final IPath resourcePath = childResource.getFullPath().removeLastSegments(1);
final int segmentCount = resourcePath.segmentCount();
for (int i = 0; i < segmentCount; i++) {
path = path.append(resourcePath.segment(i));
createFolder(path);
}
}
/**
* Recursively export the previously-specified resource
* @param monitor progress monitor.
* @throws InterruptedException
*/
protected void exportAllResources(IProgressMonitor monitor) throws InterruptedException {
if (resource.getType() == IResource.FILE) {
exportFile((IFile) resource, path, monitor);
} else {
try {
exportChildren(((IContainer) resource).members(), path, monitor);
} catch (CoreException e) {
// not safe to show a dialog
// should never happen because the file system export wizard ensures that the
// single resource chosen for export is both existent and accessible
errorTable.add(e.getStatus());
}
}
}
/**
* Export all of the resources contained in the passed collection
* @param children children to export
* @param currentPath current path
* @param monitor progress monitor
* @throws InterruptedException
*/
protected void exportChildren(IResource[] children, IPath currentPath, IProgressMonitor monitor)
throws InterruptedException
{
for (int i = 0; i < children.length; i++) {
IResource child = children[i]; // $codepro.audit.disable variableDeclaredInLoop
if (!child.isAccessible()) {
continue;
}
if (child.getType() == IResource.FILE) {
exportFile((IFile) child, currentPath, monitor);
} else {
IPath destination = currentPath.append(child.getName()); // $codepro.audit.disable variableDeclaredInLoop
createFolder(destination);
try {
exportChildren(((IContainer) child).members(), destination, monitor);
} catch (CoreException e) {
// not safe to show a dialog
// should never happen because:
// i. this method is called recursively iterating over the result of #members,
// which only answers existing children
// ii. there is an #isAccessible check done before #members is invoked
errorTable.add(e.getStatus());
}
}
}
}
/**
* Export the passed file to the specified location.
* @param file file name for full path
* @param location export location path
* @param monitor progress monitor
* @throws InterruptedException
*/
protected void exportFile(IFile file, IPath location, IProgressMonitor monitor)
throws InterruptedException
{
final IPath fullPath = location.append(file.getName());
monitor.subTask(file.getFullPath().toString());
final String properPathString = fullPath.toPortableString();
final File targetFile = new File(properPathString);
if (targetFile.exists()) {
if (!targetFile.canWrite()) {
errorTable.add(new Status(IStatus.ERROR,
PlatformUI.PLUGIN_ID,
0,
MessageFormat.format(Messages.ExportResourceOperation_0, Messages.ExportResourceOperation_1, targetFile.getAbsolutePath()),
null));
monitor.worked(1);
return;
}
if (overwriteState == OVERWRITE_NONE) {
return;
}
if (overwriteState != OVERWRITE_ALL) {
final String overwriteAnswer = overwriteCallback.queryOverwrite(properPathString);
if (overwriteAnswer.equals(IOverwriteQuery.CANCEL)) {
throw new InterruptedException(MessageFormat.format(Messages.ExportResourceOperation_6, file.getName()));
}
if (overwriteAnswer.equals(IOverwriteQuery.NO)) {
monitor.worked(1);
return;
}
if (overwriteAnswer.equals(IOverwriteQuery.NO_ALL)) {
monitor.worked(1);
overwriteState = OVERWRITE_NONE;
return;
}
if (overwriteAnswer.equals(IOverwriteQuery.ALL)) {
overwriteState = OVERWRITE_ALL;
}
}
}
System.err.println("export file " + file + " extension " + file.getFileExtension() + " to " + fullPath);
// find a matching exporter contribution for this file extension
// TODO this uses the first one it finds
for ( ExportOperation eo : ec.getExportOperations() ) {
System.err.println("contributed operation for " + eo + " operation " + eo.getOperation() ); // TODO testing
if ( eo.getFileExtension().equals( file.getFileExtension() )) {
System.err.println("performing contributed operation for " + file); // TODO testing
eo.getOperation().writeFile(file,fullPath,monitor);
}
}
monitor.worked(1);
ModalContext.checkCanceled(monitor);
}
/**
* Export the resources contained in the previously-defined resourcesToExport collection.
* @param monitor progress monitor
* @throws InterruptedException
*/
protected void exportSpecifiedResources(IProgressMonitor monitor) throws InterruptedException {
final Iterator<IResource> resources = resourcesToExport.iterator();
final IPath initPath = (IPath) path.clone();
while (resources.hasNext()) {
IResource currentResource = resources.next(); // $codepro.audit.disable variableDeclaredInLoop
if (!currentResource.isAccessible()) {
continue;
}
path = initPath;
if (null == resource) {
// No root resource specified and creation of containment directories
// is required. Create containers from depth 2 onwards (ie.- project's
// child inclusive) for each resource being exported.
if (createLeadupStructure) {
createLeadupDirectoriesFor(currentResource);
}
} else {
// Root resource specified. Must create containment directories
// from this point onwards for each resource being exported
IPath containersToCreate = currentResource.getFullPath() // $codepro.audit.disable variableDeclaredInLoop
.removeFirstSegments(
resource.getFullPath().segmentCount())
.removeLastSegments(1);
final int segmentCount = containersToCreate.segmentCount(); // $codepro.audit.disable variableDeclaredInLoop
for (int i = 0; i < segmentCount; i++) {
path = path.append(containersToCreate.segment(i));
createFolder(path);
}
}
if (currentResource.getType() == IResource.FILE) {
exportFile((IFile) currentResource, path, monitor);
} else {
if (createContainerDirectories) {
path = path.append(currentResource.getName());
createFolder(path);
}
try {
exportChildren(((IContainer) currentResource).members(), path, monitor);
} catch (CoreException e) {
// should never happen because #isAccessible is called before #members is invoked,
// which implicitly does an existence check
errorTable.add(e.getStatus());
}
}
}
}
/**
* Returns the status of the export operation.
* If there were any errors, the result is a status object containing
* individual status objects for each error.
* If there were no errors, the result is a status object with error code <code>OK.
* @return the status
*/
public IStatus getStatus() {
final IStatus[] errors = new IStatus[errorTable.size()];
errorTable.toArray(errors);
return new MultiStatus(
PlatformUI.PLUGIN_ID,
IStatus.OK,
errors,
Messages.ExportResourceOperation_4,
null);
}
/**
* Answer a boolean indicating whether the passed child is a descendant
* of one or more members of the passed resources collection
* @param resources list of ancestor resources
* @param child child to find in ancestors
* @return boolean true if child is in resources list and is not a project
*/
protected boolean isDescendent(List<?> resources, IResource child) {
if (child.getType() == IResource.PROJECT) {
return false;
}
final IResource parent = child.getParent();
if (resources.contains(parent)) {
return true;
}
return isDescendent(resources, parent);
}
/**
* Export the resources that were previously specified for export
* (or if a single resource was specified then export it recursively)
* @param progressMonitor progress monitor
* @throws InterruptedException
* @see org.eclipse.jface.operation.IRunnableWithProgress#run(IProgressMonitor)
*/
public void run(IProgressMonitor progressMonitor)
throws InterruptedException {
monitor = progressMonitor;
if (null != resource) {
if (createLeadupStructure) {
createLeadupDirectoriesFor(resource);
}
if (createContainerDirectories && resource.getType() != IResource.FILE) {
// ensure it's a container
path = path.append(resource.getName());
createFolder(path);
}
}
try {
int totalWork = IProgressMonitor.UNKNOWN;
try {
if (null == resourcesToExport) {
totalWork = countChildrenOf(resource);
} else {
totalWork = countSelectedResources();
}
} catch (CoreException e) {
// Should not happen
errorTable.add(e.getStatus());
}
monitor.beginTask(Messages.ExportResourceOperation_5, totalWork);
if (null == resourcesToExport) {
exportAllResources(monitor);
} else {
exportSpecifiedResources(progressMonitor);
}
} finally {
monitor.done();
}
}
/**
* Set this boolean indicating whether a directory should be created for
* Folder resources that are explicitly passed for export
* @param value whether to create container directories
*/
public void setCreateContainerDirectories(boolean value) {
createContainerDirectories = value;
}
/**
* Set this boolean indicating whether each exported resource's complete path should
* include containment hierarchies as dictated by its parents
* @param value whether to create lead-up structure
*/
public void setCreateLeadupStructure(boolean value) {
createLeadupStructure = value;
}
/**
* Set this boolean indicating whether exported resources should automatically
* overwrite existing files when a conflict occurs. If not, query the user.
* @param value whether to overwrite existing files
*/
public void setOverwriteFiles(boolean value) {
if (value) {
overwriteState = OVERWRITE_ALL;
}
}
}