blob: 6aadb2817e5bcf0e64259c7bcd92db70fdc0273c [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2008-2011 Chair for Applied Software Engineering,
* Technische Universitaet Muenchen.
* 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:
* Maximilian Koegel, Edgar Mueller, Otto von Wesendonk - initial API and implementation
* Johannes Faltermeier - adaptions for independent storage
******************************************************************************/
package org.eclipse.emf.emfstore.internal.common.model.util;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.text.MessageFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.emf.common.util.ECollections;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.common.util.TreeIterator;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EClassifier;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EPackage;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.EStructuralFeature.Setting;
import org.eclipse.emf.ecore.EcoreFactory;
import org.eclipse.emf.ecore.InternalEObject;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.Resource.Diagnostic;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.ecore.resource.URIConverter;
import org.eclipse.emf.ecore.resource.impl.ResourceImpl;
import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.emf.ecore.xmi.XMIResource;
import org.eclipse.emf.ecore.xmi.XMLResource;
import org.eclipse.emf.emfstore.common.ESResourceSetProvider;
import org.eclipse.emf.emfstore.common.extensionpoint.ESExtensionElement;
import org.eclipse.emf.emfstore.common.extensionpoint.ESExtensionPoint;
import org.eclipse.emf.emfstore.common.extensionpoint.ESExtensionPointException;
import org.eclipse.emf.emfstore.common.extensionpoint.ESPriorityComparator;
import org.eclipse.emf.emfstore.common.model.ESSingletonIdResolver;
import org.eclipse.emf.emfstore.internal.common.CommonUtil;
import org.eclipse.emf.emfstore.internal.common.ResourceFactoryRegistry;
import org.eclipse.emf.emfstore.internal.common.model.AssociationClassElement;
import org.eclipse.emf.emfstore.internal.common.model.IdEObjectCollection;
import org.eclipse.emf.emfstore.internal.common.model.ModelElementId;
import org.eclipse.emf.emfstore.internal.common.model.ModelFactory;
import org.eclipse.emf.emfstore.internal.common.model.Project;
import org.eclipse.emf.emfstore.internal.common.model.impl.ESModelElementIdImpl;
import org.eclipse.emf.emfstore.internal.common.model.impl.IdEObjectCollectionImpl;
import org.eclipse.emf.emfstore.internal.common.model.impl.ProjectImpl;
import org.osgi.framework.Bundle;
/**
* Utility class for ModelElements.
*
* @author koegel
* @author emueller
* @author ottovonwesen
* @author jfaltermeier
*/
public final class ModelUtil {
private static final String CLIENT_RESOURCE_SET_PROVIDER_EXT_POINT_ID = "org.eclipse.emf.emfstore.client.resourceSetProvider"; //$NON-NLS-1$
private static final String SERVER_RESOURCE_SET_PROVIDER_EXT_POINT_ID = "org.eclipse.emf.emfstore.server.resourceSetProvider"; //$NON-NLS-1$
private static final String SINGLETON_ID_RESOLVER_EXT_POINT_ID = "org.eclipse.emf.emfstore.common.model.singletonIdResolver"; //$NON-NLS-1$
/**
* Constant that may be used in case no checksum computation has taken place.
*/
public static final long NO_CHECKSUM = -1;
/**
* URI used to serialize EObject with the model util.
*/
public static final URI VIRTUAL_URI = URI.createURI("virtualUri"); //$NON-NLS-1$
private static final String ORG_ECLIPSE_EMF_EMFSTORE_COMMON_MODEL = "org.eclipse.emf.emfstore.common.model"; //$NON-NLS-1$
private static final String DISCARD_DANGLING_HREF_ID = "org.eclipse.emf.emfstore.common.discardDanglingHREFs"; //$NON-NLS-1$
private static IResourceLogger resourceLogger = new IResourceLogger() {
public void logWarning(String msg) {
ModelUtil.logWarning(msg);
}
/**
* {@inheritDoc}
*
* @see org.eclipse.emf.emfstore.internal.common.model.util.IResourceLogger#logError(java.lang.String)
*/
public void logError(String msg) {
ModelUtil.logError(msg);
}
};
/**
* Contains all ID resolvers for singleton datatypes.
*/
private static Set<ESSingletonIdResolver> singletonIdResolvers;
private static HashMap<Object, Object> resourceLoadOptions;
private static HashMap<Object, Object> resourceSaveOptions;
private static Map<Object, Object> checksumSaveOptions;
private static SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"); //$NON-NLS-1$
/**
* Private constructor.
*/
private ModelUtil() {
// nothing to do
}
/**
* Creates a ModelElementId object from a string.
*
* @param id
* as string
* @return id as object
*/
public static ModelElementId createModelElementId(String id) {
final ModelElementId modelElementId = ModelFactory.eINSTANCE.createModelElementId();
modelElementId.setId(id);
return modelElementId;
}
/**
* Compares two {@link IdEObjectCollection}s. Order of the model elements at root level
* of a collection does not influence the outcome of this operations
*
* @param collectionA
* the first collection
* @param collectionB
* the second collection
* @return true if the two collections are equal, false otherwise
*/
public static boolean areEqual(IdEObjectCollection collectionA, IdEObjectCollection collectionB) {
long a;
long b;
try {
// collections are treated specially because the order
// of root elements should not matter
a = computeChecksum(collectionA);
b = computeChecksum(collectionB);
} catch (final SerializationException e) {
return false;
}
return a == b;
}
/**
* Copies the given EObject and converts it to a string.
*
* @param eObject
* the eObject to be serialized
* @return the string representation of the <code>eObject</code>
* @throws SerializationException
* if a serialization problem occurs
*/
public static String eObjectToString(EObject eObject) throws SerializationException {
return eObjectToString(eObject, getResourceSaveOptions());
}
/**
* Copies the given EObject and converts it to a string.
*
* @param eObject
* the eObject to be serialized
* @param saveOptions
* the EMF save options to be used during serialization
* @return the string representation of the <code>eObject</code>
* @throws SerializationException
* if a serialization problem occurs
*/
public static String eObjectToString(EObject eObject, Map<?, ?> saveOptions) throws SerializationException {
if (eObject == null) {
return null;
}
final ResourceSetImpl resourceSetImpl = new ResourceSetImpl();
resourceSetImpl.setResourceFactoryRegistry(new ResourceFactoryRegistry());
final XMIResource resource = (XMIResource) resourceSetImpl.createResource(VIRTUAL_URI);
((ResourceImpl) resource).setIntrinsicIDToEObjectMap(new HashMap<String, EObject>());
EObject copy;
if (eObject instanceof IdEObjectCollection) {
copy = copyIdEObjectCollection((IdEObjectCollection) eObject, resource);
} else {
copy = copyEObject(ModelUtil.getProject(eObject), eObject, resource);
}
if (saveOptions != null) {
return copiedEObjectToString(copy, resource, saveOptions);
}
return copiedEObjectToString(copy, resource);
}
/**
* Converts the given {@link EObject} to a string.
*
* @param copy The copied {@link EObject}.
* @param resource The resource for the {@link EObject}.
* @return The string representing the {@link EObject}.
* @throws SerializationException If a serialization problem occurs.
*/
private static String copiedEObjectToString(EObject copy, XMIResource resource) throws SerializationException {
return copiedEObjectToString(copy, resource, getChecksumSaveOptions());
}
/**
* Converts the given {@link EObject} to a string.
*
* @param copy The copied {@link EObject}.
* @param resource The resource for the {@link EObject}.
* @param saveOptions define the format of the returned serialization.
* @return The string representing the {@link EObject}.
* @throws SerializationException If a serialization problem occurs.
*/
private static String copiedEObjectToString(EObject copy, XMIResource resource, Map<?, ?> saveOptions)
throws SerializationException {
final int step = 200;
final int initialSize = step;
resource.getContents().add(copy);
final StringWriter stringWriter = new StringWriter(initialSize);
final URIConverter.WriteableOutputStream uws = new URIConverter.WriteableOutputStream(stringWriter,
CommonUtil.getEncoding());
try {
resource.save(uws, saveOptions);
} catch (final IOException e) {
throw new SerializationException(e);
} finally {
try {
uws.close();
} catch (final IOException exception) {
logException(exception);
}
}
return stringWriter.toString().trim();
}
/**
* Computes the checksum for a given string representing an {@link EObject}.
*
* @param eObjectString
* the string representing the {@link EObject}.
* @return the computed checksum
*
* @throws SerializationException
* in case any errors occur during computation of the checksum
*/
private static long computeChecksumLegacy(String eObjectString) {
long h = 1125899906842597L; // prime
final int len = eObjectString.length();
for (int i = 0; i < len; i++) {
final char c = eObjectString.charAt(i);
h = 31 * h + c;
}
return h;
}
/**
* Computes the checksum for a given {@link EObject}.
*
* @param eObject
* the EObject for which to compute a checksum
* @return the computed checksum
*
* @throws SerializationException
* in case any errors occur during computation of the checksum
*/
public static long computeChecksumLegacy(EObject eObject) throws SerializationException {
return computeChecksumLegacy(eObjectToString(eObject, getChecksumSaveOptions()));
}
/**
* Converts the given {@link EObject} to a string.
*
* @param copy The copied {@link EObject}.
* @param resource The resource for the {@link EObject}.
* @param saveOptions define the format of the returned serialization.
* @return The checksum of the serialized {@link EObject}.
* @throws SerializationException If a serialization problem occurs.
*/
private static long computeChecksumForCopiedEObject(EObject copy, XMIResource resource, Map<?, ?> saveOptions)
throws SerializationException {
resource.getContents().add(copy);
final ChecksumCalculatorWriter checksumCalculatorWriter = new ChecksumCalculatorWriter();
final URIConverter.WriteableOutputStream uws = new URIConverter.WriteableOutputStream(checksumCalculatorWriter,
CommonUtil.getEncoding());
try {
resource.save(uws, saveOptions);
} catch (final IOException e) {
throw new SerializationException(e);
} finally {
try {
uws.close();
} catch (final IOException exception) {
logException(exception);
}
}
return checksumCalculatorWriter.getChecksum();
}
/**
* Computes the checksum for a given {@link EObject}.
*
* @param eObject
* the EObject for which to compute a checksum
* @return the computed checksum
*
* @throws SerializationException
* in case any errors occur during computation of the checksum
*/
public static long computeChecksum(EObject eObject) throws SerializationException {
return computeChecksumInternal(eObject, false);
}
/**
* Computes the checksum for a given {@link IdEObjectCollection}.
* The checksum for a collection is independent of the order of the
* collection's elements at the root level.
*
* @param collection
* the collection for which to compute a checksum
* @return the computed checksum
*
* @throws SerializationException
* in case any errors occur during computation of the checksum
*/
public static long computeChecksum(IdEObjectCollection collection) throws SerializationException {
return computeChecksumInternal(collection, true);
}
private static long computeChecksumInternal(EObject eObject, boolean sort) throws SerializationException {
final ResourceSetImpl resourceSetImpl = new ResourceSetImpl();
// TODO: do we need to instantiate the factory registry each time?
resourceSetImpl.setResourceFactoryRegistry(new ResourceFactoryRegistry());
final XMIResource res = (XMIResource) resourceSetImpl.createResource(VIRTUAL_URI);
((ResourceImpl) res).setIntrinsicIDToEObjectMap(new HashMap<String, EObject>());
EObject copy;
if (eObject instanceof IdEObjectCollection) {
copy = copyIdEObjectCollection((IdEObjectCollection) eObject, res);
final IdEObjectCollection castedCopy = (IdEObjectCollection) copy;
if (sort) {
ECollections.sort(castedCopy.getModelElements(), new Comparator<EObject>() {
public int compare(EObject o1, EObject o2) {
return castedCopy.getModelElementId(o1).getId()
.compareTo(castedCopy.getModelElementId(o2).getId());
}
});
}
} else {
copy = copyEObject(ModelUtil.getProject(eObject), eObject, res);
}
return computeChecksumForCopiedEObject(copy, res, getChecksumSaveOptions());
}
/**
* Returns the resource logger.
*
* @return the resource logger
*/
public static IResourceLogger getResourceLogger() {
return resourceLogger;
}
/**
* Copies the given {@link IdEObjectCollection} and writes the IDs it contains into the given {@link XMIResource}.
*
* @param collection
* the collection to be copied
* @param res
* the resource into which the collection's IDs should be written into
* @return the copied collection
*/
public static IdEObjectCollection copyIdEObjectCollection(IdEObjectCollection collection, XMIResource res) {
final IdEObjectCollection copiedCollection = clone(collection);
for (final EObject modelElement : copiedCollection.getAllModelElements()) {
final ModelElementId modelElementId = copiedCollection.getModelElementId(modelElement);
if (modelElementId == null) {
continue;
}
res.setID(modelElement, modelElementId.getId());
}
for (final EObject modelElement : ((Project) copiedCollection).getCutElements()) {
final ModelElementId modelElementId = ((IdEObjectCollectionImpl) copiedCollection)
.getModelElementId(modelElement);
if (modelElementId == null) {
continue;
}
res.setID(modelElement, modelElementId.getId());
}
return copiedCollection;
}
// TODO: javadoc
private static EObject copyEObject(IdEObjectCollection collection, EObject object, XMIResource res) {
final IdEObjectCollection copiedCollection = copyIdEObjectCollection(collection, res);
final EObject copiedEObject = copiedCollection.getModelElement(collection.getModelElementId(object));
return copiedEObject;
}
/**
* Delivers a map of options for loading resources. Especially {@link XMLResource#OPTION_DEFER_IDREF_RESOLUTION}
* which speeds up loading
* due to our id based resources.
*
* @return map of options for {@link XMIResource} or {@link XMLResource}.
*/
@SuppressWarnings("rawtypes")
public static synchronized Map<Object, Object> getResourceLoadOptions() {
if (resourceLoadOptions == null) {
resourceLoadOptions = new LinkedHashMap<Object, Object>();
resourceLoadOptions.put(XMLResource.OPTION_DEFER_ATTACHMENT, Boolean.TRUE);
resourceLoadOptions.put(XMLResource.OPTION_DEFER_IDREF_RESOLUTION, Boolean.TRUE);
resourceLoadOptions.put(XMLResource.OPTION_USE_ENCODED_ATTRIBUTE_STYLE, Boolean.TRUE);
resourceLoadOptions.put(XMLResource.OPTION_ENCODING, CommonUtil.getEncoding());
resourceLoadOptions.put(XMLResource.OPTION_CONFIGURATION_CACHE, Boolean.TRUE);
}
// force re-init name-to-feature map
resourceLoadOptions.put(XMLResource.OPTION_USE_XML_NAME_TO_FEATURE_MAP, new HashMap());
return resourceLoadOptions;
}
/**
* Delivers a map of mandatory options for saving resources.
*
* @return map of options for {@link XMIResource} or {@link XMLResource}.
*/
public static synchronized Map<Object, Object> getResourceSaveOptions() {
if (resourceSaveOptions == null) {
resourceSaveOptions = new LinkedHashMap<Object, Object>();
resourceSaveOptions.put(XMLResource.OPTION_USE_ENCODED_ATTRIBUTE_STYLE, Boolean.TRUE);
resourceSaveOptions.put(XMLResource.OPTION_ENCODING, CommonUtil.getEncoding());
resourceSaveOptions.put(XMLResource.OPTION_FLUSH_THRESHOLD, 100000);
resourceSaveOptions.put(XMLResource.OPTION_USE_FILE_BUFFER, Boolean.TRUE);
resourceSaveOptions.put(XMLResource.OPTION_CONFIGURATION_CACHE, Boolean.TRUE);
final ESExtensionPoint extensionPoint = new ESExtensionPoint(DISCARD_DANGLING_HREF_ID);
final Boolean discardDanglingHREFs = extensionPoint.getBoolean("value", Boolean.FALSE); //$NON-NLS-1$
if (discardDanglingHREFs) {
resourceSaveOptions.put(XMLResource.OPTION_PROCESS_DANGLING_HREF,
XMLResource.OPTION_PROCESS_DANGLING_HREF_RECORD);
}
logInfo(Messages.ModelUtil_Save_Options_Initialized);
for (final Map.Entry<Object, Object> entry : resourceSaveOptions.entrySet()) {
logInfo("\t" + entry.getKey() + ": " + entry.getValue()); //$NON-NLS-1$ //$NON-NLS-2$
}
}
// force re-init of list
resourceSaveOptions.put(XMLResource.OPTION_USE_CACHED_LOOKUP_TABLE, new ArrayList<Object>());
return resourceSaveOptions;
}
/**
* Delivers a map of options that is used while computing a checksum.
*
* @return map of options for {@link XMIResource} or {@link XMLResource}.
*/
public static synchronized Map<Object, Object> getChecksumSaveOptions() {
if (checksumSaveOptions == null) {
final Map<Object, Object> saveOptions = new LinkedHashMap<Object, Object>(getResourceSaveOptions());
saveOptions.put(XMLResource.OPTION_DECLARE_XML, Boolean.FALSE);
saveOptions.put(XMLResource.OPTION_FORMATTED, Boolean.FALSE);
checksumSaveOptions = saveOptions;
}
return checksumSaveOptions;
}
/**
* Saves a given resource and logs any warning and/or errors.
*
* @param resource
* the resource to be saved
* @param logger
* a logger instance which will be used to log warnings and errors on resources
* @throws IOException
* in case an exception occurs during save
*/
public static void saveResource(Resource resource, IResourceLogger logger) throws IOException {
try {
resource.save(ModelUtil.getResourceSaveOptions());
} finally {
logWarningsAndErrors(resource, logger);
}
}
/**
* Loads a given resource and logs any warning and/or errors.
*
* @param resource
* the resource to be loaded
* @param logger
* a logger instance which will be used to log warnings and errors on resources
* @throws IOException
* in case an exception occurs during load
*/
public static void loadResource(Resource resource, IResourceLogger logger) throws IOException {
try {
resource.load(ModelUtil.getResourceLoadOptions());
} catch (final IOException e) {
// rethrow exception
throw e;
} finally {
logWarningsAndErrors(resource, logger);
}
}
private static void logWarningsAndErrors(Resource resource, IResourceLogger logger) {
if (resource.getWarnings().size() > 0) {
for (final Diagnostic diagnostic : resource.getErrors()) {
logger.logWarning(logDiagnostic(diagnostic).toString());
}
}
if (resource.getErrors().size() > 0) {
for (final Diagnostic diagnostic : resource.getErrors()) {
logger.logError(logDiagnostic(diagnostic).toString());
}
}
}
private static StringWriter logDiagnostic(Diagnostic diagnostic) {
final StringWriter error = new StringWriter();
error.append(diagnostic.getLocation() + "\n"); //$NON-NLS-1$
error.append(diagnostic.getMessage() + "\n"); //$NON-NLS-1$
if (diagnostic instanceof Exception) {
final StringWriter stringWriter = new StringWriter();
final PrintWriter printWriter = new PrintWriter(stringWriter);
((Throwable) diagnostic).printStackTrace(printWriter);
error.append(stringWriter.toString() + "\n"); //$NON-NLS-1$
}
return error;
}
private static boolean canHaveInstances(EClass eClass) {
return !(eClass.isAbstract() || eClass.isInterface());
}
/**
* Recursively goes through model and create a list of all non-Abstract
* classes.
*
* @param ePackage
* the package to start with.
* @return list of all non-Abstract model element classes in starting
* package and its sub-packages
*/
public static Set<EClass> getNonAbstractMETypes(EPackage ePackage) {
final Set<EClass> nonAbstractMETypes = new LinkedHashSet<EClass>();
final Set<EClass> allMETypes = getAllMETypes(ePackage);
final Iterator<EClass> iterator = allMETypes.iterator();
while (iterator.hasNext()) {
final EClass eClass = iterator.next();
if (canHaveInstances(eClass)) {
nonAbstractMETypes.add(eClass);
}
}
return nonAbstractMETypes;
}
/**
* Recursively goes through package and returns a list of all EClasses
* inheriting ModelElement (abstract classes and interfaces are also
* include).
*
* @param ePackage
* starting package
* @return a list of all EClasses inheriting ModelElement (inclusive
* abstract classes and interfaces) in starting package and all its
* sub-packages.
*/
public static Set<EClass> getAllMETypes(EPackage ePackage) {
final Set<EClass> meTypes = new LinkedHashSet<EClass>();
for (final EObject eObject : ePackage.eContents()) {
if (eObject instanceof EClass) {
final EClass eClass = (EClass) eObject;
meTypes.add(eClass);
} else if (eObject instanceof EPackage) {
final EPackage eSubPackage = (EPackage) eObject;
meTypes.addAll(getAllMETypes(eSubPackage));
}
}
return meTypes;
}
/**
* This will add a new entry to error log view of eclipse.
*
* @param message
* message
* @param exception
* exception
* @param statusInt
* severity. Use one of constants in
* org.eclipse.core.runtime.Status class.
*/
public static void log(String message, Throwable exception, int statusInt) {
final Bundle bundle = Platform.getBundle(ORG_ECLIPSE_EMF_EMFSTORE_COMMON_MODEL);
if (bundle == null) {
return;
}
final Status status = new Status(statusInt, bundle.getSymbolicName(), statusInt, message, exception);
Platform.getLog(bundle).log(status);
}
/**
* Log an exception to the platform log. This will create a popup in the ui.
*
* @param message
* the message
* @param exception
* the exception
*/
public static void logException(String message, Throwable exception) {
log(message, exception, IStatus.ERROR);
}
/**
* Log an exception to the platform log. This will create a popup in the ui.
*
* @param exception
* the exception
*/
public static void logException(Throwable exception) {
logException(exception.getMessage(), exception);
}
/**
* Log a warning to the platform log. This will NOT create a popup in the UI.
*
* @param message
* the message
* @param exception
* the exception
*/
public static void logWarning(String message, Throwable exception) {
log(message, exception, IStatus.WARNING);
}
/**
* Log a warning to the platform log. This will NOT create a popup in the UI.
*
* @param message
* the message being logged
*/
public static void logWarning(String message) {
log(message, null, IStatus.WARNING);
}
/**
* Log a error to the platform log. This will NOT create a popup in the UI.
*
* @param message
* the message being logged
*/
public static void logError(String message) {
log(message, null, IStatus.ERROR);
}
/**
* Log an exception to the platform log. This will create a popup in the ui.
*
* @param message
* the message
*/
public static void logInfo(String message) {
log(message, null, IStatus.INFO);
}
/**
* Logs detailed information about the project state.
*
* @param message the message
* @param user the user name
* @param projectName the project name
* @param projectId the project id
* @param branch the branch
* @param revision the revision
*/
public static void logProjectDetails(
String message,
String user,
String projectName,
String projectId,
String branch,
int revision) {
if (!Boolean.getBoolean("emfstore.logDetails")) { //$NON-NLS-1$
return;
}
logInfo(MessageFormat.format(
"Time: {0} | Msg: {1} | User: {2} | Project Name: {3} | Project Id: {4} | Branch: {5} | Revision: {6}", //$NON-NLS-1$
dateFormat.format(new Date()),
message != null ? message : "", //$NON-NLS-1$
user != null ? user : "not available", //$NON-NLS-1$
projectName != null ? projectName : "not available", //$NON-NLS-1$
projectId != null ? projectId : "not available", //$NON-NLS-1$
branch != null ? branch : "not available", //$NON-NLS-1$
revision != -1 ? revision : "not available")); //$NON-NLS-1$
}
/**
* Clone any EObject.
*
* @param <T>
* the Eobject sub type
* @param eObject
* the Eobject instance
* @return a clone of the Eobject instance
*/
@SuppressWarnings("unchecked")
public static <T extends EObject> T clone(T eObject) {
if (eObject instanceof ProjectImpl) {
return (T) ((ProjectImpl) eObject).copy();
}
final EObject clone = EcoreUtil.copy(eObject);
return (T) clone;
}
/**
* Clone a list of EObjects.
*
* @param <T>
* the EObject sub type the list consists of
* @param list
* the list instance
* @return a clone of the list and its contents instance
*/
@SuppressWarnings("unchecked")
public static <T extends EObject> List<T> clone(List<T> list) {
final ArrayList<T> result = new ArrayList<T>();
for (final EObject eObject : list) {
final T clone = (T) ModelUtil.clone(eObject);
result.add(clone);
}
return result;
}
/**
* Create a flat clone of the list, the list if cloned but ot its content.
*
* @param <T>
* the list type parameter
* @param originalList
* the original list
* @return a flat copy
*/
public static <T extends EObject> List<T> flatCloneList(List<T> originalList) {
final List<T> clonedList = new ArrayList<T>(originalList.size());
for (final T element : originalList) {
clonedList.add(element);
}
return clonedList;
}
/**
* Load an EObject from a resource, the resource is supposed to contain only
* one root object of the given EClass type. Type T must match EClass type.
*
* @param <T>
* Type of the EObject
* @param eClass
* the EClass of the EObject
* @param resourceURI
* the resources URI
* @param checkConstraints
* whether to perform additional sanity checks. These checks
* basically try to enforce that a resource contains exactly one
* object.
* @return the object loaded from the resource
* @throws IOException
* if loading the object from the resource fails.
*/
@SuppressWarnings("unchecked")
public static <T extends EObject> T loadEObjectFromResource(EClass eClass, URI resourceURI,
boolean checkConstraints)
throws IOException {
final ResourceSet resourceSet = createResourceSetForURI(resourceURI);
Resource resource;
if (checkConstraints) {
resource = resourceSet.getResource(resourceURI, false);
} else {
resource = resourceSet.getResource(resourceURI, true);
}
loadResource(resource, resourceLogger);
final EList<EObject> contents = resource.getContents();
if (checkConstraints) {
if (contents.size() > 1) {
throw new IOException(Messages.ModelUtil_Resource_Contains_Multiple_Objects);
}
}
if (contents.size() < 1) {
throw new IOException(Messages.ModelUtil_Resource_Contains_No_Objects);
}
final EObject eObject = contents.get(0);
if (eObject instanceof Project && resource instanceof XMIResource) {
final XMIResource xmiResource = (XMIResource) resource;
final Project project = (Project) eObject;
final Map<EObject, String> eObjectToIdMap = new LinkedHashMap<EObject, String>();
final Map<String, EObject> idToEObjectMap = new LinkedHashMap<String, EObject>();
final TreeIterator<EObject> it = project.eAllContents();
while (it.hasNext()) {
final EObject obj = it.next();
final String id = xmiResource.getID(obj);
if (id != null) {
eObjectToIdMap.put(obj, id);
idToEObjectMap.put(id, obj);
}
}
project.initMapping(eObjectToIdMap, idToEObjectMap);
}
if (!eClass.isInstance(eObject)) {
throw new IOException(Messages.ModelUtil_Resource_Contains_No_Objects_Of_Given_Class);
}
return (T) eObject;
}
/**
* Creates and returns a new ResourceSet which may be used to load the given URI.
*
* @param resourceURI the resource URI
* @return the newly created resourceset
*/
public static ResourceSet createResourceSetForURI(URI resourceURI) {
ResourceSet resourceSet = null;
if (resourceURI != null && resourceURI.scheme() != null && resourceURI.scheme().equals("emfstore")) { //$NON-NLS-1$
ESExtensionPoint extensionPoint = null;
if (resourceURI.authority().equals("workspaces")) { //$NON-NLS-1$
extensionPoint = new ESExtensionPoint(CLIENT_RESOURCE_SET_PROVIDER_EXT_POINT_ID,
false, new ESPriorityComparator("priority", true)); //$NON-NLS-1$
} else {
extensionPoint = new ESExtensionPoint(SERVER_RESOURCE_SET_PROVIDER_EXT_POINT_ID,
false, new ESPriorityComparator("priority", true)); //$NON-NLS-1$
}
final ESResourceSetProvider resourceSetProvider = extensionPoint
.getElementWithHighestPriority().getClass("class", //$NON-NLS-1$
ESResourceSetProvider.class);
if (resourceSetProvider == null) {
resourceSet = new ResourceSetImpl();
} else {
resourceSet = resourceSetProvider.getResourceSet();
}
}
if (resourceSet == null) {
resourceSet = new ResourceSetImpl();
}
return resourceSet;
}
/**
* Save a list of EObjects to the resource with the given URI.
*
* @param eObjects
* the EObjects to be saved
* @param resourceURI
* the URI of the resource, which should be used to save the
* EObjects
* @param options The save options for the resource.
* @throws IOException
* if saving to the resource fails
*/
public static void saveEObjectToResource(List<? extends EObject> eObjects, URI resourceURI,
Map<Object, Object> options) throws IOException {
final ResourceSet resourceSet = new ResourceSetImpl();
final Resource resource = resourceSet.createResource(resourceURI);
final EList<EObject> contents = resource.getContents();
for (final EObject eObject : eObjects) {
contents.add(eObject);
if (eObject instanceof Project && resource instanceof XMIResource) {
setXmiIdsOnResource((Project) eObject, (XMIResource) resource);
}
}
contents.addAll(eObjects);
resource.save(options);
}
/**
* Save a list of EObjects to the resource with the given URI.
*
* @param eObjects
* the EObjects to be saved
* @param resourceURI
* the URI of the resource, which should be used to save the
* EObjects
* @throws IOException
* if saving to the resource fails
*/
public static void saveEObjectToResource(List<? extends EObject> eObjects, URI resourceURI) throws IOException {
saveEObjectToResource(eObjects, resourceURI, null);
}
/**
* Set all IDs contained in the project as XMI IDs for the model elements in
* the project.
*
* @param project
* a project
* @param xmiResource
* the resource that will contain the XMI IDs
*/
public static void setXmiIdsOnResource(Project project, XMIResource xmiResource) {
for (final EObject modelElement : project.getAllModelElements()) {
final ModelElementId modelElementId = project.getModelElementId(modelElement);
xmiResource.setID(modelElement, modelElementId.getId());
}
}
/**
* Save an EObject to a resource.
*
* @param eObject
* the object
* @param resourceURI
* the resources URI
* @throws IOException
* if saving to the resource fails.
*/
public static void saveEObjectToResource(EObject eObject, URI resourceURI) throws IOException {
final ArrayList<EObject> list = new ArrayList<EObject>();
list.add(eObject);
saveEObjectToResource(list, resourceURI);
}
/**
* Deletes all resources from resourceSet, which string representation of URI starts with prefix.
*
* @param resourceSet resource set
* @param prefix string prefix of the resource path
* @throws IOException if deleting the resource fails
*/
public static void deleteResourcesWithPrefix(ResourceSet resourceSet, String prefix) throws IOException {
final List<Resource> toDelete = new ArrayList<Resource>();
for (final Resource resource : resourceSet.getResources()) {
if (resource.getURI().toFileString().startsWith(prefix)) {
toDelete.add(resource);
}
}
for (final Resource resource : toDelete) {
resource.delete(null);
}
}
/**
* Get Project that contains a model element.
*
* @param modelElement
* the model element
* @return the project or null if the element is not contained in a project.
*/
public static Project getProject(EObject modelElement) {
final Set<EObject> seenModelElements = new LinkedHashSet<EObject>();
seenModelElements.add(modelElement);
return getParent(Project.class, modelElement, seenModelElements);
}
/**
* Searches for the project and then looks for the modelelement id.
*
* @param modelElement me
* @return id
*/
public static ModelElementId getModelElementId(EObject modelElement) {
final Project project = getProject(modelElement);
if (project == null) {
return null;
}
return project.getModelElementId(modelElement);
}
/**
* Get the EContainer that contains the given model element and whose {@code eContainer} is {@code null}.
*
* @param parent
* the class of the parent
* @param child
* the model element whose container should get returned
* @param <T> type of the parent class
* @return the container
*/
public static <T extends EObject> T getParent(Class<T> parent, EObject child) {
final Set<EObject> seenModelElements = new LinkedHashSet<EObject>();
seenModelElements.add(child);
return getParent(parent, child, seenModelElements);
}
@SuppressWarnings("unchecked")
private static <T extends EObject> T getParent(Class<T> parent, EObject child, Set<EObject> seenModelElements) {
if (child == null) {
return null;
}
if (seenModelElements.contains(child.eContainer())) {
throw new IllegalStateException(Messages.ModelUtil_ModelElement_Is_In_Containment_Cycle);
}
if (parent.isInstance(child)) {
return (T) child;
}
seenModelElements.add(child);
return getParent(parent, child.eContainer(), seenModelElements);
}
/**
* Whether a {@link EClass} is a association class. Association classes are
* not displayed as dedicated elements. A link from one element to another
* which goes over an association class is displayed by a dedicated widget.
* This widgets allows to trace transparently without seeing the association
* class.
*
* @param eClazz
* the {@link EClass}
* @return if it is an association
*/
public static boolean isAssociationClassElement(EClass eClazz) {
if (eClazz == null || eClazz.isAbstract() || eClazz.getInstanceClass() == null) {
return false;
}
return AssociationClassElement.class.isAssignableFrom(eClazz.getInstanceClass());
}
/**
* Get all contained elements of a given element.
*
* @param modelElement
* the model element
* @param includeTransientContainments
* true if transient containments should be included in the
* result
* @return a set of contained model elements
*/
public static Set<EObject> getAllContainedModelElements(EObject modelElement,
boolean includeTransientContainments) {
return getAllContainedModelElements(modelElement, includeTransientContainments, false);
}
/**
* Get all contained elements of a given element.
*
* @param modelElement
* the model element
* @param includeTransientContainments
* true if transient containments should be included in the
* result
* @param ignoreSingletonDatatypes
* whether to ignore singleton datatypes like, for example,
* EString
* @return a set of contained model elements
*/
public static Set<EObject> getAllContainedModelElements(EObject modelElement, boolean includeTransientContainments,
boolean ignoreSingletonDatatypes) {
return getAllContainedModelElements(Collections.singletonList(modelElement), includeTransientContainments,
ignoreSingletonDatatypes);
}
/**
* Get all contained elements of a given resource.
*
* @param resource
* the resource
* @param includeTransientContainments
* true if transient containments should be included in the
* result
* @param ignoreSingletonDatatypes
* whether to ignore singleton datatypes like, for example,
* EString
* @return a set of contained model elements
* Get all
*/
public static Set<EObject> getAllContainedModelElements(Resource resource, boolean includeTransientContainments,
boolean ignoreSingletonDatatypes) {
return getAllContainedModelElements(resource.getContents(), includeTransientContainments,
ignoreSingletonDatatypes);
}
/**
* Get all contained elements of a given collection of model elements.
*
* @param modelElements
* a collection of elements
* @param includeTransientContainments
* true if transient containments should be included in the
* result
* @param ignoreSingletonDatatypes
* whether to ignore singleton datatypes like, for example,
* EString
* @return a set of contained model elements
*/
public static Set<EObject> getAllContainedModelElements(Collection<EObject> modelElements,
boolean includeTransientContainments, boolean ignoreSingletonDatatypes) {
final Set<EObject> result = new LinkedHashSet<EObject>();
for (final EObject modelElement : modelElements) {
for (final EObject containee : modelElement.eContents()) {
if (ignoreSingletonDatatypes && isSingleton(containee)) {
continue;
}
if (!containee.eContainingFeature().isTransient() || includeTransientContainments) {
final Set<EObject> elements = getAllContainedModelElements(containee, includeTransientContainments,
ignoreSingletonDatatypes);
result.add(containee);
result.addAll(elements);
}
}
}
return result;
}
/**
* Get the container of an EObject.
*
* @param modelElement
* the model element
* @return the container
*/
public static EObject getContainerModelElement(EObject modelElement) {
final EObject container = modelElement.eContainer();
if (container == null) {
return null;
}
if (EcoreFactory.eINSTANCE.getEcorePackage().getEObject().isInstance(container)) {
return container;
}
return null;
}
/**
* Get all contained elements of a given element as a list.
*
* @param modelElement
* the model element
* @param includeTransientContainments
* true if transient containments should be included in the
* result
* @return a list of contained model elements
*/
public static List<EObject> getAllContainedModelElementsAsList(EObject modelElement,
boolean includeTransientContainments) {
final TreeIterator<EObject> it = modelElement.eAllContents();
final List<EObject> result = new ArrayList<EObject>();
while (it.hasNext()) {
final EObject containee = it.next();
if (containee.eContainingFeature() != null && !containee.eContainingFeature().isTransient()
|| includeTransientContainments) {
result.add(containee);
}
}
return result;
}
/**
* Delete the given incoming cross references to the given model element from any
* other model element in the given collection except for the set of source elements
* to ignore.
*
* @param inverseReferences a collection of inverse references
* @param element
* the model element
* @param referenceSourceElementsToIgnore
* the set of all elements referencing the element to be ignored
*/
public static void deleteIncomingCrossReferencesToElement(
EObject element,
Collection<Setting> inverseReferences,
Set<EObject> referenceSourceElementsToIgnore) {
for (final Setting setting : inverseReferences) {
final EStructuralFeature eStructuralFeature = setting.getEStructuralFeature();
final EReference reference = (EReference) eStructuralFeature;
final EObject sourceElement = setting.getEObject();
if (referenceSourceElementsToIgnore.contains(sourceElement)) {
continue;
}
if (reference.isContainer() || reference.isContainment() || !reference.isChangeable()) {
continue;
}
if (eStructuralFeature.isMany()) {
((EList<?>) sourceElement.eGet(eStructuralFeature)).remove(element);
} else {
if (sourceElement instanceof Map.Entry<?, ?> && eStructuralFeature.getName().equals("key")) { //$NON-NLS-1$
logWarning(MessageFormat.format(
Messages.ModelUtil_Incoming_CrossRef_Is_Map_Key, element));
}
sourceElement.eUnset(eStructuralFeature);
}
}
}
/**
* Delete all outgoing cross references of the given model element to any element in the given collection.
*
* @param collection the collection
* @param modelElement
* the model element
*/
public static void deleteOutgoingCrossReferences(IdEObjectCollection collection, EObject modelElement) {
final Set<EObject> allModelElements = new LinkedHashSet<EObject>();
allModelElements.add(modelElement);
allModelElements.addAll(ModelUtil.getAllContainedModelElements(modelElement, false));
final List<SettingWithReferencedElement> crossReferences = collectOutgoingCrossReferences(collection,
allModelElements);
for (final SettingWithReferencedElement settingWithReferencedElement : crossReferences) {
final Setting setting = settingWithReferencedElement.getSetting();
if (!settingWithReferencedElement.getSetting().getEStructuralFeature().isMany()) {
setting.getEObject().eUnset(setting.getEStructuralFeature());
} else {
final List<?> references = (List<?>) setting.getEObject().eGet(setting.getEStructuralFeature());
references.remove(settingWithReferencedElement.getReferencedElement());
}
}
}
/**
* Retrieve all outgoing connections from the model elements to other elements in the collection.
*
* @param collection the collection
* @param modelElements the model elements
* @return a List of references
*/
public static List<SettingWithReferencedElement> collectOutgoingCrossReferences(IdEObjectCollection collection,
Set<EObject> modelElements) {
// result object
final List<SettingWithReferencedElement> settings = new ArrayList<SettingWithReferencedElement>();
for (final EObject currentElement : modelElements) {
for (final EReference reference : currentElement.eClass().getEAllReferences()) {
final EClassifier eType = reference.getEType();
// sanity checks
if (reference.isContainer() || reference.isContainment() || !reference.isChangeable()
|| !(eType instanceof EClass)) {
continue;
}
final Setting setting = ((InternalEObject) currentElement).eSetting(reference);
// multi references
if (reference.isMany()) {
@SuppressWarnings("unchecked")
final List<EObject> referencedElements = (List<EObject>) currentElement.eGet(reference);
for (final EObject referencedElement : referencedElements) {
if (shouldBeCollected(collection, modelElements, referencedElement)) {
settings.add(new SettingWithReferencedElement(setting, referencedElement));
}
}
} else {
// single references
final EObject referencedElement = (EObject) currentElement.eGet(reference);
if (shouldBeCollected(collection, modelElements, referencedElement)) {
settings.add(new SettingWithReferencedElement(setting, referencedElement));
}
}
}
}
return settings;
}
/**
* Checks if the referenced elements is an element in the given collection which is not a singleton, not an ignored
* data type and not already contained in the given set of elements.
*
* @param collection the collection
* @param allModelElements the set of model elements
* @param referencedElement the referenced element
* @return true, if the specified conditions are met.
*/
public static boolean shouldBeCollected(IdEObjectCollection collection, Set<EObject> allModelElements,
EObject referencedElement) {
if (referencedElement == null) {
return false;
}
if (!collection.contains(referencedElement)
&& ((ProjectImpl) collection).getDeletedModelElementId(referencedElement) == null) {
return false;
}
return !ModelUtil.isSingleton(referencedElement) && !allModelElements.contains(referencedElement);
}
/**
* Get the singleton instance for a given model element id.
*
* @param singletonId
* the id
* @return the singleton instance
*/
public static EObject getSingleton(ModelElementId singletonId) {
initSingletonIdResolvers();
for (final ESSingletonIdResolver resolver : singletonIdResolvers) {
final EObject singleton = resolver.getSingleton(singletonId.toAPI());
if (singleton != null) {
return singleton;
}
}
return null;
}
/**
* Get the singleton id for a singleton instance.
*
* @param singleton
* the singleton
* @return the id
*
* @see org.eclipse.emf.emfstore.common.model.ESSingletonIdResolver#getSingletonModelElementId(org.eclipse.emf.ecore.EObject)
*/
public static ModelElementId getSingletonModelElementId(EObject singleton) {
initSingletonIdResolvers();
for (final ESSingletonIdResolver resolver : singletonIdResolvers) {
final ESModelElementIdImpl id = (ESModelElementIdImpl) resolver.getSingletonModelElementId(singleton);
if (id != null) {
return clone(id.toInternalAPI());
}
}
return null;
}
/**
* Return whether the given eObject instance is a singleton.
*
* @param eObject
* the instance
* @return true if it is a singleton
*
* @see org.eclipse.emf.emfstore.common.model.ESSingletonIdResolver#isSingleton(org.eclipse.emf.ecore.EObject)
*/
public static boolean isSingleton(EObject eObject) {
initSingletonIdResolvers();
for (final ESSingletonIdResolver resolver : singletonIdResolvers) {
if (resolver.isSingleton(eObject)) {
return true;
}
}
return false;
}
/**
* Initializes all available {@link ESSingletonIdResolver}.
*/
private static synchronized void initSingletonIdResolvers() {
if (singletonIdResolvers == null) {
// collect singleton ID resolvers
singletonIdResolvers = new LinkedHashSet<ESSingletonIdResolver>();
for (final ESExtensionElement element : new ESExtensionPoint(
SINGLETON_ID_RESOLVER_EXT_POINT_ID).getExtensionElements()) {
try {
singletonIdResolvers.add(element.getClass("class", ESSingletonIdResolver.class)); //$NON-NLS-1$
} catch (final ESExtensionPointException e) {
ModelUtil.logWarning(Messages.ModelUtil_SingletonIdResolver_Not_Instantiated + e.getMessage());
}
}
}
}
}