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;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.Writer;
import java.util.HashMap;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
......@@ -98,7 +96,7 @@ public class DataElementProcessor extends AbstractProcessor {
.dataFieldExtractor(new DataFieldExtractor(elementUtils))
.build();
PersistenceExtractor persistenceExtractor = new PersistenceExtractor(elementUtils);
WriterGeneratorFactory generatorFactory = new WriterGeneratorFactory(
FromDataBuilder<WriterGenerator> generatorFactory = new FromDataBuilder<>(
Set.of(
DataElementWriterGenerator.class,
PersistenceWriterGenerator.class
......@@ -108,22 +106,16 @@ public class DataElementProcessor extends AbstractProcessor {
// Iterate over all elements with DataElement Annotation
for (final Element element : roundEnv.getElementsAnnotatedWith(DataElement.class)) {
try {
// Create and populate DataPool
Map<Class<?>, Object> dataPool = new HashMap<>();
dataPool.put(
DataElementMetadata.class,
dataElementExtractor.extract(element)
);
Optional<PersistenceMetadata> persistence =
persistenceExtractor.extractIfApplies(element);
if (persistence.isPresent()) {
dataPool.put(PersistenceMetadata.class, persistence.get());
}
// Create and populate data pool
List<Object> data = new ArrayList<>();
data.add(dataElementExtractor.extract(element));
persistenceExtractor.extractIfApplies(element)
.ifPresent(data::add);
// Get flattened list of GeneratedFileWriters from set of
// Generators.
List<GeneratedFileWriter> fileWriters =
generatorFactory.create(dataPool).stream()
generatorFactory.create(data).stream()
// generators into GeneratedFileWriter Streams
.flatMap(generator -> generator.generate().stream())
// Collect into flattened list
......
......@@ -14,6 +14,7 @@ package org.eclipse.ice.dev.annotations.processors;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Optional;
......@@ -24,72 +25,96 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Factory for WriterGenerators. Create method parameters represent dependencies
* of a set of generators.
* Construct used for building classes from a pool of data.
*
* 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
*/
public class WriterGeneratorFactory {
public class FromDataBuilder<T> {
/**
* 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.
* @param generators set of generators that can be created.
* Create FromDataBuilder.
* @param classes set of classes that can be instantiated.
*/
public WriterGeneratorFactory(
Set<Class<? extends WriterGenerator>> generators
public FromDataBuilder(
Set<Class<? extends T>> classes
) {
this.generatorClasses = generators;
this.classes = classes;
}
/**
* Create all writer generators that can be created from the given data
* pool.
* @param dataPool pool of data from which writer generators are created.
* @return created writer generators.
* 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<WriterGenerator> create(Map<Class<?>, Object> dataPool) {
Set<WriterGenerator> generators = generatorClasses.stream()
.map(cls -> create(cls, dataPool))
public Set<T> create(Object... dataPool) {
Map<Class<?>, Object> dataPoolMap = Arrays.stream(dataPool)
.collect(Collectors.toMap(
Object::getClass,
o -> o
));
Set<T> objects = classes.stream()
.map(cls -> create(cls, dataPoolMap))
.filter(Optional::isPresent)
.map(Optional::get)
.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,
* null otherwise.
* @param cls type of writer generator to attempt creating.
* @param dataPool pool of data from which writer generator will be created.
* @return created writer generator or null.
* Create an instance of class from the data pool if possible, otherwise
* return empty.
* @param cls type to attempt to create.
* @param dataPoolMap pool of data from which instance will be created.
* @return created object wrapped in optional or empty.
*/
private Optional<WriterGenerator> create(
Class<? extends WriterGenerator> cls,
Map<Class<?>, Object> dataPool
private Optional<T> create(
Class<? extends T> cls,
Map<Class<?>, Object> dataPoolMap
) {
Constructor<?>[] constructors = cls.getConstructors();
for (Constructor<?> cons : constructors) {
Class<?>[] parameters = cons.getParameterTypes();
Optional<Object[]> objects = getAll(dataPool, parameters);
Optional<Object[]> objects = getAll(dataPoolMap, parameters);
if (objects.isPresent()) {
try {
return Optional.of(
(WriterGenerator) cons.newInstance(objects.get())
cls.cast(cons.newInstance(objects.get()))
);
} catch (
InstantiationException | IllegalAccessException |
IllegalArgumentException | InvocationTargetException e
) {
logger.debug(
"Failed to instantiate WriterGenerator from data pool:",
"Failed to instantiate {} from data pool:",
cls.getSimpleName(),
e
);
return Optional.empty();
......@@ -102,16 +127,16 @@ public class WriterGeneratorFactory {
/**
* Get all values for given keys from dataPool if all keys are present,
* 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.
* @return Objects gathered wrapped in Optional or empty.
*/
private Optional<Object[]> getAll(
Map<Class<?>, Object> dataPool, Class<?>... keys
Map<Class<?>, Object> dataPoolMap, Class<?>... keys
) {
List<Object> parameters = new ArrayList<>();
for (Class<?> key : keys) {
Object retrieved = dataPool.get(key);
Object retrieved = dataPoolMap.get(key);
if (retrieved == null) {
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