/*******************************************************************************
* Copyright (c) 2011-2014 EclipseSource Muenchen GmbH and others.
*
* 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:
* Philip Langer - initial API and implementation
******************************************************************************/
package org.eclipse.emf.emfstore.internal.modelmutator.mutation;
import static com.google.common.base.Predicates.alwaysTrue;
import static com.google.common.base.Predicates.and;
import static com.google.common.collect.Iterables.filter;
import static com.google.common.collect.Iterables.get;
import static com.google.common.collect.Iterables.size;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Random;
import java.util.Set;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.emfstore.modelmutator.ESModelMutatorUtil;
import org.eclipse.emf.emfstore.modelmutator.ESMutationException;
import com.google.common.base.Predicate;
import com.google.common.collect.Lists;
/**
* A selector of a mutation target.
* <p>
* A mutation target is an {@link EObject} and a {@link EStructuralFeature} to be mutated. This selector can be
* configured with predicates that are respected during the selection and can also be partly pre-filled with, for
* instance, an {@link EStructuralFeature}, and the selector will complete the selection based on the configuration or
* pre-filled data.
* </p>
*
* @author Philip Langer
*/
public class MutationTargetSelector {
private final ESModelMutatorUtil util;
private final Collection<EClass> excludedEClasses = new HashSet<EClass>();
private final Collection<EStructuralFeature> excludedFeatures = new HashSet<EStructuralFeature>();
private final Collection<EObject> excludedObjects = new HashSet<EObject>();
private final Set<Predicate<? super EStructuralFeature>> targetFeaturePredicates = new HashSet<Predicate<? super EStructuralFeature>>();
private final Set<Predicate<? super EObject>> targetObjectPredicates = new HashSet<Predicate<? super EObject>>();
private final Set<Predicate<? super Object>> originalFeatureValuePredicates = new HashSet<Predicate<? super Object>>();
private EObject targetObject;
private EStructuralFeature targetFeature;
/**
* Creates a new mutation target selector with the given {@code util}.
*
* @param util The model mutator util to be used.
*/
public MutationTargetSelector(ESModelMutatorUtil util) {
this.util = util;
addExcludedEStructuralFeaturesAndEClassesFromConfig();
}
private void addExcludedEStructuralFeaturesAndEClassesFromConfig() {
excludedFeatures.addAll(util.getModelMutatorConfiguration().geteStructuralFeaturesToIgnore());
excludedEClasses.addAll(util.getModelMutatorConfiguration().geteClassesToIgnore());
}
/**
* Creates a new mutation target selector with the same values as the given {@code selector} and the given
* {@code util}.
*
* @param util The model mutator util to be used.
* @param selector The selector to copy from.
*/
public MutationTargetSelector(ESModelMutatorUtil util, MutationTargetSelector selector) {
this.util = util;
setupFromOtherSelector(selector);
}
private void setupFromOtherSelector(MutationTargetSelector selector) {
setupExcludedEClasses(selector);
setupExcludedObjects(selector);
setupExcludedFeatures(selector);
setTargetObject(selector.getTargetObject());
setTargetFeature(selector.getTargetFeature());
targetFeaturePredicates.addAll(selector.getTargetFeaturePredicates());
targetObjectPredicates.addAll(selector.getTargetObjectPredicates());
originalFeatureValuePredicates.addAll(selector.getOriginalFeatureValuePredicates());
}
private void setupExcludedEClasses(MutationTargetSelector selector) {
getExcludedEClasses().clear();
getExcludedEClasses().addAll(selector.getExcludedEClasses());
}
private void setupExcludedObjects(MutationTargetSelector selector) {
getExcludedObjects().clear();
getExcludedObjects().addAll(selector.getExcludedObjects());
}
private void setupExcludedFeatures(MutationTargetSelector selector) {
getExcludedFeatures().clear();
getExcludedFeatures().addAll(selector.getExcludedFeatures());
}
/**
* Returns the list of EClasses to be excluded from the selection.
* <p>
* That is, that objects are excluded if they are instances of one of the excluded EClasses.
* </p>
*
* @return The list of EClasses to be excluded from the selection.
*/
protected Collection<EClass> getExcludedEClasses() {
return excludedEClasses;
}
/**
* Returns the list of features to be excluded from the selection.
*
* @return The list of features to be excluded from the selection.
*/
protected Collection<EStructuralFeature> getExcludedFeatures() {
return excludedFeatures;
}
/**
* Returns the list of EObjects to be excluded from the selection.
*
* @return The list of EObjects to be excluded from the selection.
*/
protected Collection<EObject> getExcludedObjects() {
return excludedObjects;
}
/**
* Returns the set or selected target object.
*
* @return The set or selected target object.
*/
protected EObject getTargetObject() {
return targetObject;
}
/**
* Sets the target object to be used.
*
* @param targetObject The target object to be used.
*/
protected void setTargetObject(EObject targetObject) {
this.targetObject = targetObject;
}
/**
* Returns the set or selected target feature.
*
* @return The set or selected target feature.
*/
protected EStructuralFeature getTargetFeature() {
return targetFeature;
}
/**
* Sets the target feature to be used.
*
* @param targetFeature The target feature to be used.
*/
protected void setTargetFeature(EStructuralFeature targetFeature) {
this.targetFeature = targetFeature;
}
/**
* Returns the modifiable list of predicates constraining the target feature.
*
* @return The list of predicates constraining the target feature.
*/
protected Set<Predicate<? super EStructuralFeature>> getTargetFeaturePredicates() {
return targetFeaturePredicates;
}
private Predicate<? super EStructuralFeature> getTargetFeaturePredicatesConjunction() {
return and(getTargetFeaturePredicates());
}
/**
* Returns the modifiable list of predicates constraining the target object.
*
* @return The list of predicates constraining the target object.
*/
protected Set<Predicate<? super EObject>> getTargetObjectPredicates() {
return targetObjectPredicates;
}
private Predicate<? super EObject> getTargetObjectPredicatesConjunction() {
return and(getTargetObjectPredicates());
}
/**
* Returns the modifiable list of predicates constraining the original value of the target object at the target
* feature.
*
* @return The list of predicates constraining the original value of the target object at the target feature.
*/
protected Set<Predicate<? super Object>> getOriginalFeatureValuePredicates() {
return originalFeatureValuePredicates;
}
private Predicate<? super Object> getOriginalFeatureValuePredicatesConjunction() {
return and(getOriginalFeatureValuePredicates());
}
/**
* Performs the selection according to the configured predicates and optionally pre-filled data.
*
* @throws ESMutationException If no valid target object or feature could be found.
*/
protected void doSelection() throws ESMutationException {
final List<EStructuralFeature> features = getShuffledFeaturesToSelect();
for (final EStructuralFeature feature : features) {
for (final EObject eObject : getShuffledTargetObjectsToSelect(feature)) {
if (isValid(feature, eObject)) {
setTargetFeature(feature);
setTargetObject(eObject);
return;
}
}
}
throw new ESMutationException("No valid target found."); //$NON-NLS-1$
}
private List<EStructuralFeature> getShuffledFeaturesToSelect() {
if (hasTargetFeature()) {
return Lists.newArrayList(getTargetFeature());
} else if (hasTargetObject()) {
return getShuffledAvailableFeaturesFromTargetObject();
} else {
return getShuffledAvailableFeatures();
}
}
private boolean hasTargetFeature() {
return targetFeature != null;
}
private boolean hasTargetObject() {
return targetObject != null;
}
private List<EStructuralFeature> getShuffledAvailableFeaturesFromTargetObject() {
final List<EStructuralFeature> availableFeatures = new ArrayList<EStructuralFeature>();
final EClass eClassOfTargetObject = targetObject.eClass();
availableFeatures.addAll(eClassOfTargetObject.getEAllStructuralFeatures());
excludeAndShuffleTargetFeatures(availableFeatures);
return availableFeatures;
}
private void excludeAndShuffleTargetFeatures(final List<EStructuralFeature> features) {
features.removeAll(excludedFeatures);
filterTargetFeaturePredicates(features);
Collections.shuffle(features, getRandom());
}
private void filterTargetFeaturePredicates(final List<EStructuralFeature> features) {
for (final EStructuralFeature feature : Lists.newArrayList(features)) {
if (!getTargetFeaturePredicatesConjunction().apply(feature)) {
features.remove(feature);
}
}
}
private List<EStructuralFeature> getShuffledAvailableFeatures() {
final List<EStructuralFeature> features = getAvailableFeatures();
excludeAndShuffleTargetFeatures(features);
return features;
}
private List<EStructuralFeature> getAvailableFeatures() {
return Lists.newArrayList(util
.getAvailableFeatures(getTargetFeaturePredicatesConjunction()));
}
private List<EObject> getShuffledTargetObjectsToSelect(EStructuralFeature feature) {
if (hasTargetObject()) {
return Lists.newArrayList(targetObject);
}
return getShuffledEObjectsForAvailableFeature(feature);
}
private List<EObject> getShuffledEObjectsForAvailableFeature(EStructuralFeature feature) {
final ArrayList<EObject> eObjects = getEObjectsForAvailableFeature(feature);
excludeAndShuffleTargetObjects(eObjects);
return eObjects;
}
private void excludeAndShuffleTargetObjects(final List<EObject> eObjects) {
eObjects.removeAll(excludedObjects);
filterTargetObjectPredicates(eObjects);
Collections.shuffle(eObjects);
}
private void filterTargetObjectPredicates(final List<EObject> eObjects) {
for (final EObject eObject : Lists.newArrayList(eObjects)) {
if (!getTargetObjectPredicatesConjunction().apply(eObject)) {
eObjects.remove(eObject);
}
}
}
private ArrayList<EObject> getEObjectsForAvailableFeature(EStructuralFeature feature) {
final Predicate<? super EObject> targetObjectPredicate = getTargetObjectPredicatesConjunction();
return Lists.newArrayList(util.getOfferingEObjectsForAvailableFeature(feature, targetObjectPredicate));
}
private Random getRandom() {
return util.getModelMutatorConfiguration().getRandom();
}
/**
* Specifies whether the current selection is valid with respect to the configured predicates and excluded EClasses,
* EObjects, and features.
*
* @return <code>true</code> if the selection is valid, <code>false</code> otherwise.
*/
protected boolean isValid() {
return isValid(targetFeature, targetObject);
}
private boolean isValid(final EStructuralFeature feature, final EObject eObject) {
if (feature == null || eObject == null) {
return false;
}
final EClass eClass = eObject.eClass();
final EList<EStructuralFeature> featuresOfEClass = eClass.getEAllStructuralFeatures();
return !isExcluded(feature, eObject)
&& featuresOfEClass.contains(feature)
&& fulfillsTargetFeaturePredicate(feature)
&& fulfillsTargetObjectPredicate(eObject)
&& fulfullsOriginalFeatureValuePredicate(feature, eObject);
}
private boolean isExcluded(EStructuralFeature feature, EObject eObject) {
final EClass eClass = eObject.eClass();
return excludedFeatures.contains(feature)
|| excludedEClasses.contains(feature.getEType())
|| excludedEClasses.contains(eClass)
|| excludedObjects.contains(eObject);
}
private boolean fulfillsTargetFeaturePredicate(EStructuralFeature feature) {
return getTargetFeaturePredicatesConjunction().apply(feature);
}
private boolean fulfillsTargetObjectPredicate(EObject eObject) {
return getTargetObjectPredicatesConjunction().apply(eObject);
}
private boolean fulfullsOriginalFeatureValuePredicate(EStructuralFeature feature, EObject eObject) {
final Object originalValue = eObject.eGet(feature);
return getOriginalFeatureValuePredicatesConjunction().apply(originalValue);
}
/**
* Returns a random value from the currently selected target object at the selected target feature.
*
* @return A random value from the target object at the target feature.
*/
protected Object selectRandomValueFromTargetObject() {
return selectRandomContainedValue(alwaysTrue());
}
/**
* Returns a random value that conforms to the given {@code predicates} from the currently selected target object at
* the selected target feature.
*
* @param predicate The predicate to be respected for selecting the random value.
* @return A random value respecting the {@code predicates} from the target object at the target feature.
*/
protected Object selectRandomContainedValue(Predicate<? super Object> predicate) {
if (!isValid()) {
throw new IllegalStateException("There is no valid selection to get value for."); //$NON-NLS-1$
} else if (getTargetFeature().isMany()) {
return selectRandomValueFromTargetObjectWithMultiValuedFeature(predicate);
} else {
return selectRandomValueFromTargetObjectWithSingleValuedFeature(predicate);
}
}
private Object selectRandomValueFromTargetObjectWithMultiValuedFeature(Predicate<? super Object> predicate) {
@SuppressWarnings("unchecked")
final List<Object> values = (List<Object>) getTargetValue();
final Iterable<Object> filteredValues = filter(values, predicate);
final int randomIndex = getRandomIndexFromValueRange(filteredValues);
final Object randomObject = get(filteredValues, randomIndex);
return randomObject;
}
private Object getTargetValue() {
return getTargetObject().eGet(getTargetFeature());
}
private Object selectRandomValueFromTargetObjectWithSingleValuedFeature(Predicate<? super Object> predicate) {
final Object targetValue = getTargetValue();
if (predicate.apply(targetValue)) {
return targetValue;
}
return null;
}
/**
* Returns a random index within the range of currently contained values in the target object at the target feature.
*
* @return A random index within the range of the current value of the target object at the target feature.
*/
protected int getRandomIndexFromTargetObjectAndFeatureValueRange() {
@SuppressWarnings("unchecked")
final Collection<Object> values = (Collection<Object>) getTargetValue();
return getRandomIndexFromValueRange(values);
}
private int getRandomIndexFromValueRange(Iterable<Object> values) {
final int randomIndex;
final int numberOfCurrentValues = size(values);
if (numberOfCurrentValues > 0) {
randomIndex = getRandom().nextInt(numberOfCurrentValues);
} else {
randomIndex = 0;
}
return randomIndex;
}
}