/*****************************************************************************
* Copyright (c) 2008 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:
* Cedric Dumoulin Cedric.dumoulin@lifl.fr - Initial API and implementation
* Emilien Perico emilien.perico@atosorigin.com - manage loading strategies
*
*****************************************************************************/
package org.eclipse.papyrus.infra.core.resource;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.emf.common.notify.Adapter;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.common.util.WrappedException;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.impl.ResourceImpl;
import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl;
import org.eclipse.emf.ecore.xmi.XMLResource;
import org.eclipse.emf.transaction.TransactionalEditingDomain;
import org.eclipse.emf.transaction.impl.EditingDomainManager;
import org.eclipse.emf.workspace.WorkspaceEditingDomainFactory;
import org.eclipse.papyrus.infra.core.resource.additional.AdditionalResourcesModel;
/**
* This class is used to manage a set of {@link IModel}.
*
* <h2>>Usage</h2>
* <ul>
* <li>First, register associated model. A loader can be used.</li>
* <li>Second, call load() or create()</li>
* <li>Then, it is possible to get associated models</li>
* <li>Finally, call save()</li>
* </ul>
*
* Please note that indirectly referenced models are loaded on demand. If a
* model contains a cross reference towards another model (e.g. an import in
* case of UML) the referenced resource does not appear initially in the set.
* However, it is added once the referenced model is resolved.
*
* TODO Modify ModelSetSnippet in order to inform them of model addition.
*
* @author cedric dumoulin
*
*/
public class ModelSet extends ResourceSetImpl {
/**
* Id use to register the EditinDomain into the registry
*/
public static final String PAPYRUS_EDITING_DOMAIN_ID = "org.eclipse.papyrus.SharedEditingDomainID";
/** The associated IModels. */
private Map<String, IModel> models = new HashMap<String, IModel>();
/** The snippets. */
private ModelSetSnippetList snippets = new ModelSetSnippetList();
private AdditionalResourcesModel additional = new AdditionalResourcesModel();
/**
* The associated EditingDomain.
*/
private TransactionalEditingDomain transactionalEditingDomain;
/**
* The filename path, without extension, used for action on models.
*/
private IPath filenameWithoutExtension;
/**
*
* Constructor.
*
*/
public ModelSet() {
registerModel(additional);
this.setURIResourceMap(new HashMap<URI, Resource>());
getLoadOptions().put(XMLResource.OPTION_DEFER_ATTACHMENT, true);
getLoadOptions().put(XMLResource.OPTION_DEFER_IDREF_RESOLUTION, true);
}
/**
* Register the specified model under its associated key. The key is defined
* in the model itself. It is usually the model type from
* (ModelPackage.eCONTENT_TYPE).
*
* @param model
* the model
*/
public void registerModel(IModel model) {
models.put(model.getIdentifier(), model);
model.init(this);
}
/**
* Get a model by its key. TODO throw an exception if not found.
*
* @param key
* the key
* @return the model
*/
public IModel getModel(String key) {
return models.get(key);
}
/**
* Get a model by its key. TODO throw an exception if not found.
*
* @param key
* the key
* @return the model
* @throws NotFoundException
* If no model is registered under the key.
*/
public IModel getModelChecked(String key) throws NotFoundException {
IModel model = models.get(key);
if(model == null) {
throw new NotFoundException("Can't find model for identifier '" + key + "'.");
}
return model;
}
@Override
public Resource createResource(URI uri, String contentType) {
return setResourceOptions(super.createResource(uri, contentType));
}
@Override
public Resource getResource(URI uri, boolean loadOnDemand) {
Resource resource = null;
resource = super.getResource(uri, loadOnDemand);
return setResourceOptions(resource);
}
/**
* Retrieve and load the associated resource which have the given extension.
*
* @param modelElement
* @param associatedResourceExtension
* @return
*/
public Resource getAssociatedResource(EObject modelElement, String associatedResourceExtension) {
if(modelElement != null) {
return getAssociatedResource(modelElement.eResource(), associatedResourceExtension);
}
return null;
}
/**
* Retrieve and load the associated resource which have the given extension.
*
* @param modelResource
* @param associatedResourceExtension
* @return
*/
public Resource getAssociatedResource(Resource modelResource, String associatedResourceExtension) {
Resource r = null;
if(modelResource != null) {
URI trimmedModelURI = modelResource.getURI().trimFileExtension();
try {
r = getResource(trimmedModelURI.appendFileExtension(associatedResourceExtension), true);
} catch (WrappedException e) {
if(ModelUtils.isDegradedModeAllowed(e.getCause())) {
r = getResource(trimmedModelURI.appendFileExtension(associatedResourceExtension), false);
if(r == null) {
throw e;
}
}
} catch (Exception e) {
}
}
return setResourceOptions(r);
}
/**
* This method is called by getResource and createResource before returning
* the resource to the caller so we can set options on the resource.
*
* @param r
* , can be null
* @return the same resource for convenience
*/
protected Resource setResourceOptions(Resource r) {
if(r instanceof ResourceImpl) {
ResourceImpl impl = (ResourceImpl)r;
if(impl.getIntrinsicIDToEObjectMap() == null) {
impl.setIntrinsicIDToEObjectMap(new HashMap<String, EObject>());
}
}
return r;
}
/**
* Create the transactional editing domain.
*
* @return the transactional editing domain
*/
public TransactionalEditingDomain getTransactionalEditingDomain() {
transactionalEditingDomain = WorkspaceEditingDomainFactory.INSTANCE.getEditingDomain(this);
if(transactionalEditingDomain == null) {
transactionalEditingDomain = TransactionalEditingDomainManager.createTransactionalEditingDomain(this);
// register the id for lifecyle events the id is set by the registry
EditingDomainManager.getInstance().configureListeners(PAPYRUS_EDITING_DOMAIN_ID, transactionalEditingDomain);
}
return transactionalEditingDomain;
}
/**
* @return the filenameWithoutExtension
*/
public IPath getFilenameWithoutExtension() {
return filenameWithoutExtension;
}
/**
* @return the filenameWithoutExtension
* @throws BadStateException
*/
protected IPath getFilenameWithoutExtensionChecked() throws BadStateException {
if(filenameWithoutExtension == null) {
throw new BadStateException("Path should be set prior calling any operations.");
}
return filenameWithoutExtension;
}
/**
* @param filenameWithoutExtension
* the filenameWithoutExtension to set
*/
protected void setFilenameWithoutExtension(IPath filenameWithoutExtension) {
this.filenameWithoutExtension = filenameWithoutExtension;
}
/**
* Create all the associated models. This creates the models, regardless if
* they already exist.
*
* @param newFile
* The file from which path is extracted to create the new
* resources
*/
public void createsModels(IFile newFile) {
// Get the file name, without extension.
filenameWithoutExtension = newFile.getFullPath().removeFileExtension();
// Walk all registered models
for(IModel model : models.values()) {
model.createModel(filenameWithoutExtension);
}
// call snippets to allow them to do their stuff
snippets.performStart(this);
}
/**
* Create the model specified by the identifiers. Other models are
* untouched, unless they are sharing something with specified models.
*
* This creates the models, regardless if they already exist.
*
* @param newFile
* The file from which path is extracted to create the new
* resources
*/
public void createsModels(ModelIdentifiers modelIdentifiers) {
// Walk all registered models
for(String modelId : modelIdentifiers) {
IModel model = getModel(modelId);
// Load models using the default path
model.createModel(filenameWithoutExtension);
}
// call snippets to allow them to do their stuff
// snippets.modelsAdded(modelIdentifiers);
}
/**
* Load only the specified model. ModelSetSnippets are not called. Model is
* loaded using the ModelSet Path.
*
* @param modelIdentifier
* the model identifier
* @param file
* the file
* @return the i model
* @throws BadStateException
* If the global path is not specified.
* @returns The loaded model.
*/
public IModel loadModel(String modelIdentifier) throws BadStateException {
IModel model = getModel(modelIdentifier);
model.loadModel(getFilenameWithoutExtensionChecked());
return model;
}
/**
* Import only the specified model. ModelSetSnippets are not called.
*
* @param modelIdentifier
* the model identifier
* @param file
* the file
* @return the i model
* @throws ModelException
* @returns The loaded model.
* @deprecated Use {@link #importModel(ModelIdentifier, IFile)}
*/
@Deprecated
public IModel loadModel(String modelIdentifier, IFile file) throws ModelException {
importModels(new ModelIdentifiers(modelIdentifier), file);
return getModel(modelIdentifier);
}
/**
* Load all the associated models from a handle on one of the associated
* file.
*
* @param file
* The file to load (no matter the extension)
*/
public void loadModels(IFile file) throws ModelMultiException {
// Get the file name, without extension.
filenameWithoutExtension = file.getFullPath().removeFileExtension();
ModelMultiException exceptions = null;
// Walk all registered models
for(IModel model : models.values()) {
// Try to load each model. Catch exceptions in order to load other
// models.
try {
model.loadModel(filenameWithoutExtension);
} catch (Exception e) {
// Record the exception
if(exceptions == null) {
exceptions = new ModelMultiException("Problems encountered while loading one of the models.");
}
exceptions.addException(model.getIdentifier(), e);
}
}
// call snippets to allow them to do their stuff
snippets.performStart(this);
// Report exceptions if any
if(exceptions != null) {
throw exceptions;
}
}
/**
* Import specified models into the ModelSet. The models are imported using
* the specified IFile. After import, the models are associated with the
* ModelSet Path.
*
* @param modelIdentifiers
* The model to import from the specified IFile.
* @param file
* The IFile used to import the model.
* @throws ModelException
* If an error occur during import.
*/
public void importModels(ModelIdentifiers modelIdentifiers, IFile file) throws ModelException {
IPath path = file.getFullPath().removeFileExtension();
// Walk all registered models
for(String modelId : modelIdentifiers) {
IModel model = getModel(modelId);
// Load models using the default path
model.importModel(path);
if(filenameWithoutExtension != null) {
model.changeModelPath(filenameWithoutExtension);
}
}
}
/**
* Import only the specified model. ModelSetSnippets are not called. An
* import can be performed after model are loaded. Normally, it should not
* be done before a model is loaded.
*
* @param modelIdentifier
* the model identifier
* @param file
* the file
* @throws ModelException
* @returns The loaded model.
*/
public IModel importModel(String modelIdentifier, IFile file) throws ModelException {
importModels(new ModelIdentifiers(modelIdentifier), file);
return getModel(modelIdentifier);
}
/**
* Create models that are not already created or loaded.
*/
public void createMissingModels() throws ModelException {
throw new UnsupportedOperationException("Not yet implemented");
}
/**
* Load models that are not already created or loaded.
*/
public void loadMissingModels() throws ModelException {
throw new UnsupportedOperationException("Not yet implemented");
}
/**
* Save the resources.
*
* @param monitor
* The monitor.
* @throws IOException
* IO Error.
*/
public void save(IProgressMonitor monitor) throws IOException {
// Initialize monitor with the number of models
Collection<IModel> modelList = models.values();
monitor.beginTask("Saving resources", modelList.size());
try {
// Walk all registered models
for(IModel model : modelList) {
if(!(model instanceof AdditionalResourcesModel)) {
model.saveModel();
monitor.worked(1);
}
}
additional.saveModel();
} finally {
monitor.done();
}
}
/**
* The resources are already loaded, but we want to save them under another
* name.
*
* @param path
* the path
* @throws IOException
* Signals that an I/O exception has occurred.
*/
public void saveAs(IPath path) throws IOException {
// Get the file name, without extension.
filenameWithoutExtension = path.removeFileExtension();
// Walk all registered models
for(IModel model : models.values()) {
model.changeModelPath(filenameWithoutExtension);
}
// Save with new paths
save(new NullProgressMonitor());
}
/**
* Unload all the resources. Do not disguard associated models.
*/
public void unload() {
// call snippets to allow them to do their stuff
snippets.performDispose(this);
// Walk all registered models
for(IModel model : models.values()) {
if(!(model instanceof AdditionalResourcesModel)) {
model.unload();
}
}
additional.unload();
// Unload remaining resources
for(Iterator<Resource> iter = getResources().iterator(); iter.hasNext();) {
iter.next().unload();
iter.remove();
}
// Dispose Editing Domain
if(transactionalEditingDomain != null) {
transactionalEditingDomain.dispose();
}
// Detach associated factories
if(adapterFactories != null) {
adapterFactories.clear();
}
EList<Adapter> adapters = eAdapters();
if(adapters != null) {
adapters.clear();
}
}
/**
* Add a {@link IModelSetSnippet}. A snippet allows to add code that will
* perform additional operations on the ModelSet.
*
* @param snippet
* The snippet to add.
*/
public void addModelSetSnippet(IModelSetSnippet snippet) {
snippets.add(snippet);
}
/**
* A list of {@link IModelSetSnippet}.
*
* Used by Models to maintain their list of Snippets.
*
* @author cedric dumoulin
*
*/
public class ModelSetSnippetList extends ArrayList<IModelSetSnippet> {
/** The Constant serialVersionUID. */
private static final long serialVersionUID = 1L;
/**
* Call the start method on all registered snippets.
*
* @param modelsManager
* The model that is starting
*/
public void performStart(ModelSet modelsManager) {
for(IModelSetSnippet snippet : this) {
snippet.start(modelsManager);
}
}
/**
* Call the start method on all registered snippets.
*
* @param modelsManager
* The model that is stopping
*/
public void performDispose(ModelSet modelsManager) {
for(IModelSetSnippet snippet : this) {
snippet.dispose(modelsManager);
}
}
}
}