/**
* <copyright> Copyright (c) 2008-2009 Jonas Helming, Maximilian Koegel. 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 </copyright>
*/
package org.eclipse.emf.emfstore.modelgenerator.common;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.Map.Entry;
import org.eclipse.emf.common.util.TreeIterator;
import org.eclipse.emf.ecore.EAttribute;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EClassifier;
import org.eclipse.emf.ecore.EEnum;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EPackage;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.EPackage.Registry;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.EcorePackage;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.emf.edit.command.AddCommand;
import org.eclipse.emf.edit.command.RemoveCommand;
import org.eclipse.emf.edit.command.SetCommand;
import org.eclipse.emf.edit.domain.AdapterFactoryEditingDomain;
import org.eclipse.emf.edit.domain.EditingDomain;
import org.eclipse.emf.emfstore.modelgenerator.common.attribute.AttributeHandler;
import org.eclipse.emf.emfstore.modelgenerator.common.attribute.IAttributeSetter;
/**
* Utility class to generate and change Ecore models.
* All methods should be accessed in a static way,
* therefore the constructor is not visible.
*/
public final class ModelGeneratorUtil {
/**
* Set of all EClasses that are contained in EPackages that are
* currently registered in the EPackage registry.
*
* @see #getAllEClasses()
*/
private static List<EClass> allEClasses;
/**
* Set of all EPackages that are currently registered in the
* EPackage registry and not contained in any other package.
*
* @see #getAllRootEPackages()
*/
private static Set<EPackage> rootModelPackages;
/**
* Map that maps EPackages to a set of all their contained EClasses.
*
* @see #getAllEClasses(EPackage)
*/
private static Map<EPackage, List<EClass>> packageToModelElementEClasses = new LinkedHashMap<EPackage, List<EClass>>();
/**
* Map that maps EReferences to all possible EClasses that can be contained
* when using them.
*
* @see #getAllEContainments(EReference)
*/
private static Map<EReference, List<EClass>> allEContainments = new LinkedHashMap<EReference, List<EClass>>();
/**
* Map that maps EClasses to their subclasses.
*
* @see #getAllSubEClasses(EClass)
*/
private static Map<EClass, List<EClass>> eClassToSubEClasses = new LinkedHashMap<EClass, List<EClass>>();
/**
* Private constructor.
*/
private ModelGeneratorUtil() {
// all methods should be accessed in a static way
}
/**
* Returns the EPackage to the specified <code>nsURI</code>.
*
* @param nsURI the NsUri of the EPackage to get
* @return the EPackage belonging to <code>nsURI</code>
* @see Registry#getEPackage(String)
*/
public static EPackage getEPackage(String nsURI) {
return EPackage.Registry.INSTANCE.getEPackage(nsURI);
}
/**
* Returns all EPackages on the root level that are currently
* registered in the registry.
*
* @return a Set of all root EPackages
* @see Registry
*/
public static Set<EPackage> getAllRootEPackages() {
// were the root packages computed before?
if(rootModelPackages != null) {
return rootModelPackages;
}
rootModelPackages = new LinkedHashSet<EPackage>();
Registry registry = EPackage.Registry.INSTANCE;
for (Entry<String, Object> entry : registry.entrySet()) {
EPackage ePackage = registry.getEPackage(entry.getKey());
// is the current EPackage a root package?
if (ePackage.getESuperPackage() == null){
rootModelPackages.add(ePackage);
}
}
return rootModelPackages;
}
/**
* Retrieve all EClasses that are contained in <code>ePackage</code>.
*
* @param ePackage the package to get contained EClasses from
* @return a set of EClasses contained in <code>ePackage</code>
*/
public static List<EClass> getAllEClasses(EPackage ePackage) {
if(packageToModelElementEClasses.containsKey(ePackage)) {
return packageToModelElementEClasses.get(ePackage);
}
if(ePackage == null) {
packageToModelElementEClasses.put(ePackage, new LinkedList<EClass>());
return packageToModelElementEClasses.get(ePackage);
}
List<EClass> result = new LinkedList<EClass>();
// obtain all EClasses from sub packages
for(EPackage subPackage : ePackage.getESubpackages()) {
result.addAll(getAllEClasses(subPackage));
}
// obtain all EClasses that are direct contents of the EPackage
for(EClassifier classifier : ePackage.getEClassifiers()) {
if(classifier instanceof EClass) {
result.add((EClass) classifier);
}
}
// save the result for upcoming method calls
packageToModelElementEClasses.put(ePackage, result);
return result;
}
/**
* Iterates over all registered EPackages in order to retrieve all available
* EClasses, excluding abstract classes and interfaces, and returns them as a Set.
*
* @return a set of all EClasses that are contained in registered EPackages
* @see Registry
*/
public static List<EClass> getAllEClasses() {
// were all EClasses computed before?
if (allEClasses != null) {
return allEClasses;
}
allEClasses = new LinkedList<EClass>();
Registry registry = EPackage.Registry.INSTANCE;
// for all registered EPackages
for (Entry<String, Object> entry : registry.entrySet()) {
EPackage ePackage = registry.getEPackage(entry.getKey());
for(EClass eClass : getAllEClasses(ePackage)) {
// no abstracts or interfaces
if(canHaveInstance(eClass)) {
allEClasses.add(eClass);
}
}
}
return allEClasses;
}
/**
* Returns all possible EClasses, excluding abstract classes and interfaces,
* that can be contained when using <code>reference</code>.
*
* @param reference the EReference to get containable EClasses for
* @return a set of all EClasses that can be contained when using <code>reference</code>
*/
public static List<EClass> getAllEContainments(EReference reference) {
if(allEContainments.containsKey(reference)) {
return allEContainments.get(reference);
}
if(reference == null) {
allEContainments.put(reference, new LinkedList<EClass>());
return allEContainments.get(reference);
}
List<EClass> result = new LinkedList<EClass>();
EClass referenceType = reference.getEReferenceType();
// no abstracts or interfaces
if(canHaveInstance(referenceType)) {
result.add(referenceType);
}
// 'referenceType: EObject' allows all kinds of EObjects
if(EcorePackage.eINSTANCE.getEObject().equals(referenceType)) {
return getAllEClasses();
}
// all sub EClasses can be referenced as well
result.addAll(getAllSubEClasses(referenceType));
// save the result for upcoming method calls
allEContainments.put(reference, result);
return result;
}
/**
* Returns whether <code>eClass</code> can be instantiated or not.
* An EClass can be instantiated, if it is neither an interface nor abstract.
*
* @param eClass the EClass in question
* @return whether <code>eClass</code> can be instantiated or not.
*/
public static boolean canHaveInstance(EClass eClass) {
return !eClass.isInterface() && !eClass.isAbstract();
}
/**
* Returns all subclasses of an EClass, excluding abstract classes
* and interfaces.
*
* @param eClass the EClass to get subclasses for
* @return all subclasses of <code>eClass</code>
*/
public static List<EClass> getAllSubEClasses(EClass eClass) {
if(eClassToSubEClasses.containsKey(eClass)) {
return eClassToSubEClasses.get(eClass);
}
if(eClass == null) {
eClassToSubEClasses.put(eClass, new LinkedList<EClass>());
return eClassToSubEClasses.get(eClass);
}
List<EClass> allEClasses = getAllEClasses();
List<EClass> result = new LinkedList<EClass>();
for (EClass possibleSubClass : allEClasses) {
// is the EClass really a subClass, while not being abstract or an interface?
if (eClass.isSuperTypeOf(possibleSubClass) && canHaveInstance(possibleSubClass)) {
result.add(possibleSubClass);
}
}
// save the result for upcoming method calls
eClassToSubEClasses.put(eClass, result);
return result;
}
/**
* Returns all containing references where <code>parentClass</code> is
* the container and <code>childClass</code> the containment.
*
* @param childClass the EClass which shall be contained
* @param parentClass the EClass to get containment references from
* @return all possible container-references as a set
*/
public static Set<EReference> getAllPossibleContainingReferences(EClass childClass, EClass parentClass) {
List<EReference> allReferences = parentClass.getEAllContainments();
Set<EReference> result = new LinkedHashSet<EReference>();
for(EReference reference : allReferences) {
EClass referenceType = reference.getEReferenceType();
if(referenceType == null) {
continue;
}
// is the reference type a perfect match?
if(referenceType.equals(childClass)) {
result.add(reference);
// is the reference type either EObject or a super type?
} else if (referenceType.equals(EcorePackage.eINSTANCE.getEObject())
|| referenceType.isSuperTypeOf(childClass)) {
result.add(reference);
}
}
return result;
}
/**
* Returns all direct and indirect contents of <code>rootObject</code> as a map.
* All EObjects that appear in these contents are mapped to their corresponding
* EClass.
*
* @param rootObject the EObject to get contents for
* @return all contents as a map from EClass to lists of EObjects
*/
public static Map<EClass, List<EObject>> getAllClassesAndObjects(EObject rootObject) {
// initialize the computation process
Map<EClass, List<EObject>> result = new LinkedHashMap<EClass, List<EObject>>();
TreeIterator<EObject> allContents = rootObject.eAllContents();
List<EObject> newList = new LinkedList<EObject>();
newList.add(rootObject);
result.put(rootObject.eClass(), newList);
// iterate over all direct and indirect contents
while(allContents.hasNext()) {
EObject eObject = allContents.next();
// did this EObject's EClass appear before?
if(result.containsKey(eObject.eClass())) {
result.get(eObject.eClass()).add(eObject);
} else {
newList = new LinkedList<EObject>();
newList.add(eObject);
result.put(eObject.eClass(), newList);
}
}
return result;
}
/**
* Returns all valid references for an EObject. This excludes container/containment references.
* A reference is valid if it is neither derived nor volatile and if it is changeable and
* either many-valued or not already set.
*
* @param eObject the EObject to get references for
* @param exceptionLog the current log of exceptions
* @param ignoreAndLog should exceptions be ignored and added to <code>exceptionLog</code>?
* @return all valid references as a list
*/
public static List<EReference> getValidReferences(EObject eObject,
Set<RuntimeException> exceptionLog, boolean ignoreAndLog) {
List<EReference> result = new LinkedList<EReference>();
for(EReference reference : eObject.eClass().getEAllReferences()) {
if(!reference.isContainer() && !reference.isContainment() && isValid(reference, eObject, exceptionLog, ignoreAndLog)
&& (reference.isMany() || !eObject.eIsSet(reference))) {
result.add(reference);
}
}
return result;
}
/**
* Returns whether an EStructuralFeature is valid for an EObject or not.
* A reference is valid, if it can be set or added to.
*
* @param feature the EStructuralFeature in question
* @param eObject the EObject to check the feature for
* @param exceptionLog the current log of exceptions
* @param ignoreAndLog should exceptions be ignored and added to <code>exceptionLog</code>?
* @return if the feature can be set or added to
*/
public static boolean isValid(EStructuralFeature feature, EObject eObject,
Set<RuntimeException> exceptionLog, boolean ignoreAndLog) {
boolean result = false;
try {
if(feature.isMany()) {
// has the maximum amount of referenced objects been reached?
Collection<?> referencedItems = (Collection<?>) eObject.eGet(feature);
if(feature.getUpperBound() >= 0 && referencedItems.size() >= feature.getUpperBound()) {
return false;
}
}
// can the feature be changed reflectively?
result = feature.isChangeable() && !feature.isVolatile() && !feature.isDerived();
} catch(RuntimeException e) {
handle(e, exceptionLog, ignoreAndLog);
}
return result;
}
/**
* Handles <code>exception</code>, meaning it is thrown if <code>ignoreAndLog</code>
* is <code>false</code>. Otherwise <code>exception</code> is ignored and added to
* <code>exceptionLog</code>.
*
* @param exception the exception to handle
* @param exceptionLog the current log of exceptions
* @param ignoreAndLog should exceptions be ignored and added to <code>exceptionLog</code>?
*/
private static void handle(RuntimeException exception, Set<RuntimeException> exceptionLog,
boolean ignoreAndLog) {
if(ignoreAndLog) {
exceptionLog.add(exception);
} else {
throw exception;
}
}
/**
* Sets a feature between <code>eObject</code> and <code>newValue</code>
* using a SetCommand. Exceptions are caught if <code>ignoreAndLog</code> is
* true, otherwise a RuntimeException might be thrown if the command fails.
*
* @param eObject the EObject for which <code>feature</code> shall be set
* @param feature the EStructuralFeature that shall be set
* @param newValue the Object that shall be set as a feature in <code>parentEObject</code>
* @param exceptionLog the current log of exceptions
* @param ignoreAndLog should exceptions be ignored and added to <code>exceptionLog</code>?
* @return <code>newValue</code> if the <code>SetCommand</code> was performed successful
* or <code>null</code> if it failed
* @see SetCommand
*/
public static EObject setPerCommand(EObject eObject, EStructuralFeature feature, Object newValue,
Set<RuntimeException> exceptionLog, boolean ignoreAndLog) {
EditingDomain domain = AdapterFactoryEditingDomain.getEditingDomainFor(eObject);
// no new value to set? -> unset the feature
if(newValue==null) {
newValue = SetCommand.UNSET_VALUE;
}
try {
new SetCommand(domain, eObject, feature, newValue).doExecute();
if(newValue instanceof EObject) {
return (EObject) newValue;
} else {
return null;
}
} catch(RuntimeException e){
handle(e, exceptionLog, ignoreAndLog);
return null;
}
}
/**
* Adds <code>newValue</code> to the many-valued feature of
* <code>eObject</code> using an AddCommand.
* Exceptions are caught if <code>ignoreAndLog</code> is
* true, otherwise a RuntimeException might be thrown if the command fails.
*
* @param eObject the EObject to which <code>newObject</code> shall be added
* @param feature the EStructuralFeature that <code>newObject</code> shall be added to
* @param newValue the Object that shall be added to <code>feature</code>
* @param exceptionLog the current log of exceptions
* @param ignoreAndLog should exceptions be ignored and added to <code>exceptionLog</code>?
* @return <code>newValue</code> if the <code>AddCommand</code> was performed successful
* or <code>null</code> if it failed
* @see AddCommand#AddCommand(EditingDomain, EObject, EStructuralFeature, Object)
*/
public static EObject addPerCommand(EObject eObject, EStructuralFeature feature, Object newValue,
Set<RuntimeException> exceptionLog, boolean ignoreAndLog) {
EditingDomain domain = AdapterFactoryEditingDomain.getEditingDomainFor(eObject);
try {
if(feature.isUnique() && ((Collection<?>) eObject.eGet(feature)).contains(newValue)) {
// unique feature already contains object -> nothing to do
return null;
}
new AddCommand(domain, eObject, feature, newValue).doExecute();
if(newValue instanceof EObject) {
return (EObject) newValue;
} else {
return null;
}
} catch(RuntimeException e){
handle(e, exceptionLog, ignoreAndLog);
return null;
}
}
/**
* Adds all <code>objects</code> to the many-valued feature of
* <code>eObject</code> using an AddCommand.
* Exceptions are caught if <code>ignoreAndLog</code> is
* true, otherwise a RuntimeException might be thrown if the command fails.
*
* @param eObject the EObject to which <code>objects</code> shall be added
* @param feature the EReference that <code>objects</code> shall be added to
* @param objects collection of objects that shall be added to <code>feature</code>
* @param exceptionLog the current log of exceptions
* @param ignoreAndLog should exceptions be ignored and added to <code>exceptionLog</code>?
*/
public static void addPerCommand(EObject eObject, EStructuralFeature feature, Collection<?> objects,
Set<RuntimeException> exceptionLog, boolean ignoreAndLog) {
EditingDomain domain = AdapterFactoryEditingDomain.getEditingDomainFor(eObject);
try {
for(Object object : objects) {
if(feature.isUnique() && ((Collection<?>) eObject.eGet(feature)).contains(object)) {
// object already exists in unique feature, don't add it again
objects.remove(object);
}
}
// no objects to add left
if(objects.isEmpty()) {
return;
}
new AddCommand(domain, eObject, feature, objects).doExecute();
} catch(RuntimeException e) {
handle(e, exceptionLog, ignoreAndLog);
}
}
/**
* Removes <code>objects</code> from a feature of <code>eObject</code>
* using a RemoveCommand. Exceptions are caught if <code>ignoreAndLog</code> is
* true, otherwise a RuntimeException might be thrown if the command fails.
*
* @param eObject the EObject to remove <code>objects</code> from
* @param feature the EStructuralFeature <code>objects</code> shall be removed from
* @param objects collection of Objects that shall be removed
* @param exceptionLog the current log of exceptions
* @param ignoreAndLog should exceptions be ignored and added to <code>exceptionLog</code>?
* @see RemoveCommand
*/
public static void removePerCommand(EObject eObject, EStructuralFeature feature, Collection<?> objects,
Set<RuntimeException> exceptionLog, boolean ignoreAndLog) {
EditingDomain domain = AdapterFactoryEditingDomain.getEditingDomainFor(eObject);
try {
new RemoveCommand(domain, eObject, feature, objects).doExecute();
} catch(RuntimeException e){
handle(e, exceptionLog, ignoreAndLog);
}
}
/**
* Retrieves all EClasses from <code>allEClasses</code> that can possibly be
* referenced by <code>reference</code> and returns them as a list.
*
* @param reference the EReference to get EClasses for
* @param allEClasses set of all possible EClasses
* @return list of all EClasses that can be referenced by <code>reference</code>
*/
public static Set<EClass> getReferenceClasses(EReference reference, Set<EClass> allEClasses) {
Set<EClass> result = new LinkedHashSet<EClass>();
EClass referenceType = reference.getEReferenceType();
// 'referenceType: EObject' allows all kinds of EObjects
if(referenceType.equals(EcorePackage.eINSTANCE.getEObject())) {
return allEClasses;
}
for(EClass eClass : allEClasses) {
// can eClass be referenced by reference
if(referenceType.equals(eClass) || referenceType.isSuperTypeOf(eClass)) {
result.add(eClass);
}
}
return result;
}
/**
* Sets or adds to a reference for an EObject with any generated instance
* of <code>referenceClass</code> using SetCommand/AddCommand. If the reference is
* not required, <code>random</code> decides whether the reference is set or how
* many EObjects are added to it.
*
* @param eObject the EObject to set the reference for
* @param referenceClass the EClass all referenced EObject shall be instances of
* @param reference the reference to set
* @param random the Random-object that randomizes EObjects and their amount
* @param exceptionLog the current log of exceptions
* @param ignoreAndLog should exceptions be ignored and added to <code>exceptionLog</code>?
* @param allEObjects all existing EObjects mapped to their EClass
*
* @see #addPerCommand(EObject, EStructuralFeature, Collection, Set, boolean)
* @see #addPerCommand(EObject, EStructuralFeature, Object, Set, boolean)
* @see #setPerCommand(EObject, EStructuralFeature, Object, Set, boolean)
*/
public static void setReference(EObject eObject, EClass referenceClass, EReference reference,
Random random, Set<RuntimeException> exceptionLog, boolean ignoreAndLog, Map<EClass, List<EObject>> allEObjects) {
List<EObject> possibleReferenceObjects = allEObjects.get(referenceClass);
Collections.shuffle(possibleReferenceObjects, random);
if(!possibleReferenceObjects.isEmpty()) {
int index = 0;
if(reference.isMany()) {
int numberOfReferences = computeFeatureAmount(reference, random);
for(int i = 0; i < numberOfReferences; i++) {
ModelGeneratorUtil.addPerCommand(eObject, reference, possibleReferenceObjects.get(index),
exceptionLog, ignoreAndLog);
// ensures every EObject is set at most once
if(++index==possibleReferenceObjects.size()) {
break;
}
}
//Change of referencing
// Delete reference.isRequired() in order to set 0..1 references
//} else if (reference.isRequired() || random.nextBoolean()){
} else if (random.nextBoolean()) {
ModelGeneratorUtil.setPerCommand(eObject, reference, possibleReferenceObjects.get(index),
exceptionLog, ignoreAndLog);
}
}
}
/**
* Sets all possible attributes of known types to random values using {@link IAttributeSetter}
* and SetCommands/AddCommands.
*
* @param eObject the EObject to set attributes for
* @param random the Random object used to determine the creation of attributes
* @param exceptionLog the current log of exceptions
* @param ignoreAndLog should exceptions be ignored and added to <code>exceptionLog</code>?
* @see IAttributeSetter
* @see AttributeHandler
* @see #addPerCommand(EObject, EStructuralFeature, Collection, Set, boolean)
* @see #addPerCommand(EObject, EStructuralFeature, Object, Set, boolean)
* @see #setPerCommand(EObject, EStructuralFeature, Object, Set, boolean)
*/
public static void setEObjectAttributes(EObject eObject, Random random,
Set<RuntimeException> exceptionLog, boolean ignoreAndLog) {
Map<EClassifier, IAttributeSetter<?>> attributeSetters = AttributeHandler.getAttributeSetters();
for(EAttribute attribute : eObject.eClass().getEAllAttributes()) {
EClassifier attributeType = attribute.getEAttributeType();
if(!isValid(attribute, eObject, exceptionLog, ignoreAndLog)) {
continue;
}
// the attribute setter used to create new attributes
IAttributeSetter<?> attributeSetter = null;
// is there a setter for this attribute?
if(attributeSetters.containsKey(attributeType)) {
attributeSetter = attributeSetters.get(attributeType);
} else if(isEnum(attributeType)) {
attributeSetter = AttributeHandler.getEEnumSetter((EEnum) attributeType);
}
// was there a fitting attribute setter?
if(attributeSetter != null) {
if (attribute.isMany()) {
int numberOfAttributes = computeFeatureAmount(attribute, random);
addPerCommand(eObject, attribute,
attributeSetter.createNewAttributes(numberOfAttributes), exceptionLog, ignoreAndLog);
} else {
setPerCommand(eObject, attribute,
attributeSetter.createNewAttribute(), exceptionLog, ignoreAndLog);
}
}
}
}
/**
* Returns whether <code>attributeType</code> is an instance of EEnum.
*
* @param attributeType the EClassifier in question
* @return is <code>attributeType</code> an instance of EEnum?
*/
private static boolean isEnum(EClassifier attributeType) {
return EcorePackage.eINSTANCE.getEEnum().isInstance(attributeType);
}
/**
* Computes the random amount of objects to add to a feature.
*
* @param feature the feature to compute the amount of objects for
* @param random the Random object used to obtain random values
* @return 1 if the feature is single valued,<br>
* a random value from 0 to 10 if the feature is many-valued and has no upper bound,<br>
* a random value between the feature's lower and upper bound otherwise
*/
private static int computeFeatureAmount(EStructuralFeature feature, Random random) {
if(!feature.isMany()) {
return 1;
}
if(feature.getUpperBound() < feature.getLowerBound()) {
return random.nextInt(10);
}
return feature.getLowerBound() + random.nextInt(feature.getUpperBound() - feature.getLowerBound() + 1);
}
/**
* Deletes an EObject, while exceptions are handled with the values specified.
*
* @param eObject the EObject to delete
* @param exceptionLog the current log of exceptions
* @param ignoreAndLog should exceptions be ignored and added to <code>exceptionLog</code>?
* @see EcoreUtil#delete(EObject)
*/
public static void delete(EObject eObject, Set<RuntimeException> exceptionLog, boolean ignoreAndLog) {
try {
EcoreUtil.delete(eObject,true);
} catch(RuntimeException e) {
handle(e, exceptionLog, ignoreAndLog);
}
}
}