/*****************************************************************************
* Copyright (c) 2010 CEA LIST.
*
* 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:
* Camille Letavernier (CEA LIST) camille.letavernier@cea.fr - Initial API and implementation
*****************************************************************************/
package org.eclipse.papyrus.customization.properties.ui;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl;
import org.eclipse.jface.dialogs.ProgressMonitorDialog;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.papyrus.customization.properties.messages.Messages;
import org.eclipse.papyrus.infra.emf.utils.EMFHelper;
import org.eclipse.papyrus.views.properties.Activator;
import org.eclipse.papyrus.views.properties.catalog.PropertiesURIHandler;
import org.eclipse.papyrus.views.properties.contexts.Context;
import org.eclipse.papyrus.views.properties.contexts.ContextsPackage;
import org.eclipse.papyrus.views.properties.contexts.Section;
import org.eclipse.papyrus.views.properties.contexts.Tab;
import org.eclipse.papyrus.views.properties.runtime.ConfigurationManager;
import org.eclipse.papyrus.views.properties.util.PropertiesUtil;
import org.eclipse.swt.widgets.Display;
/**
* An action to build a new Property view context from an existing one.
*
* @author Camille Letavernier
*/
public class CopyContextAction {
private IStatus result;
/**
* Copy an existing context to a new one with the given name.
* The new context is registered to the {@link ConfigurationManager}.
* To enable the edition of the context, an invisible project is created
* in the workspace. The files are stored in the runtime plugin's preference
* folder.
*
* @param source
* The source Context to copy
* @param targetName
* The name of the new context
* @param activate
* If true, the new context will be activated and available immediately,
* while the previous one will be disabled to avoid conflicts
* @return
* The new Context
*
* @throws IOException
* If an error occured : the previous context cannot be read, or
* the new context cannot be created
*/
public Context copy(Context source, String targetName, boolean activate) throws IOException {
IPath targetDirectoryPath = Activator.getDefault().getPreferencesPath().append("/" + targetName); //$NON-NLS-1$
final File targetDirectory = targetDirectoryPath.toFile();
if(targetDirectory.exists()) {
throw new IOException("A context with this name already exists"); //$NON-NLS-1$
}
URI targetModelUri = URI.createFileURI(targetDirectory.toString() + "/" + targetName + ".ctx"); //$NON-NLS-1$ //$NON-NLS-2$
ResourceSet resourceSet = new ResourceSetImpl();
Context sourceContext = (Context)EMFHelper.loadEMFModel(resourceSet, source.eResource().getURI());
copyAll(sourceContext, new File(targetDirectory, targetName + ".ctx")); //$NON-NLS-1$
if(result.getCode() == IStatus.OK) {
Context targetContext = ConfigurationManager.instance.getContext(targetModelUri);
targetContext.setName(targetName);
targetContext.eResource().save(Collections.EMPTY_MAP);
ConfigurationManager.instance.addContext(targetContext, activate);
if(activate) {
ConfigurationManager.instance.disableContext(source, true);
}
return targetContext;
} else {
return null;
}
}
/**
* Lightweight method for loading a resource set
* This method ignores the *.xwt files, which do not contain any useful
* cross-reference (As they can only reference Environment files), and are
* really expensive to load.
*/
private void resolveAllResources(Context source) {
resolveAllResources(source, new HashSet<EObject>());
}
/**
* Lightweight method for loading a resource set
* This method ignores the *.xwt files, which do not contain any useful
* cross-reference (As they can only reference Environment files), and are
* really expensive to load.
*/
private void resolveAllResources(EObject source, Set<EObject> visitedEObjects) {
if(!visitedEObjects.add(source)) {
return;
}
for(EReference reference : source.eClass().getEAllReferences()) {
//Do not load *.xwt resources
//These files do not contain any useful cross-reference, and are really expensive to load
if(reference == ContextsPackage.eINSTANCE.getSection_Widget()) {
continue;
}
Object value = source.eGet(reference);
if(value instanceof EList) {
for(Object object : (EList<?>)value) {
if(object instanceof EObject) {
resolveAllResources((EObject)object, visitedEObjects);
}
}
} else if(value instanceof EObject) {
resolveAllResources((EObject)value, visitedEObjects);
}
}
}
private void copyAll(final Context source, final File target) {
final File targetDirectory = target.getParentFile();
final String targetName = target.getName();
ProgressMonitorDialog dialog = new ProgressMonitorDialog(Display.getCurrent().getActiveShell());
try {
dialog.run(true, false, new IRunnableWithProgress() {
public void run(IProgressMonitor monitor) {
monitor.beginTask(Messages.CopyContextAction_InitializingTheCopyOf + source.getName() + Messages.CopyContextAction_ThisMayTakeSomeTime, IProgressMonitor.UNKNOWN);
//EcoreUtil.resolveAll(source); //This method is too expensive
resolveAllResources(source); //Ignores the *.xwt files. We will copy them manually.
monitor.done();
result = Status.OK_STATUS;
}
});
dialog = new ProgressMonitorDialog(Display.getCurrent().getActiveShell());
if(result.getCode() == IStatus.OK) {
dialog.run(true, true, new IRunnableWithProgress() {
public void run(IProgressMonitor monitor) {
try {
targetDirectory.mkdirs();
int filesToCopy = source.eResource().getResourceSet().getResources().size();
List<Context> contexts = new LinkedList<Context>();
for(Context context : PropertiesUtil.getDependencies(source)) {
if(isRelative(source, context.eResource())) {
contexts.add(context);
for(Tab tab : context.getTabs()) {
filesToCopy += tab.getSections().size();
}
}
}
monitor.beginTask(Messages.CopyContextAction_Copying + source.getName() + Messages.CopyContextAction_To + targetName, filesToCopy);
//Copy of the context
copy(source.eResource(), target);
monitor.worked(1);
//Copy of the dependent resources which are located in the same folder
//(or subfolders)
for(Resource resource : source.eResource().getResourceSet().getResources()) {
if(monitor.isCanceled()) {
return;
}
if(source.eResource() != resource && isRelative(source, resource)) {
copy(resource, targetDirectory, source, targetName);
}
monitor.worked(1);
}
//Copy the XWT files (they haven't been loaded in the resource set)
for(Context context : contexts) {
for(Tab tab : context.getTabs()) {
for(Section section : tab.getSections()) {
if(monitor.isCanceled()) {
return;
}
copy(section.getSectionFile(), targetDirectory, source);
monitor.worked(1);
}
}
}
monitor.done();
} catch (IOException ex) {
Activator.log.error(ex);
result = new Status(IStatus.ERROR, Activator.PLUGIN_ID, "An error occured during the copy of " + source.getName(), ex); //$NON-NLS-1$
return;
}
result = Status.OK_STATUS;
}
});
}
} catch (InvocationTargetException ex) {
Activator.log.error(ex);
} catch (InterruptedException ex) {
Activator.log.error(ex);
}
}
protected void copy(String xwtFileName, File targetDirectory, Context source) {
File target = new File(targetDirectory, xwtFileName);
URI sourceURI = URI.createURI(xwtFileName).resolve(source.eResource().getURI());
PropertiesURIHandler uriHandler = new PropertiesURIHandler();
if(uriHandler.canHandle(sourceURI)) {
sourceURI = uriHandler.getConvertedURI(sourceURI);
}
try {
java.net.URL netURL = new java.net.URL(sourceURI.toString());
InputStream is = netURL.openStream();
copy(is, target);
} catch (MalformedURLException ex) {
Activator.log.error(ex);
} catch (IOException ex) {
Activator.log.error(ex);
}
}
protected void copy(Resource resource, File target) throws IOException {
PropertiesURIHandler uriHandler = new PropertiesURIHandler();
URI uri = resource.getURI();
if(uriHandler.canHandle(uri)) {
uri = uriHandler.getConvertedURI(uri);
}
copy(new URL(uri.toString()).openStream(), target);
}
private void copy(Resource resource, File directory, EObject source, String targetName) throws IOException {
URI relativeURI = resource.getURI().deresolve(source.eResource().getURI());
if(relativeURI.toString().equals("")) { //$NON-NLS-1$
relativeURI = URI.createURI(targetName + ".ctx"); //$NON-NLS-1$
}
File target = new File(directory, relativeURI.toString());
copy(resource, target);
}
private boolean isRelative(EObject source, Resource resource) {
URI baseURI = source.eResource().getURI();
URI resourceURI = resource.getURI();
URI uri = resourceURI.deresolve(baseURI);
if(uri.isRelative()) {
if(!(uri.toString().startsWith("..") || uri.toString().startsWith("/"))) { //$NON-NLS-1$ //$NON-NLS-2$
return true;
}
}
return false;
}
//Strict copy : we read directly the file, instead of interpreting it as a Model
private void copy(InputStream source, File target) throws IOException {
if(!target.getParentFile().exists()) {
target.getParentFile().mkdirs();
}
FileWriter out = new FileWriter(target);
try {
int c;
while((c = source.read()) != -1) {
out.write(c);
}
} catch (IOException ex) {
throw ex;
} finally {
source.close();
out.close();
}
}
}