/** * <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); } } }