/******************************************************************************* | |
* 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: | |
******************************************************************************/ | |
package org.eclipse.emf.emfstore.modelgenerator; | |
import java.util.Collections; | |
import java.util.LinkedHashMap; | |
import java.util.LinkedHashSet; | |
import java.util.LinkedList; | |
import java.util.List; | |
import java.util.Map; | |
import java.util.Random; | |
import java.util.Set; | |
import org.eclipse.emf.ecore.EClass; | |
import org.eclipse.emf.ecore.EObject; | |
import org.eclipse.emf.ecore.EReference; | |
import org.eclipse.emf.ecore.util.EcoreUtil; | |
import org.eclipse.emf.emfstore.modelgenerator.common.ModelGeneratorConfiguration; | |
import org.eclipse.emf.emfstore.modelgenerator.common.ModelGeneratorUtil; | |
import org.eclipse.emf.emfstore.modelgenerator.common.attribute.AttributeHandler; | |
/** | |
* Helper class that takes work from <code>ModelGenerator</code>. | |
* With the help of this class, <code>ModelGenerator</code> only | |
* has to be aware of its current configuration. All methods | |
* should be accessed in a static way and <code>init()</code> | |
* has to be called every time before starting a generation process. | |
* | |
* @see ModelGenerator | |
* @see #init(ModelGeneratorConfiguration) | |
*/ | |
final class ModelGeneratorHelper { | |
/** | |
* The configuration containing settings for the generation process. | |
* | |
* @see ModelGeneratorConfiguration | |
*/ | |
private static ModelGeneratorConfiguration configuration; | |
/** | |
* A set of RuntimeExceptions that occurred during the last generation process. | |
* | |
* @see #getExceptionLog() | |
*/ | |
private static Set<RuntimeException> exceptionLog; | |
/** | |
* All EClasses that shall be ignored during the generation process. | |
* These EClasses are specified in the configuration. | |
* | |
* @see #getEClassesToIgnore() | |
*/ | |
private static Set<EClass> eClassesToIgnore; | |
/** | |
* Map that maps every EClass to its corresponding list of EClasses, | |
* that should be created during the generation process. | |
* | |
* @see #getElementsToCreate(EReference) | |
*/ | |
private static Map<EReference, List<EClass>> eClassToElementsToCreate; | |
/** | |
* Map that saves the last used index for every EClass | |
* to prevent that every instance of that EClass contains | |
* the same EClasses as children. | |
* | |
* @see #getStartingIndex(EClass) | |
*/ | |
private static Map<EClass, Integer> eClassToLastUsedIndex; | |
/** | |
* Random-object to compute random values for creating EObjects | |
* and setting their attributes and references. | |
*/ | |
private static Random random; | |
/** | |
* Private constructor. | |
*/ | |
private ModelGeneratorHelper() { | |
// all methods should be accessed in a static way | |
} | |
/** | |
* Initializes the helper for the next generation process. | |
* All private fields get new values, old ones are discarded. | |
* This also initializes <code>AttributeHandler</code>. | |
* | |
* @param config the new configuration to use | |
* @see AttributeHandler#setRandom(Random) | |
*/ | |
protected static void init(ModelGeneratorConfiguration config) { | |
configuration = config; | |
random = new Random(config.getSeed()); | |
exceptionLog = new LinkedHashSet<RuntimeException>(); | |
eClassesToIgnore = getEClassesToIgnore(); | |
eClassToElementsToCreate = new LinkedHashMap<EReference, List<EClass>>(); | |
eClassToLastUsedIndex = new LinkedHashMap<EClass, Integer>(); | |
AttributeHandler.setRandom(random); | |
} | |
/** | |
* Computes the total amount of work to do (in units) during the generation process. | |
* This number is used for the Progress Bar and is twice the number of all EObjects | |
* that will be created (once for the creation, once for setting its references). | |
* | |
* @return the total amount of work in units as an integer | |
*/ | |
protected static int computeAmountOfWork() { | |
int result = 0; | |
// compute number of all elements | |
for(int i=1; i<=configuration.getDepth(); i++) { | |
result += (int) Math.pow(configuration.getWidth(), i); | |
} | |
// return double of the computed amount, once for creation, once for setting references | |
return result*2; | |
} | |
/** | |
* Creates a valid instance of <code>childClass</code> (includes setting attributes) and sets | |
* it as a child of <code>parentEObject</code> using AddCommand/SetCommand. | |
* | |
* @param parentEObject the EObject that shall contain the new instance of <code>childClass</code> | |
* @param childClass the EClass of the child that shall be contained in <code>parentEObject</code> | |
* @param reference the containment reference | |
* @return the instance of <code>childClass</code> that is contained in <code>parentEObject</code> | |
* or <code>null</code> if the operation failed | |
* | |
* @see ModelGeneratorUtil#addPerCommand(EObject, EStructuralFeature, Object, Set, boolean) | |
* @see ModelGeneratorUtil#setPerCommand(EObject, EStructuralFeature, Object, Set, boolean) | |
*/ | |
protected static EObject setContainment(EObject parentEObject, EClass childClass, EReference reference) { | |
// create and set attributes | |
EObject newEObject = EcoreUtil.create(childClass); | |
ModelGeneratorUtil.setEObjectAttributes(newEObject, random, exceptionLog, configuration.getIgnoreAndLog()); | |
// reference created EObject to the parent | |
if(reference.isMany()) { | |
return ModelGeneratorUtil.addPerCommand(parentEObject, reference, newEObject, | |
exceptionLog, configuration.getIgnoreAndLog()); | |
} | |
else { | |
return ModelGeneratorUtil.setPerCommand(parentEObject, reference, newEObject, | |
exceptionLog, configuration.getIgnoreAndLog()); | |
} | |
} | |
/** | |
* Validates <code>rootEObject</code> so the root is a valid EObject instance. | |
* If <code>rootEObject</code> is valid already, it is just returned. Otherwise, | |
* if <code>rootEObject</code> is an EClass (not abstract, no interface), it is | |
* instantiated and its attributes are set. If none of the above is true, null is returned. | |
* | |
* @param rootEObject the EObject to validate | |
* @return <code>null</code> if <code>rootEObject</code> is <code>null</code> or an | |
* abstract EClass or interface,<br> | |
* <code>rootEObject</code> if the root is already valid,<br> | |
* a new instance of <code>rootEObject</code> if it is an EClass | |
*/ | |
protected static EObject validateRoot(EObject rootEObject) { | |
if(rootEObject == null) { | |
throw new IllegalArgumentException("Root mustn't be null!"); | |
} | |
if(rootEObject instanceof EClass) { | |
EClass parentClass = (EClass) rootEObject; | |
if(parentClass.isInterface() || parentClass.isAbstract()) { | |
throw new IllegalArgumentException("Root mustn't be abstract or an interface!"); | |
} | |
rootEObject = EcoreUtil.create(parentClass); | |
ModelGeneratorUtil.setEObjectAttributes(rootEObject, random, exceptionLog, configuration.getIgnoreAndLog()); | |
} | |
return rootEObject; | |
} | |
/** | |
* Returns all EClasses that shall be excluded from the generation process, | |
* using the <code>eClassesToIgnore</code>-collection from the configuration. | |
* Every subclass of the specified EClasses is ignored as well. | |
* | |
* @return all EClasses that shall be ignored as a set | |
*/ | |
protected static Set<EClass> getEClassesToIgnore() { | |
Set<EClass> result = new LinkedHashSet<EClass>(); | |
for(EClass eClass : configuration.getEClassesToIgnore()) { | |
result.add(eClass); | |
result.addAll(ModelGeneratorUtil.getAllSubEClasses(eClass)); | |
} | |
return result; | |
} | |
/** | |
* Returns all EClasses that can possibly be created as children for <code>reference</code>. | |
* The result is shuffled before it is returned, so different seeds return different results. | |
* Only EClasses that are also contained in the configuration's <code>modelPackage</code> | |
* and not in the <code>eClassesToIgnore</code>-collection are retained. | |
* | |
* @param reference the EReference to compute the possible child EClasses for | |
* @return all possible child-EClasses as a list | |
* @see ModelGeneratorUtil#getAllEContainments(EReference) | |
* @see ModelGeneratorUtil#getAllEClasses(EPackage) | |
* @see #eClassesToIgnore | |
*/ | |
protected static List<EClass> getElementsToCreate(EReference reference) { | |
if(eClassToElementsToCreate.containsKey(reference)) { | |
return eClassToElementsToCreate.get(reference); | |
} else { | |
List<EClass> result = new LinkedList<EClass>(ModelGeneratorUtil.getAllEContainments(reference)); | |
List<EClass> modelElementEClasses = ModelGeneratorUtil.getAllEClasses(configuration.getModelPackage()); | |
// only retain EClasses that are not explicitly excluded and also appear in the specified EPackage | |
result.retainAll(modelElementEClasses); | |
result.removeAll(eClassesToIgnore); | |
Collections.shuffle(result, random); | |
// save the result for upcoming method-calls | |
eClassToElementsToCreate.put(reference, result); | |
return result; | |
} | |
} | |
/** | |
* Stores the last used index of <code>eClass</code> in the | |
* corresponding map for later use. | |
* | |
* @param eClass the EClass to store the index for | |
* @param index the latest used index for that EClass | |
* @see #getStartingIndex(EClass) | |
*/ | |
protected static void updateIndex(EClass eClass, int index) { | |
eClassToLastUsedIndex.put(eClass, index); | |
} | |
/** | |
* Returns an index that hasn't been used before, if existent. | |
* This prevents that an EClass always contains the same EClasses as children, | |
* where <code>eClass</code> is the parentEObject's EClass. | |
* | |
* @param eClass the EClass to get the index for | |
* @return the last used index or 0 if this EClass hasn't been instantiated before | |
*/ | |
protected static int getStartingIndex(EClass eClass) { | |
if(eClassToLastUsedIndex.containsKey(eClass)) { | |
return eClassToLastUsedIndex.get(eClass); | |
} else { | |
return 0; | |
} | |
} | |
/** | |
* Gets the next valid EClass from a list of EClasses. A valid EClass is an | |
* EClass that is neither abstract nor an interface and can therefore be | |
* instantiated. | |
* | |
* @param elementsToCreate the list of usable EClasses | |
* @param index the next index to use | |
* @return the next valid EClass or <code>null</code> if there is none | |
*/ | |
protected static EClass getNextClassToCreate(List<EClass> elementsToCreate, int index) { | |
if(elementsToCreate.isEmpty()) { | |
return null; | |
} | |
index %= elementsToCreate.size(); | |
EClass result = elementsToCreate.get(index); | |
// repeat until result can be instantiated | |
while(!ModelGeneratorUtil.canHaveInstance(result)) { | |
// current result can't be instantiated -> remove it | |
elementsToCreate.remove(result); | |
if(elementsToCreate.isEmpty()) { | |
return null; | |
} | |
// index might be out of bounds now | |
index %= elementsToCreate.size(); | |
result = elementsToCreate.get(index); | |
} | |
return result; | |
} | |
/** | |
* Returns random valid reference if there is any. | |
* | |
* @param eObject the EObject belonging to the EReference | |
* @param possibleReferences all possible references as a list | |
* @return a random valid EReference or <code>null</code> if there is none | |
*/ | |
protected static EReference getRandomReference(EObject eObject, List<EReference> possibleReferences) { | |
if(possibleReferences.isEmpty()) { | |
return null; | |
} | |
Collections.shuffle(possibleReferences, random); | |
EReference result = possibleReferences.get(0); | |
// repeat until result is valid | |
while(!isValid(eObject, result)) { | |
// current result isn't valid -> remove it | |
possibleReferences.remove(result); | |
if(possibleReferences.isEmpty()) { | |
return null; | |
} | |
result = possibleReferences.get(0); | |
} | |
return result; | |
} | |
/** | |
* Sets a reference, if it is valid, | |
* using {@link ModelGeneratorUtil#setReference}. | |
* | |
* @param eObject the EObject to set the reference for | |
* @param referenceClass the EClass of EObjects that shall be referenced | |
* @param reference the EReference that shall be set | |
* @param allEObjects all possible EObjects that can be referenced | |
* @see ModelGeneratorUtil#setReference(EObject, EClass, EReference, Random, Set, boolean, Map) | |
*/ | |
protected static void setReference(EObject eObject, EClass referenceClass, EReference reference, | |
Map<EClass, List<EObject>> allEObjects) { | |
if(!ModelGeneratorUtil.isValid(reference, eObject, exceptionLog, configuration.getIgnoreAndLog())) { | |
return; | |
} | |
ModelGeneratorUtil.setReference(eObject, referenceClass, reference, random, | |
exceptionLog, configuration.getIgnoreAndLog(), allEObjects); | |
} | |
/** | |
* Returns the Exception-Log of the <code>ModelGenerator</code>. | |
* The log is cleared after every {@link #init(ModelGeneratorConfiguration)}-call, | |
* i.e. before every generation process. | |
* The log will be empty if no RuntimeException occurred or <code>ignoreAndLog</code> | |
* was set to <code>false</code> in the last configuration used. | |
* | |
* @return a set of RuntimeExceptions that occurred during the last generation process | |
*/ | |
protected static Set<RuntimeException> getExceptionLog() { | |
return exceptionLog; | |
} | |
/** | |
* Returns all valid non-containment references for an EObject | |
* using {@link ModelGeneratorUtil#getValidReferences}. | |
* | |
* @param eObject the EObject to retrieve valid EReferences for | |
* @return all valid non-containment references as a list | |
*/ | |
protected static List<EReference> getValidReferences(EObject eObject) { | |
return ModelGeneratorUtil.getValidReferences(eObject, exceptionLog, configuration.getIgnoreAndLog()); | |
} | |
/** | |
* Returns whether an EReference is valid for a given EObject or not. | |
* This method uses {@link ModelGeneratorUtil#isValid}. | |
* | |
* @param eObject the EObject <code>reference</code> belongs to | |
* @param reference the EReference in question | |
* @return whether <code>reference</code> is valid or not | |
*/ | |
protected static boolean isValid(EObject eObject, EReference reference) { | |
boolean result = reference.isMany() || eObject.eIsSet(reference); | |
return result && ModelGeneratorUtil.isValid(reference, eObject, exceptionLog, configuration.getIgnoreAndLog()); | |
} | |
} |