Commit e3be6ef2 authored by Daniel Bluhm's avatar Daniel Bluhm

Turn WriterGeneratorFactory into generic FromDataBuilder

And add tests for it.
Signed-off-by: Daniel Bluhm's avatarDaniel Bluhm <bluhmdj@ornl.gov>
parent 691d7f7e
...@@ -15,10 +15,8 @@ import java.io.IOException; ...@@ -15,10 +15,8 @@ import java.io.IOException;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.io.StringWriter; import java.io.StringWriter;
import java.io.Writer; import java.io.Writer;
import java.util.HashMap; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
...@@ -98,7 +96,7 @@ public class DataElementProcessor extends AbstractProcessor { ...@@ -98,7 +96,7 @@ public class DataElementProcessor extends AbstractProcessor {
.dataFieldExtractor(new DataFieldExtractor(elementUtils)) .dataFieldExtractor(new DataFieldExtractor(elementUtils))
.build(); .build();
PersistenceExtractor persistenceExtractor = new PersistenceExtractor(elementUtils); PersistenceExtractor persistenceExtractor = new PersistenceExtractor(elementUtils);
WriterGeneratorFactory generatorFactory = new WriterGeneratorFactory( FromDataBuilder<WriterGenerator> generatorFactory = new FromDataBuilder<>(
Set.of( Set.of(
DataElementWriterGenerator.class, DataElementWriterGenerator.class,
PersistenceWriterGenerator.class PersistenceWriterGenerator.class
...@@ -108,22 +106,16 @@ public class DataElementProcessor extends AbstractProcessor { ...@@ -108,22 +106,16 @@ public class DataElementProcessor extends AbstractProcessor {
// Iterate over all elements with DataElement Annotation // Iterate over all elements with DataElement Annotation
for (final Element element : roundEnv.getElementsAnnotatedWith(DataElement.class)) { for (final Element element : roundEnv.getElementsAnnotatedWith(DataElement.class)) {
try { try {
// Create and populate DataPool // Create and populate data pool
Map<Class<?>, Object> dataPool = new HashMap<>(); List<Object> data = new ArrayList<>();
dataPool.put( data.add(dataElementExtractor.extract(element));
DataElementMetadata.class, persistenceExtractor.extractIfApplies(element)
dataElementExtractor.extract(element) .ifPresent(data::add);
);
Optional<PersistenceMetadata> persistence =
persistenceExtractor.extractIfApplies(element);
if (persistence.isPresent()) {
dataPool.put(PersistenceMetadata.class, persistence.get());
}
// Get flattened list of GeneratedFileWriters from set of // Get flattened list of GeneratedFileWriters from set of
// Generators. // Generators.
List<GeneratedFileWriter> fileWriters = List<GeneratedFileWriter> fileWriters =
generatorFactory.create(dataPool).stream() generatorFactory.create(data).stream()
// generators into GeneratedFileWriter Streams // generators into GeneratedFileWriter Streams
.flatMap(generator -> generator.generate().stream()) .flatMap(generator -> generator.generate().stream())
// Collect into flattened list // Collect into flattened list
......
...@@ -14,6 +14,7 @@ package org.eclipse.ice.dev.annotations.processors; ...@@ -14,6 +14,7 @@ package org.eclipse.ice.dev.annotations.processors;
import java.lang.reflect.Constructor; import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
...@@ -24,72 +25,96 @@ import org.slf4j.Logger; ...@@ -24,72 +25,96 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
/** /**
* Factory for WriterGenerators. Create method parameters represent dependencies * Construct used for building classes from a pool of data.
* of a set of generators. *
* Given a set of classes to attempt to build and a pool of data, the
* FromDataBuilder will check for constructors on those classes that it can
* satisfy with the data found in the pool.
*
* Repeat instances of the same data type in the pool will result in whichever
* instance that happened to come first in the pool to be ignored when building
* objects.
*
* @author Daniel Bluhm * @author Daniel Bluhm
*/ */
public class WriterGeneratorFactory { public class FromDataBuilder<T> {
/** /**
* Logger. * Logger.
*/ */
private static final Logger logger = LoggerFactory.getLogger(WriterGeneratorFactory.class); private static final Logger logger = LoggerFactory.getLogger(FromDataBuilder.class);
/** /**
* Generators to potentially create. * Classes to potentially instantiate.
*/ */
private Set<Class<? extends WriterGenerator>> generatorClasses; private Set<Class<? extends T>> classes;
/** /**
* Create WriterGeneratorFactory. * Create FromDataBuilder.
* @param generators set of generators that can be created. * @param classes set of classes that can be instantiated.
*/ */
public WriterGeneratorFactory( public FromDataBuilder(
Set<Class<? extends WriterGenerator>> generators Set<Class<? extends T>> classes
) { ) {
this.generatorClasses = generators; this.classes = classes;
} }
/** /**
* Create all writer generators that can be created from the given data * Create instances of {@link #classes} that can be created from the given
* pool. * data pool.
* @param dataPool pool of data from which writer generators are created. * @param dataPool pool of data from which instances are created.
* @return created writer generators. * @return created objects.
*/ */
public Set<WriterGenerator> create(Map<Class<?>, Object> dataPool) { public Set<T> create(Object... dataPool) {
Set<WriterGenerator> generators = generatorClasses.stream() Map<Class<?>, Object> dataPoolMap = Arrays.stream(dataPool)
.map(cls -> create(cls, dataPool)) .collect(Collectors.toMap(
Object::getClass,
o -> o
));
Set<T> objects = classes.stream()
.map(cls -> create(cls, dataPoolMap))
.filter(Optional::isPresent) .filter(Optional::isPresent)
.map(Optional::get) .map(Optional::get)
.collect(Collectors.toSet()); .collect(Collectors.toSet());
return generators; return objects;
}
/**
* Create instances of {@link #classes} that can be created from the given
* data pool.
* @param dataPool pool of data from which instances are created.
* @return created objects.
*/
public Set<T> create(List<Object> dataPool) {
return create(dataPool.toArray());
} }
/** /**
* Get a writer generator instance of given type from data pool if possible, * Create an instance of class from the data pool if possible, otherwise
* null otherwise. * return empty.
* @param cls type of writer generator to attempt creating. * @param cls type to attempt to create.
* @param dataPool pool of data from which writer generator will be created. * @param dataPoolMap pool of data from which instance will be created.
* @return created writer generator or null. * @return created object wrapped in optional or empty.
*/ */
private Optional<WriterGenerator> create( private Optional<T> create(
Class<? extends WriterGenerator> cls, Class<? extends T> cls,
Map<Class<?>, Object> dataPool Map<Class<?>, Object> dataPoolMap
) { ) {
Constructor<?>[] constructors = cls.getConstructors(); Constructor<?>[] constructors = cls.getConstructors();
for (Constructor<?> cons : constructors) { for (Constructor<?> cons : constructors) {
Class<?>[] parameters = cons.getParameterTypes(); Class<?>[] parameters = cons.getParameterTypes();
Optional<Object[]> objects = getAll(dataPool, parameters); Optional<Object[]> objects = getAll(dataPoolMap, parameters);
if (objects.isPresent()) { if (objects.isPresent()) {
try { try {
return Optional.of( return Optional.of(
(WriterGenerator) cons.newInstance(objects.get()) cls.cast(cons.newInstance(objects.get()))
); );
} catch ( } catch (
InstantiationException | IllegalAccessException | InstantiationException | IllegalAccessException |
IllegalArgumentException | InvocationTargetException e IllegalArgumentException | InvocationTargetException e
) { ) {
logger.debug( logger.debug(
"Failed to instantiate WriterGenerator from data pool:", "Failed to instantiate {} from data pool:",
cls.getSimpleName(),
e e
); );
return Optional.empty(); return Optional.empty();
...@@ -102,16 +127,16 @@ public class WriterGeneratorFactory { ...@@ -102,16 +127,16 @@ public class WriterGeneratorFactory {
/** /**
* Get all values for given keys from dataPool if all keys are present, * Get all values for given keys from dataPool if all keys are present,
* otherwise return empty. * otherwise return empty.
* @param dataPool from which data is retrieved. * @param dataPoolMap from which data is retrieved.
* @param keys types to look up in data pool. * @param keys types to look up in data pool.
* @return Objects gathered wrapped in Optional or empty. * @return Objects gathered wrapped in Optional or empty.
*/ */
private Optional<Object[]> getAll( private Optional<Object[]> getAll(
Map<Class<?>, Object> dataPool, Class<?>... keys Map<Class<?>, Object> dataPoolMap, Class<?>... keys
) { ) {
List<Object> parameters = new ArrayList<>(); List<Object> parameters = new ArrayList<>();
for (Class<?> key : keys) { for (Class<?> key : keys) {
Object retrieved = dataPool.get(key); Object retrieved = dataPoolMap.get(key);
if (retrieved == null) { if (retrieved == null) {
return Optional.empty(); return Optional.empty();
} }
......
package org.eclipse.ice.tests.dev.annotations.processors;
import static org.junit.jupiter.api.Assertions.*;
import java.util.Set;
import java.util.stream.Collectors;
import org.eclipse.ice.dev.annotations.processors.FromDataBuilder;
import org.junit.jupiter.api.Test;
import lombok.AllArgsConstructor;
class FromDataBuilderTest {
interface Data {
public String getData();
}
public static class Data1 implements Data {
private static final String VALUE = "Data1";
private String data = VALUE;
@Override
public String getData() {
return data;
}
}
public static class Data2 implements Data {
private static final String VALUE = "Data2";
private String data = VALUE;
@Override
public String getData() {
return data;
}
}
interface Thing {
public void assertValues();
}
@AllArgsConstructor
public static class Thing1 implements Thing {
private Data1 data1;
@Override
public void assertValues() {
assertEquals(Data1.VALUE, data1.getData());
}
}
@AllArgsConstructor
public static class Thing2 implements Thing {
private Data2 data2;
@Override
public void assertValues() {
assertEquals(Data2.VALUE, data2.getData());
}
}
@AllArgsConstructor
public static class Thing3 implements Thing {
private Data1 data1;
private Data2 data2;
@Override
public void assertValues() {
assertEquals(Data1.VALUE, data1.getData());
assertEquals(Data2.VALUE, data2.getData());
}
}
@Test
void testCreate() {
FromDataBuilder<Thing> builder = new FromDataBuilder<>(
Set.of(Thing1.class, Thing2.class, Thing3.class)
);
Set<Thing> things = builder.create(new Data1());
assertEquals(1, things.size());
assertTrue(things.stream().findFirst().get() instanceof Thing1);
things = builder.create(new Data2());
assertEquals(1, things.size());
assertTrue(things.stream().findFirst().get() instanceof Thing2);
things = builder.create(new Data1(), new Data2());
assertEquals(3, things.size());
assertEquals(1, things.stream()
.filter(thing -> thing instanceof Thing1)
.collect(Collectors.toSet())
.size()
);
assertEquals(1, things.stream()
.filter(thing -> thing instanceof Thing2)
.collect(Collectors.toSet())
.size()
);
assertEquals(1, things.stream()
.filter(thing -> thing instanceof Thing3)
.collect(Collectors.toSet())
.size()
);
things.stream().forEach(Thing::assertValues);
}
}
\ No newline at end of file
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment