blob: f7c570aa686730a710e8607da2c9b9c643966994 [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:
******************************************************************************/
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());
}
}