package org.magenta; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Random; import java.util.Set; import java.util.concurrent.Callable; import org.magenta.core.DataDomainAggregator; import org.magenta.core.DataSetRelationLoader; import org.magenta.core.EmptyDataSet; import org.magenta.core.GeneratedDataSet; import org.magenta.core.GeneratorImpl; import org.magenta.core.GenericDataSet; import org.magenta.core.PersistentDataSet; import org.magenta.core.RestrictionHelper; import org.magenta.core.injection.DataSetFieldHandler; import org.magenta.core.injection.DataSpecificationFieldHandler; import org.magenta.core.injection.FieldInjectionChainProcessor; import org.magenta.core.injection.FieldInjectionHandler; import org.magenta.core.injection.FluentRandomFieldHandler; import org.magenta.core.injection.HiearchicalFieldsFinder; import org.magenta.core.injection.Injector; import org.magenta.core.injection.ThreadLocalDataDomainSupplier; import org.magenta.events.DataSetRegistered; import org.magenta.events.DataSetRemoved; import org.magenta.generators.ContextualGenerationStrategyDecorator; import org.magenta.generators.DataSetAggregationStrategy; import org.magenta.generators.GeneratorAnnotationHelper; import org.magenta.generators.IterableSupplierGenerationStrategyAdapter; import org.magenta.generators.SupplierGenerationStrategyAdapter; import org.magenta.generators.TransformedStrategy; import org.magenta.random.FluentRandom; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.base.Function; import com.google.common.base.Functions; import com.google.common.base.Preconditions; import com.google.common.base.Predicate; import com.google.common.base.Predicates; import com.google.common.base.Supplier; import com.google.common.base.Suppliers; import com.google.common.collect.FluentIterable; import com.google.common.collect.Iterables; import com.google.common.collect.Lists; import com.google.common.eventbus.EventBus; import com.google.common.eventbus.Subscribe; /** * This class allow the management of {@link DataSet}. Use this class to add, * get and remove {@link DataSet}s. * * @author normand * */ public class FixtureFactory<S extends DataSpecification> implements Fixture<S> { private static final Logger LOG = LoggerFactory.getLogger(FixtureFactory.class); private final Fixture<S> parent; private boolean persistent; private DataStoreProvider dataStoreProvider; private final FluentRandom randomizer; private final S specification; private final Map<DataKey<?>, DataSet<?>> dataSetMap; private final Map<DataKey<?>, GenerationStrategy<?, ? extends DataSpecification>> generatorMap; private final Map<DataKey<?>, Integer> numberOfItemsMap; private final List<Processor<S>> processors; private final Injector injector; private final ThreadLocalDataDomainSupplier<S> currentFixtureSupplier; private EventBus eventBus; private int version = 1; private final String name; // ------------------------------------------------------------------------------------------------------------------------------------------------- // // Constructor // // ------------------------------------------------------------------------------------------------------------------------------------------------- /** * Full constructor. * * @param parent * the parent to fallabck to when a requested dataset is not found. * @param datastoreProvider * the datasource provider for persitent DataSet. */ private FixtureFactory(String name, Fixture<S> parent, S specification, boolean persistent, FluentRandom randomizer, DataStoreProvider datastoreProvider, ThreadLocalDataDomainSupplier<S> fixtureSupplier) { this.name = name; this.parent = parent; this.specification = specification; this.randomizer = randomizer; this.dataStoreProvider = datastoreProvider; this.persistent = persistent; this.currentFixtureSupplier = fixtureSupplier; this.eventBus = new EventBus(name); this.dataSetMap = new HashMap<DataKey<?>, DataSet<?>>(); this.generatorMap = new HashMap<DataKey<?>, GenerationStrategy<?, ?>>(); this.numberOfItemsMap = new HashMap<DataKey<?>, Integer>(); this.processors = Lists.newArrayList(); final List<FieldInjectionHandler<S>> handlers = Lists.newArrayList(); handlers.add(new DataSetFieldHandler()); handlers.add(new DataSpecificationFieldHandler()); handlers.add(new FluentRandomFieldHandler()); this.injector = new FieldInjectionChainProcessor<S>(currentFixtureSupplier, HiearchicalFieldsFinder.SINGLETON, handlers); } // ------------------------------------------------------------------------------------------------------------------------------------------------- // // Factory Methods // // ------------------------------------------------------------------------------------------------------------------------------------------------- /** * Create a new root {@link FixtureFactory}. * * @param name * the name of this data domain * @param specification * the {@link DataSpecification} of this data domain * @param randomizer * the randomizer to use * @param <S> * the type of {@link DataSpecification} * @return a new {@link FixtureFactory} */ public static <S extends DataSpecification> FixtureFactory<S> newRoot(String name, S specification, FluentRandom randomizer) { FixtureFactory<S> fixtureBuilder = new FixtureFactory<S>(name, null, specification, false, randomizer, null, new ThreadLocalDataDomainSupplier()); fixtureBuilder.getEventBus().register(new DataSetRelationLoader()); return fixtureBuilder; } /** * Open a new scope using this {@link FixtureFactory} as parent. The new * scope is itself a {@link FixtureFactory} where new {@link DataSet} may * be added that won't be visible for this {@link FixtureFactory}. * * @param name * the name of the new node. * @return a new {@link FixtureFactory} child of this one. */ public FixtureFactory<S> newNode(String name) { FixtureFactory<S> child = new FixtureFactory<S>(name, this, this.getSpecification(), this.persistent, this.randomizer, this.dataStoreProvider, this.currentFixtureSupplier/*,this.generationCallStack*/); child.getEventBus().register(this); return child; } /** * Open a new scope using this {@link FixtureFactory} as parent. The new * scope is itself a {@link FixtureFactory} where new {@link DataSet} may * be added that won't be visible for this {@link FixtureFactory}. * * @param name * the new of the new node * @param dataspecification * the {@link DataSpecification} of the new node. * @param <X> * the type of the {@link DataSpecification} inheriting from <S> * @return a new DataSetManager child of this one. */ @SuppressWarnings("unchecked") public <X extends S> FixtureFactory<X> newNode(String name, X dataspecification) { // Cast is safe here, because the parent will be in fact used with its child // DataSpecification which extends the parent data specification FixtureFactory<X> child = new FixtureFactory<X>(name, (FixtureFactory<X>) this, dataspecification, this.persistent, this.randomizer, this.dataStoreProvider, (ThreadLocalDataDomainSupplier<X>)this.currentFixtureSupplier/*, this.generationCallStack*/); child.getEventBus().register(this); return child; } /** * Open a new scope delegating to <code>dataDomain</code> and using this * {@link FixtureFactory} as parent. The new scope is itself a * {@link FixtureFactory} where new {@link DataSet} may be added that won't * be visible for this {@link FixtureFactory}. * * @param dataDomain * the delegated data domain * @return a new DataSetManager child of this one. */ public FixtureFactory<S> newNode(Fixture<? super S> dataDomain) { DataDomainAggregator<S> aggregation = new DataDomainAggregator<S>(dataDomain, this); FixtureFactory<S> child = new FixtureFactory<S>("child of " + this.getName(), aggregation, this.getSpecification(), this.persistent, randomizer, this.dataStoreProvider, this.currentFixtureSupplier/*, this.generationCallStack*/); child.getEventBus().register(this); return child; } public FixtureFactory<S> setDataStoreProvider(DataStoreProvider provider, boolean enablePersistence) { this.dataStoreProvider = provider; this.persistent = enablePersistence; return this; } // ------------------------------------------------------------------------------------------------------------------------------------------------- // // Public Methods // // ------------------------------------------------------------------------------------------------------------------------------------------------- @Override public S getSpecification() { return specification; } @Override public String getName() { return name; } @Override public int getVersion(){ return version; } /* * (non-Javadoc) * * @see com.expedia.gps.fixtures.DataSets#getReferenceFor(java.lang.Class) */ /** * Return the actual parent of this DataSetMapManager. * * @return the parent or null if this is the root parent. */ @Override public Fixture<S> getParent() { return parent; } public boolean isTransient() { return !persistent; } public boolean isPersistent() { return persistent; } public FixtureFactory<S> persistent(boolean persistent){ this.persistent = persistent; return this; } /** * return the {@link DataStoreProvider}. * * @return the {@link DataStoreProvider} */ public DataStoreProvider getDataStoreProvider() { return dataStoreProvider; } /** * * @return the {@link Random} */ @Override public FluentRandom getRandomizer() { return this.randomizer; } /** * <p> * Convenient method used to replace existing data set. By "restricting", we * mean replacing an existing dataset by a fixed one having only one or two * elements. * </p> * <p> * The purpose of doing this is to restrain the range of a certain type of * data so every other dataset depending on it for their generation are forced * to use your sample. * </p> * <p> * Let say we have a dataset of "City" containing dozens of cities. This * dataset is used by a generation strategy to generate "Monument". Each * "Monument" generated within one of the available cities. If we want to * generate "Monument" for the city of Paris only, then we need to "restrict" * the dataset of Cities to only one element being "Paris". * </p> * * Here is a sample of code: * * <pre> * FixtureFactory tourismDomain = TourismDomain.createDomain(); * City paris = CityBuilder.build("Paris"); * Monument monument = tourismDomain.restrictTo(paris).dataset(Monument.class).any(); * * Assert.assertEquals(paris, monument.getCity()); * * </pre> * * @param objects * an array of object from which the dataset will be created * @return this FixturesManager */ public FixtureFactory<S> restrictTo(Object... objects) { FixtureFactory<S> child = newNode("restricted " + this.getName()); RestrictionHelper.applyRestrictions(child, objects); return child; } /** * Return a new {@link FixtureFactory} node which return fixed values for * every {@link DataSet} identified by <code>classes</code>. Normally, * generated data set are regenerated for new node but this method "fixes" the * generated values as they are currently found in this * {@link FixtureFactory}. * * @param classes * the dataset default identifiers. * @return a new {@link FixtureFactory} node. */ public FixtureFactory<S> fix(Class<?>... classes) { DataKey<?>[] keys = FluentIterable.from(Arrays.asList(classes)).transform(DataKey.classToKey()).toArray(DataKey.class); return fix(keys); } /** * Return a new {@link FixtureFactory} node which return fixed values for * every {@link DataSet} identified by <code>keys</code>. Normally, generated * data set are regenerated for new node but this method "fixes" the generated * values as they are currently found in this {@link FixtureFactory}. * * @param keys * the data set identifiers. * @return a new {@link FixtureFactory} node. */ @SuppressWarnings("unchecked") public FixtureFactory<S> fix(DataKey<?>... keys) { FixtureFactory<S> child = newNode("frozen " + this.getName()); for (DataKey<?> key : keys) { @SuppressWarnings("rawtypes") DataSet ds = this.dataset(key); if (ds.isGenerated()) { // this trick replace all generated dataset by fixed one which will // prevent regeneration of data child.newDataSet(key).composedOf(ds); } } return child; } // ------------------------------------------------------------------------------------------------------------------------------------------------- // // Builder methods // // ------------------------------------------------------------------------------------------------------------------------------------------------- /** * Return a builder allowing you to create a new {@link DataSet} for the * <code>key</code> in this {@link FixtureFactory}. * * @param key * the qualifier of the {@link DataSet} to create. * @param <D> * the type of data * @return a new builder */ public <D> DataSetBuilder<D, D> newDataSet(DataKey<D> key) { return new DataSetBuilder<D, D>(key, Predicates.alwaysTrue(), Functions.<D> identity()); } /** * Return a builder allowing you to create a new {@link DataSet} for the * <code>clazz</code> in this {@link FixtureFactory}. * * @param clazz * the type of data the data set will contain, will be use to create * a default {@link DataKey}. * @param <D> * the type of data * @return a new builder */ public <D> DataSetBuilder<D, D> newDataSet(Class<D> clazz) { DataKey<D> qualifier = DataKey.makeDefault(clazz); return newDataSet(qualifier); } /** * Add a new empty {@link DataSet} in this {@link FixtureFactory} and * return it. * * @param clazz * the type of data the data set will contain, will be use to create * a default {@link DataKey}. * @param <D> * the type of data * @return a new empty data set. */ public <D> DataSet<D> newEmptyDataSet(Class<D> clazz) { DataKey<D> qualifier = DataKey.makeDefault(clazz); return newEmptyDataSet(qualifier); } /** * Add a new empty {@link DataSet} in this {@link FixtureFactory} and * return it. * * @param key * the data set key. * @param <D> * the type of data * @return a new empty data set. */ public <D> DataSet<D> newEmptyDataSet(DataKey<D> key) { DataSet<D> d = EmptyDataSet.ofType(key.getType()); this.put(key, d); return d; } /** * Return a builder allowing you to create a new {@link Generator} for the * <code>key</code> in this {@link FixtureFactory}. * * @param key * the key of the {@link DataSet} to create. * @param <D> * the type of data * @return a new builder */ public <D> GeneratorBuilder<D, D> newGenerator(DataKey<D> key) { return new GeneratorBuilder<D, D>(key, Predicates.alwaysTrue(), Functions.<D> identity()); } /** * Return a builder allowing you to create a new {@link Generator} for this * data space. * * @param clazz * the type of data the dataset will contain, will be use to create a * default {@link DataKey}. * @param <D> * the type of data * @return a new builder */ public <D> GeneratorBuilder<D, D> newGenerator(Class<D> clazz) { DataKey<D> qualifier = DataKey.makeDefault(clazz); return newGenerator(qualifier); } /** * Add new {@link Processor} to this {@link FixtureFactory}. * * @param processor the processor to add. */ @SuppressWarnings("unchecked") public void newProcessor(Processor<? super S> processor) { this.processors.add((Processor<S>) processor); } //------------------------------------------------------------------------------------------------------------------------------------------------- // // Accessor methods // // ------------------------------------------------------------------------------------------------------------------------------------------------- /* * (non-Javadoc) * * @see com.expedia.gps.fixtures.DataSets#of(java.lang.Class) */ @Override public <D> DataSet<D> dataset(Class<D> clazz) { Preconditions.checkNotNull(clazz); DataKey<D> ref = getKeyFor(clazz); return dataset(ref); } /* * (non-Javadoc) * * @see * com.expedia.gps.fixtures.DataSets#of(com.expedia.gps.fixtures.Qualifier) */ @Override public <D> DataSet<D> dataset(DataKey<D> key) { LOG.trace("lookup [{}] in [{}] domain", key, FixtureFactory.this.getName()); return doGet(key); } public FixtureFactory<S> setSizeOf(Class<?> key, Integer numberOfElements) { return setSizeOf(getKeyFor(key), numberOfElements); } public FixtureFactory<S> setSizeOf(DataKey<?> key, Integer numberOfElements) { Preconditions.checkState(datasetKeys().contains(key), "Cannot set the number of elements of %s : No dataset exists for this key.", key); this.numberOfItemsMap.put(key, numberOfElements); return this; } public FixtureFactory<S> setEmpty(Class<?>...clazzes) { for(Class<?> k:clazzes){ setSizeOf(k, 0); } return this; } public FixtureFactory<S> setEmpty(DataKey<?>...keys) { for(DataKey<?> k:keys){ setSizeOf(k, 0); } return this; } @Override public Integer sizeOf(Class<?> key) { return sizeOf(getKeyFor(key)); } @Override public Integer sizeOf(DataKey<?> key) { Integer n = null; Fixture<S> parent = this.getParent(); n = numberOfItemsMap.get(key); if (n == null && parent != null) { n = parent.sizeOf(key); } if (n == null) { DataSet<?> d = this.dataSetMap.get(key); if (d != null) { n = d.isGenerated() ? getSpecification().getDefaultNumberOfItems() : d.list() .size(); } } return n; } <D> DataSet<D> doGet(DataKey<D> key) { Preconditions .checkNotNull(key); DataSet<D> ds = doGetFromThisDomain(key); if (ds == null && parent != null) { ds = delegateGetToParent(key); } if (ds == null) { throw new DataSetNotFoundException("No dataset found for key " + key); } else { LOG.trace("FOUND [{}] as a [{}] in [{}] domain", new Object[]{key, ds.toString(), FixtureFactory.this.getName()}); return ds; } } private <D> DataSet<D> doGetFromThisDomain(DataKey<D> key) { @SuppressWarnings("unchecked") DataSet<D> ds = (DataSet<D>) dataSetMap.get(key); return ds; } private <D> DataSet<D> delegateGetToParent(DataKey<D> key) { DataSet<D> ds; LOG.trace("delegating to parent"); ds = parent.dataset(key); if (ds != null && ds.isGenerated()) { ds = regenerateData(key, ds); } return ds; } private <D> DataSet<D> regenerateData(final DataKey<D> key, DataSet<D> ds) { LOG.trace("found the generated dataset in the parent domain, regenerating it for {} domain", FixtureFactory.this.getName()); GenerationStrategy<D, ? super S> s = strategy(key); if (s != null) { DataSet<D> gd= new GeneratedDataSet<D,S>(FixtureFactory.this, s, key, eventBus); if (ds.isPersistent()) { ds = new PersistentDataSet<D>(gd, new Supplier<DataStore<D>>(){ @SuppressWarnings("unchecked") @Override public DataStore<D> get() { if(FixtureFactory.this.isPersistent()){ return FixtureFactory.this.getDataStoreProvider().get(key); }else{ return (DataStore<D>) DataStore.IDENTITY; } } }, this.getRandomizer()); } else { ds = gd; } put(key, ds); } return ds; } @Override public Iterable<DataSet<?>> datasets() { return parent == null ? dataSetMap.values() : FluentIterable.from(Iterables.concat(dataSetMap.values(), parent.datasets())); } @Override public Set<DataKey<?>> datasetKeys() { return parent == null ? dataSetMap.keySet() : FluentIterable.from(Iterables.concat(dataSetMap.keySet(), parent.datasetKeys())).toSet(); } @Override public <D> Generator<D> generator(Class<D> clazz) { GenerationStrategy<D, ? super S> gen = strategy(clazz); if (gen == null) { throw new GeneratorNotFoundException("No generator found for key " + clazz); } else { return new GeneratorImpl<D, S>(FixtureFactory.this, gen, clazz); } } @Override public <D> Generator<D> generator(DataKey<D> key) { GenerationStrategy<D, ? super S> gen = strategy(key); if (gen == null) { throw new GeneratorNotFoundException("No generator found for key " + key); } else { return new GeneratorImpl<D, S>(FixtureFactory.this, gen, key.getType()); } } @Override public Iterable<GenerationStrategy<?, ? extends DataSpecification>> strategies() { return parent == null ? generatorMap.values() : FluentIterable.from(Iterables.concat(generatorMap.values(), parent.strategies())); } @Override public Set<DataKey<?>> strategyKeys() { return parent == null ? generatorMap.keySet() : FluentIterable.from(Iterables.concat(generatorMap.keySet(), parent.strategyKeys())).toSet(); } @Override public <D> GenerationStrategy<D, S> strategy(Class<D> clazz) { DataKey<D> key = getKeyFor(clazz); return strategy(key); } @Override public <D> GenerationStrategy<D, S> strategy(DataKey<D> key) { Preconditions.checkNotNull(key); @SuppressWarnings("unchecked") GenerationStrategy<D, S> gen = (GenerationStrategy<D, S>) generatorMap.get(key); if (gen == null && getParent() != null) { return getParent().strategy(key); } return gen; } <D> DataKey<D> getKeyFor(Class<D> clazz) { Preconditions.checkNotNull(clazz); DataKey<D> key = DataKey.makeDefault(clazz); return key; } /** * Register a generation strategy under the specified <code>key</code>. * * TODO make one method instead of two that takes a data set and a generator * * @param key * the key * @param dataset * the data set * @param <D> * the type of data generated */ <D> void put(DataKey<D> key, DataSet<D> dataset) { dataSetMap.put(key, dataset); this.version ++; eventBus.post(new DataSetRegistered(key, this, dataset.isGenerated(), dataset.isPersistent())); } /** * Register a generation strategy under the specified qualifier. * * TODO make one method instead of two that takes a data set and a generator * * @param key * the key * @param strategy * the generation strategy * @param <D> * the type of data generated */ <D> void put(DataKey<D> key, GenerationStrategy<D, ? super S> strategy) { generatorMap.put(key, strategy); } /** * Remove the {@link DataSet} identified by <code>key</code> from this {@link FixtureFactory}. * * @param key * the qualifier associated to the {@link DataSet}. * @param <D> * the type of data * @return this instance */ @SuppressWarnings("unchecked") public <D> DataSet<D> remove(DataKey<D> key) { generatorMap.remove(key); DataSet<D> removed = (DataSet<D>) dataSetMap.remove(key); if(removed !=null) { this.version ++; eventBus.post(new DataSetRemoved(key, this)); } return removed; } /** * Remove the specified {@link DataSet} of this instance. * * @param type * the implicit key to the {@link DataSet}. * @param <D> * the type of data * @return this instance */ public <D> DataSet<D> remove(Class<D> type) { return remove(DataKey.makeDefault(type)); } @Override public EventBus getEventBus() { return this.eventBus; } @Subscribe public void captureChildrenNodeEvent(Object event){ this.eventBus.post(event); } // ------------------------------------------------------------------------------------------------------------------------------------------------- // // Builder implementations // // ------------------------------------------------------------------------------------------------------------------------------------------------- /** * Builder of {@link DataSet}. * * @author ngagnon * * @param <T> * the type of data that will be managed by the new {@link DataSet}. */ public class DataSetBuilder<D, T> { private final DataKey<D> originalKey; private Predicate<? super T> filter; private final Function<? super T, D> converter; private boolean persistent; DataSetBuilder(DataKey<D> originalQualifier, Predicate<? super T> filter, Function<? super T, D> converter) { this.filter = filter; this.converter = converter; this.originalKey = originalQualifier; } /** * Make this {@link DataSet} persistent, generated data will be persisted. * * @return this builder */ public final DataSetBuilder<D, T> persistent() { this.persistent = true; return this; } /** * Make this {@link DataSet} persistent if <code>persistent</code> is true, generated data will be persisted. * * @param persistent true if the dataset to build should be persistent. * @return this builder */ public final DataSetBuilder<D, T> persistent(boolean persistent) { this.persistent = persistent; return this; } /** * Filter the content of the {@link DataSet} to expose only what matches the <code>aFilter</code>. * * @param aFilter the filter to use. * @return this builder */ public final DataSetBuilder<D, T> filtered(Predicate<? super T> aFilter) { injector.inject(aFilter); this.filter = Predicates.and(this.filter, aFilter); return this; } /** * Convert the content of the {@link DataSet} to a new type using <code>aConverter</code>. * * @param aConverter the converter to use. * @param <NEW_TYPE> the output type of the converter * @return this builder */ public final <NEW_TYPE> DataSetBuilder<D, NEW_TYPE> transformed(Function<? super NEW_TYPE, ? extends T> aConverter) { injector.inject(aConverter); Function<? super NEW_TYPE, D> newConverter = Functions.compose(this.converter, aConverter); Predicate<? super NEW_TYPE> newPredicate = Predicates.compose(this.filter, aConverter); return new DataSetBuilder<D, NEW_TYPE>(originalKey, newPredicate, newConverter); } /** * Build a {@link DataSet} composed of the provided <code>elements</code>. * * @param elements the elements that will compose the new {@link DataSet} * @return a new static data set */ @SafeVarargs public final DataSet<D> composedOf(T... elements) { Iterable<T> items = Iterables.filter(Arrays.asList(elements), filter); DataSet<D> dataset = new GenericDataSet<D>(Iterables.transform(items, converter), originalKey.getType(), getRandomizer()); addToDataDomain(dataset); return dataset; } /** * Build a {@link DataSet} composed of the provided <code>elements</code>. * * @param elements the elements that will compose the new {@link DataSet} * @return a new static data set */ public final DataSet<D> composedOf(Iterable<? extends T> elements) { Iterable<? extends T> items = Iterables.filter(elements, filter); DataSet<D> dataset = new GenericDataSet<D>(Iterables.transform(items, converter), originalKey.getType(), getRandomizer()); addToDataDomain(dataset); return dataset; } /** * Build a {@link DataSet} composed of the provided <code>datasets</code>. The passed in <code>datasets</code> are lazily loaded when * the returned {@link DataSet} is first accessed. * * @param datasets the data sets that will compose this new {@link DataSet} * @return a new static data set */ @SafeVarargs public final DataSet<D> composedOf(DataSet<? extends T>... datasets) { List<DataSet<? extends T>> datasetList = Arrays.asList(datasets); Function<DataSet<? extends T>, Iterable<? extends T>> toIterable = new Function<DataSet<? extends T>, Iterable<? extends T>>() { @Override public Iterable<? extends T> apply(DataSet<? extends T> ds) { return ds.get(); } }; Iterable<D> items = FluentIterable.from(datasetList).transformAndConcat(toIterable).filter(filter).transform(converter); DataSet<D> dataset = new GenericDataSet<D>(Suppliers.ofInstance(items), originalKey.getType(), getRandomizer()); addToDataDomain(dataset); return dataset; } /** * Build a {@link DataSet} generated by a <code>generator</code> which * implements the Goggle guava's {@link Supplier} interface. * * @param generator * the generator to use * @param numberOfItems * the number of items to generate * @return a new generated data set */ public final DataSet<D> generatedBy(final Supplier<? extends T> generator, final int numberOfItems) { DataSet<D> dataset = currentFixtureSupplier.execute(new Callable<DataSet<D>>() { @Override public DataSet<D> call() { return generatedBy( new SupplierGenerationStrategyAdapter<>(originalKey, injector.inject(generator), GeneratorAnnotationHelper.getAffectedDataSet(generator.getClass())), numberOfItems); } }, FixtureFactory.this); return dataset; } /** * Build a {@link DataSet} generated by a <code>generator</code> which * implements the Goggle guava's {@link Supplier} interface. * * @param generator * the generator to use * @return a new generated data set */ public final DataSet<D> generatedBy(final Supplier<? extends T> generator) { DataSet<D> dataset = currentFixtureSupplier.execute(new Callable<DataSet<D>>() { @Override public DataSet<D> call() { return generatedBy(new SupplierGenerationStrategyAdapter<>(originalKey, injector.inject(generator), GeneratorAnnotationHelper.getAffectedDataSet(generator.getClass()))); } }, FixtureFactory.this); return dataset; } /** * Build a {@link DataSet} generated by an implicit <code>strategy</code> * which generate a predetermined number of items. * * @param strategy * the strategy to use * @return a new generated data set */ public final DataSet<D> generatedAsIterableBy(final Supplier<? extends Iterable<T>> strategy) { DataSet<D> dataset = currentFixtureSupplier.execute(new Callable<DataSet<D>>() { @Override public DataSet<D> call() { return generatedBy(new IterableSupplierGenerationStrategyAdapter<>(injector.inject(strategy), GeneratorAnnotationHelper.getAffectedDataSet(strategy.getClass()))); } }, FixtureFactory.this); return dataset; } /** * Build a {@link DataSet} generated by a generic <code>strategy</code>. * * @param strategy * the strategy to use * @return a new generated data set */ public final DataSet<D> generatedBy(final GenerationStrategy<? extends T, ? super S> strategy, final Integer numberOfItems) { DataSet<D> dataset = generatedBy(strategy); if (numberOfItems != null) { FixtureFactory.this.setSizeOf(originalKey, numberOfItems); } return dataset; } /** * Build a {@link DataSet} generated by a generic <code>strategy</code>. * * @param strategy * the strategy to use * @return a new generated data set */ public final DataSet<D> generatedBy(final GenerationStrategy<? extends T, ? super S> strategy) { GenerationStrategy<D, S> derivedStrategy = new ContextualGenerationStrategyDecorator<>(new TransformedStrategy<D, T, S>(strategy, filter, converter), originalKey, currentFixtureSupplier); GeneratedDataSet<D, S> dataset = new GeneratedDataSet<D, S>(FixtureFactory.this, derivedStrategy, originalKey, eventBus); FixtureFactory.this.put(originalKey, derivedStrategy); addToDataDomain(dataset); return dataset; } // TODO : add a generatedBy from a list of generation strategy key /** * Build a {@link DataSet} materialized from existing {@link DataSet} of this {@link FixtureFactory}. The resulting {@link DataSet} is * a view of the {@link DataSet} is materialized from. * * @param keys the default keys of the data sets * @return a new generated data set */ @SafeVarargs public final DataSet<D> materalizedFrom(final Class<? extends T>... keys) { return materalizedFrom(Iterables.transform(Arrays.asList(keys), new Function<Class<? extends T>, DataKey<? extends T>>() { @Override public DataKey<? extends T> apply(Class<? extends T> input) { return DataKey.makeDefault(input); } })); } /** * Build a {@link DataSet} materialized from existing {@link DataSet} of this {@link FixtureFactory}. The resulting {@link DataSet} is * a view of the {@link DataSet} is materialized from. * * @param keys the keys of the data sets * @return a new generated data set */ @SafeVarargs public final DataSet<D> materalizedFrom(final DataKey<? extends T>... keys) { return materalizedFrom(Arrays.asList(keys)); } /** * Build a {@link DataSet} materialized from existing {@link DataSet} of this {@link FixtureFactory}. The resulting {@link DataSet} is * a view of the {@link DataSet} is materialized from. * * @param keys the keys of the data sets * @return a new generated data set */ public final DataSet<D> materalizedFrom(final Iterable<DataKey<? extends T>> keys) { GenerationStrategy<D, S> derivedStrategy = new TransformedStrategy<D, T, S>(new DataSetAggregationStrategy<T, S>(keys), filter, converter); GeneratedDataSet<D,S> dataset = new GeneratedDataSet<D,S>(FixtureFactory.this, derivedStrategy, originalKey, eventBus); FixtureFactory.this.put(originalKey, derivedStrategy); addToDataDomain(dataset); return dataset; } private void addToDataDomain(DataSet<D> dataset) { if (persistent) { dataset = new PersistentDataSet<>(dataset, new Supplier<DataStore<D>>(){ @SuppressWarnings("unchecked") @Override public DataStore<D> get() { //If this fixture factory is not persistent, we return a transient datastore but we //need to keep the persistent nature of the dataset since another fixture factory inheriting from //this factory may be persistent if(FixtureFactory.this.isPersistent()){ return FixtureFactory.this.getDataStoreProvider().get(originalKey); }else{ return (DataStore<D>) DataStore.IDENTITY; } } }, randomizer); } FixtureFactory.this.put(originalKey, dataset); } } /** * Builder of {@link DataSet}. * * @author ngagnon * * @param <T> * the type of data that will be managed by the new {@link DataSet}. */ public class GeneratorBuilder<D, T> { private final DataKey<D> originalKey; private Predicate<? super T> filter; private final Function<? super T, D> converter; GeneratorBuilder(DataKey<D> origoriginalKey, Predicate<? super T> filter, Function<? super T, D> converter) { this.filter = filter; this.converter = converter; this.originalKey = origoriginalKey; } /** * Filter the content of the {@link Generator} being built to expose only what matches the <code>filter</code>. * * @param filter the filter to use. * @return this builder */ public GeneratorBuilder<D, T> filtered(Predicate<? super T> filter) { injector.inject(filter); this.filter = Predicates.and(this.filter, filter); return this; } /** * Convert the content of the {@link Generator} to a new type using the <code>converter</code>. * * @param converter the converter to use. * @param <NEW_TYPE> the output type of the converter * @return this builder */ public <NEW_TYPE> GeneratorBuilder<D, NEW_TYPE> transformed(Function<? super NEW_TYPE, T> converter) { injector.inject(converter); Function<? super NEW_TYPE, D> newConverter = Functions.compose(this.converter, converter); Predicate<? super NEW_TYPE> newPredicate = Predicates.compose(this.filter, converter); return new GeneratorBuilder<D, NEW_TYPE>(originalKey, newPredicate, newConverter); } /** * Build a {@link Generator} that delegate to a generic <code>strategy</code>. * * @param strategy the strategy to use * @return a new generated data set */ public Generator<D> generatedBy(final GenerationStrategy<? extends T, ? super S> strategy) { GenerationStrategy<D, S> derivedStrategy = new ContextualGenerationStrategyDecorator<>(new TransformedStrategy<D, T, S>(strategy, filter, converter), originalKey, currentFixtureSupplier); FixtureFactory.this.put(originalKey, derivedStrategy); Generator<D> generator = new GeneratorImpl<D, S>(FixtureFactory.this, derivedStrategy, originalKey.getType()); addToDataDomain(generator); return generator; } /** * Build a {@link Generator} that delegate to a generic <code>strategy</code>. * * @param strategy the strategy to use * @return a new generated data set */ public Generator<D> generatedBy(final GenerationStrategy<? extends T, ? super S> strategy, final Integer numberOfItems) { Generator<D> generator = generatedBy(strategy); if (numberOfItems != null) { FixtureFactory.this.setSizeOf(originalKey, numberOfItems); } return generator; } /** * Build a {@link Generator} that delegate to a simple <code>strategy</code>. * * @param generator the strategy to use * @param numberOfItems override the default number of items of the {@link DataSpecification}. * @return a new generated data set */ public Generator<D> generatedBy(final Supplier<? extends T> generator) { return currentFixtureSupplier.execute(new Callable<Generator<D>>(){ @Override public Generator<D> call(){ return generatedBy(new SupplierGenerationStrategyAdapter<>(originalKey, injector.inject(generator), GeneratorAnnotationHelper.getAffectedDataSet(generator.getClass()))); } }, FixtureFactory.this); } /** * Build a {@link Generator} that delegate to a simple <code>strategy</code>. * * @param generator the strategy to use * @param numberOfItems override the default number of items of the {@link DataSpecification}. * @return a new generated data set */ public Generator<D> generatedBy(final Supplier<? extends T> generator, final int numberOfItems) { return currentFixtureSupplier.execute(new Callable<Generator<D>>(){ @Override public Generator<D> call(){ return generatedBy(new SupplierGenerationStrategyAdapter<>(originalKey, injector.inject(generator), GeneratorAnnotationHelper.getAffectedDataSet(generator.getClass())), numberOfItems); } }, FixtureFactory.this); } private void addToDataDomain(Generator<D> dataset) { FixtureFactory.this.put(originalKey, dataset); } } }