package org.mapfish.print.processor;
import com.google.common.base.Function;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import jsr166y.ForkJoinPool;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.mapfish.print.AbstractMapfishSpringTest;
import org.mapfish.print.config.Configuration;
import org.mapfish.print.output.Values;
import org.mapfish.print.parser.HasDefaultValue;
import org.mapfish.print.processor.map.CreateOverviewMapProcessor;
import org.mapfish.print.processor.map.SetStyleProcessor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.annotation.DirtiesContext;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import javax.annotation.Nullable;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
/**
* Test building processor graphs.
* <p></p>
*/
public class ProcessorDependencyGraphFactoryTest extends AbstractMapfishSpringTest {
private static final String EXECUTION_TRACKER = "executionOrder";
@Autowired
private ProcessorDependencyGraphFactory processorDependencyGraphFactory;
static class TestOrderExecution {
List<TestProcessor> testOrderExecution = new ArrayList<TestProcessor>();
public void doExecute(TestProcessor p) {
testOrderExecution.add(p);
}
}
private ForkJoinPool forkJoinPool;
@Before
public void setUp() throws Exception {
forkJoinPool = new ForkJoinPool(1);
}
@After
public void tearDown() throws Exception {
forkJoinPool.shutdownNow();
}
@Test
public void testSimpleBuild() throws Exception {
final ArrayList<TestProcessor> processors = Lists.newArrayList(RootNoOutput, RootMapOut,
RootTableAndWidthOut, NeedsTable, NeedsMap);
ProcessorDependencyGraph graph = this.processorDependencyGraphFactory.build(processors,
Collections.<String, Class<?>>emptyMap());
assertContainsProcessors(graph.getRoots(), RootMapOut, RootNoOutput, RootTableAndWidthOut);
final TestOrderExecution execution = new TestOrderExecution();
Values values = new Values();
values.put(EXECUTION_TRACKER, execution);
forkJoinPool.invoke(graph.createTask(values));
assertEquals(execution.testOrderExecution.toString(), 5, execution.testOrderExecution.size());
assertHasOrdering(execution, RootMapOut, NeedsMap);
assertHasOrdering(execution, RootTableAndWidthOut, NeedsTable);
}
@Test
@DirtiesContext
public void testSimpleBuild_ExternalDependency_NoInput() throws Exception {
final ArrayList<TestProcessor> processors = Lists.newArrayList(
RootNoOutput, RootMapOut, RootTableAndWidthOut, NeedsTable, NeedsMap);
// external dependency: RootNoOutput should run before RootMapOuts
final List<ProcessorDependency> dependencies = Lists.newArrayList(
new ProcessorDependency(RootNoOutputClass.class, RootMapOutClass.class));
this.processorDependencyGraphFactory.setDependencies(dependencies);
ProcessorDependencyGraph graph = this.processorDependencyGraphFactory.build(processors,
Collections.<String, Class<?>>emptyMap());
assertContainsProcessors(graph.getRoots(), RootNoOutput, RootMapOut, RootTableAndWidthOut);
final TestOrderExecution execution = new TestOrderExecution();
Values values = new Values();
values.put(EXECUTION_TRACKER, execution);
forkJoinPool.invoke(graph.createTask(values));
assertEquals(execution.testOrderExecution.toString(), 5, execution.testOrderExecution.size());
assertHasOrdering(execution, RootMapOut, NeedsMap);
assertHasOrdering(execution, RootTableAndWidthOut, NeedsTable);
assertHasOrdering(execution, RootNoOutput, RootMapOut);
}
@Test
@DirtiesContext
public void testSimpleBuild_ExternalDependency_SameInput() throws Exception {
final ArrayList<TestProcessor> processors = Lists.newArrayList(RootNoOutput,
RootMapOut, RootTableAndWidthOut, NeedsTable, StyleNeedsMap, NeedsMap);
// external dependency: StyleNeedsMap should run before NeedsMap when they have the same
// mapped input for "map"
final List<ProcessorDependency> dependencies = Lists.newArrayList(
new ProcessorDependency(StyleNeedsMapClass.class, NeedsMapClass.class, Sets.newHashSet("map")));
this.processorDependencyGraphFactory.setDependencies(dependencies);
ProcessorDependencyGraph graph = this.processorDependencyGraphFactory.build(processors,
Collections.<String, Class<?>>emptyMap());
assertContainsProcessors(graph.getRoots(), RootNoOutput, RootTableAndWidthOut, RootMapOut);
final TestOrderExecution execution = new TestOrderExecution();
Values values = new Values();
values.put(EXECUTION_TRACKER, execution);
forkJoinPool.invoke(graph.createTask(values));
assertEquals(execution.testOrderExecution.toString(), 6, execution.testOrderExecution.size());
assertHasOrdering(execution, RootMapOut, StyleNeedsMap, NeedsMap);
assertHasOrdering(execution, RootTableAndWidthOut, NeedsTable);
}
/**
* This is a test for an external dependency on a common input property,
* where the property has a different name for the two nodes.
* For example, this is the case for {@link CreateOverviewMapProcessor} and
* {@link SetStyleProcessor}. The map property for the overview map is called
* `overviewMap` in the `CreateOverviewMapProcessor`, but `map` in the `SetStyleProcessor`.
*/
@Test
@DirtiesContext
public void testSimpleBuild_ExternalDependency_SameInputWithDifferentName() throws Exception {
RootMapOutClass rootMap2Out = new RootMapOutClass("rootMap2Out", MapOutput.class);
rootMap2Out.getOutputMapperBiMap().put("map", "map2");
NeedsOverviewMapAndMap.getInputMapperBiMap().put("map2", "overviewMap");
TestProcessor styleNeedsMap2 = new StyleNeedsMapClass("StyleNeedsMap2", Void.class);
styleNeedsMap2.getInputMapperBiMap().put("map2", "map");
final ArrayList<TestProcessor> processors = Lists.newArrayList(
RootNoOutput, RootMapOut, rootMap2Out, RootTableAndWidthOut,
StyleNeedsMap, styleNeedsMap2,
NeedsOverviewMapAndMap, NeedsTable, NeedsMap);
// external dependency: StyleNeedsMap should run before NeedsMap when they have the same
// mapped input for "map"
final List<ProcessorDependency> dependencies = Lists.newArrayList(
new ProcessorDependency(StyleNeedsMapClass.class, NeedsMapClass.class, Sets.newHashSet("map")));
// external dependency: StyleNeedsMap should run before NeedsOverviewMap when they have the same
// mapped input for "map" and "overviewMap"
dependencies.add(new ProcessorDependency(
StyleNeedsMapClass.class, NeedsOverviewMapAndMapClass.class,
Sets.newHashSet("map;overviewMap")));
this.processorDependencyGraphFactory.setDependencies(dependencies);
ProcessorDependencyGraph graph = this.processorDependencyGraphFactory.build(processors,
Collections.<String, Class<?>>emptyMap());
assertContainsProcessors(graph.getRoots(), RootNoOutput, RootTableAndWidthOut, RootMapOut, rootMap2Out);
final TestOrderExecution execution = new TestOrderExecution();
Values values = new Values();
values.put(EXECUTION_TRACKER, execution);
values.put("map2", "ov-map");
forkJoinPool.invoke(graph.createTask(values));
assertEquals(execution.testOrderExecution.toString(), 9, execution.testOrderExecution.size());
assertHasOrdering(execution, RootMapOut, NeedsMap);
assertHasOrdering(execution, RootTableAndWidthOut, NeedsTable);
assertHasOrdering(execution, StyleNeedsMap, NeedsMap);
assertHasOrdering(execution, styleNeedsMap2, NeedsOverviewMapAndMap);
// check that NeedsOverviewMap is not added as dependency to StyleNeedsMap
ProcessorGraphNode<Object, Object> mapNode = getNodeForProcessor(graph.getRoots(), rootMap2Out);
ProcessorGraphNode<Object, Object> styleNode = getNodeForProcessor(
mapNode.getDependencies(), styleNeedsMap2);
assertEquals(2, styleNode.getAllProcessors().size());
}
/**
* This is a test for an external dependency with two maps.
* It ensures that `NeedsMap2` only has a dependency on `StyleNeedsMap2` and
* `NeedsMap` only a dependency on `StyleNeedsMap`. But not `NeedsMap2` on `StyleNeedsMap`
* or `NeedsMap` on `StyleNeedsMap2`.
*/
@Test
@DirtiesContext
public void testSimpleBuild_ExternalDependency_SameInputTwoMaps() throws Exception {
// add processors for a second map
RootMapOutClass rootMap2Out = new RootMapOutClass("rootMap2Out", MapOutput.class);
rootMap2Out.getOutputMapperBiMap().put("map", "map2");
TestProcessor needsMap2 = new NeedsMapClass("NeedsMap2", Void.class);
needsMap2.getInputMapperBiMap().put("map2", "map");
TestProcessor styleNeedsMap2 = new StyleNeedsMapClass("StyleNeedsMap2", Void.class);
styleNeedsMap2.getInputMapperBiMap().put("map2", "map");
final ArrayList<TestProcessor> processors = Lists.newArrayList(
RootNoOutput, RootMapOut, rootMap2Out, RootTableAndWidthOut,
StyleNeedsMap, styleNeedsMap2,
NeedsTable, NeedsMap, needsMap2);
// external dependency: StyleNeedsMap should run before NeedsMap when they have the same
// mapped input for "map"
final List<ProcessorDependency> dependencies = Lists.newArrayList(new ProcessorDependency(
StyleNeedsMapClass.class, NeedsMapClass.class, Sets.newHashSet("map")));
this.processorDependencyGraphFactory.setDependencies(dependencies);
ProcessorDependencyGraph graph = this.processorDependencyGraphFactory.build(processors,
Collections.<String, Class<?>>emptyMap());
assertContainsProcessors(graph.getRoots(), RootNoOutput, RootTableAndWidthOut, RootMapOut, rootMap2Out);
final TestOrderExecution execution = new TestOrderExecution();
Values values = new Values();
values.put(EXECUTION_TRACKER, execution);
values.put("map2", "the 2nd map definition");
forkJoinPool.invoke(graph.createTask(values));
assertEquals(execution.testOrderExecution.toString(), 9, execution.testOrderExecution.size());
assertHasOrdering(execution, RootMapOut, NeedsMap);
assertHasOrdering(execution, RootTableAndWidthOut, NeedsTable);
assertHasOrdering(execution, StyleNeedsMap, NeedsMap);
assertHasOrdering(execution, styleNeedsMap2, needsMap2);
// check that NeedMap is not added as dependency to styleNeedsMap2
ProcessorGraphNode<Object, Object> mapNode = getNodeForProcessor(graph.getRoots(), rootMap2Out);
ProcessorGraphNode<Object, Object> styleNode = getNodeForProcessor(
mapNode.getDependencies(), styleNeedsMap2);
assertEquals(2, styleNode.getAllProcessors().size());
}
@Test
public void testBuildProcessInputObject() throws Exception {
final ArrayList<Processor> processors = Lists.newArrayList(
RootOutputExecutionTracker, RootNoOutput, RootMapOut, RootTableAndWidthOut,
NeedsTable, NeedsMap);
ProcessorDependencyGraph graph = this.processorDependencyGraphFactory.build(processors,
Collections.<String, Class<?>>emptyMap());
assertContainsProcessors(graph.getRoots(), RootOutputExecutionTracker);
final TestOrderExecution execution = new TestOrderExecution();
Values values = new Values();
values.put(EXECUTION_TRACKER, execution);
forkJoinPool.invoke(graph.createTask(values));
assertEquals(0, execution.testOrderExecution.size());
TestOrderExecution correctTracker = values.getObject(EXECUTION_TRACKER, TestOrderExecution.class);
assertEquals(correctTracker.testOrderExecution.toString(), 5, correctTracker.testOrderExecution.size());
assertHasOrdering(correctTracker, RootMapOut, NeedsMap);
assertHasOrdering(correctTracker, RootTableAndWidthOut, NeedsTable);
}
@Test
public void testBuildProcessInputHasValuesAndOtherInput() throws Exception {
final NeedsValuesAndMap needsValuesAndMap = new NeedsValuesAndMap();
final ArrayList<Processor> processors = Lists.<Processor>newArrayList(
RootOutputExecutionTracker, RootMapOut, needsValuesAndMap);
ProcessorDependencyGraph graph = this.processorDependencyGraphFactory.build(processors,
Collections.<String, Class<?>>emptyMap());
assertContainsProcessors(graph.getRoots(), RootOutputExecutionTracker);
final TestOrderExecution execution = new TestOrderExecution();
Values values = new Values();
values.put(Values.VALUES_KEY, values);
values.put(EXECUTION_TRACKER, execution);
forkJoinPool.invoke(graph.createTask(values));
assertEquals(0, execution.testOrderExecution.size());
TestOrderExecution correctTracker = values.getObject(EXECUTION_TRACKER, TestOrderExecution.class);
assertEquals(correctTracker.testOrderExecution.toString(), 2, correctTracker.testOrderExecution.size());
assertHasOrdering(correctTracker, RootTableAndWidthOut, needsValuesAndMap);
}
@Test
public void testBuildProcessInputHasValuesAndOtherInput_WithInputMapping() throws Exception {
final NeedsValuesAndMap needsValuesAndMap = new NeedsValuesAndMap();
final RootMapOutClass outMapProcessor = new RootMapOutClass("mapSubReport", MapOutput.class);
outMapProcessor.getOutputMapperBiMap().put("map", "mapSubReportput");
needsValuesAndMap.getInputMapperBiMap().put("mapSubReportput", "map");
final ArrayList<Processor> processors = Lists.newArrayList(
RootOutputExecutionTracker, outMapProcessor, needsValuesAndMap);
ProcessorDependencyGraph graph = this.processorDependencyGraphFactory.build(processors,
Collections.<String, Class<?>>emptyMap());
assertContainsProcessors(graph.getRoots(), RootOutputExecutionTracker);
final TestOrderExecution execution = new TestOrderExecution();
Values values = new Values();
values.put(Values.VALUES_KEY, values);
values.put(EXECUTION_TRACKER, execution);
forkJoinPool.invoke(graph.createTask(values));
assertEquals(0, execution.testOrderExecution.size());
TestOrderExecution correctTracker = values.getObject(EXECUTION_TRACKER, TestOrderExecution.class);
assertEquals(correctTracker.testOrderExecution.toString(), 2, correctTracker.testOrderExecution.size());
assertHasOrdering(correctTracker, outMapProcessor, needsValuesAndMap);
}
/**
* This test checks that when there are 2 or more processors that produce the same output there is an exception thrown
*
* @throws Exception
*/
@Test(expected = IllegalArgumentException.class)
public void testBuildDependencyAttachesToLastElementProducingTheValue() throws Exception {
final ArrayList<TestProcessor> processors = Lists.newArrayList(NeedsMap, RootMapOut,
NeedsMapAndWidthOutputsMap, RootTableAndWidthOut);
ProcessorDependencyGraph graph = this.processorDependencyGraphFactory.build(processors,
Collections.<String, Class<?>>emptyMap());
}
/**
* This test checks that when there are 2 or more processors that produce the same output, and this
* output is annotated with @DebugOutput, that no exception is thrown.
*/
@Test
public void testBuildDebugOutput() throws Exception {
final ArrayList<TestProcessor> processors = Lists.newArrayList(RootDebugMapOut1, RootDebugMapOut2);
ProcessorDependencyGraph graph = this.processorDependencyGraphFactory.build(processors,
Collections.<String, Class<?>>emptyMap());
assertContainsProcessors(graph.getRoots(), RootDebugMapOut1, RootDebugMapOut2);
}
/**
* Check that an exception is thrown when one processor provides an output value and
* another processor expects an input value of the same name, but with a different type.
*/
@Test(expected = IllegalArgumentException.class)
public void testBuildValuesWithSameNameAndDifferentType() throws Exception {
final ArrayList<TestProcessor> processors = Lists.newArrayList(NeedsMap, RootMapOut,
NeedsMapList);
ProcessorDependencyGraph graph = this.processorDependencyGraphFactory.build(processors,
Collections.<String, Class<?>>emptyMap());
}
/**
* This test addresses the case where the processors have the same input and output and therefore could be dependent on itself.
*/
@Test
public void testBuildWhenOutputsMapToAllOtherInputs() throws Exception {
final ArrayList<TestProcessor> processors = Lists.newArrayList(NeedsMapProducesMap, NeedsTableProducesTable);
try {
ProcessorDependencyGraph graph = this.processorDependencyGraphFactory.build(processors,
Collections.<String, Class<?>>emptyMap());
assertTrue("A prossessor shouldn't be able to depends on himself", false);
} catch (IllegalArgumentException e) {
// => ok
}
}
/**
* This test checks that all the outputMapper mappings have an associated property in the output object.
*
* @throws Exception
*/
@Test(expected = RuntimeException.class)
public void testExtraOutputMapperMapping() throws Exception {
final ArrayList<TestProcessor> processors = Lists.newArrayList(NeedsMap, RootMapOut,
NeedsMapAndWidthOutputsMap, RootTableAndWidthOut);
ProcessorDependencyGraph graph = this.processorDependencyGraphFactory.build(processors,
Collections.<String, Class<?>>emptyMap());
assertContainsProcessors(graph.getRoots(), RootMapOut, RootTableAndWidthOut);
}
private void assertHasOrdering(TestOrderExecution execution, Processor... processors) {
final ArrayList<Processor> processorList = Lists.newArrayList(processors);
for (int i = 0; i < processorList.size(); i++) {
final Processor p = processorList.get(0);
int actualIndex = execution.testOrderExecution.indexOf(p);
for (Processor p2 : processorList.subList(i + 1, processorList.size())) {
assertTrue(actualIndex < execution.testOrderExecution.indexOf(p2));
}
}
}
private void assertContainsProcessors(List<ProcessorGraphNode> nodes, Processor... processors) {
final List<Object> actualProcessorsList = Lists.transform(nodes, new Function<ProcessorGraphNode, Object>() {
@Nullable
@Override
public Object apply(@Nullable ProcessorGraphNode input) {
return input.getProcessor();
}
});
final String comparison = actualProcessorsList + " expected " + Arrays.asList(processors);
assertEquals(comparison, processors.length, nodes.size());
assertTrue(comparison, actualProcessorsList.containsAll(Arrays.asList(processors)));
}
private ProcessorGraphNode getNodeForProcessor(
Collection<ProcessorGraphNode> roots, TestProcessor processor) {
for (ProcessorGraphNode node : roots) {
if (node.getProcessor() == processor) {
return node;
}
}
return null;
}
static class TrackerContainer {
@HasDefaultValue
public TestOrderExecution executionOrder;
}
private abstract static class TestProcessor<In extends TrackerContainer, Out>
extends AbstractProcessor<In, Out> {
public String name;
protected TestProcessor(String name, Class<Out> outputType) {
super(outputType);
this.name = name;
}
@Override
protected void extraValidation(List<Throwable> validationErrors, final Configuration configuration) {
// no checks
}
@Override
public final Out execute(In values, ExecutionContext context) throws Exception {
TestOrderExecution tracker = values.executionOrder;
if (tracker != null) {
tracker.doExecute(this);
}
return getExtras();
}
protected abstract Out getExtras();
@SuppressWarnings("unchecked")
@Override
public In createInputParameter() {
return (In) new TrackerContainer();
}
@Override
public String toString() {
return name;
}
}
private static class RootNoOutputClass extends TestProcessor<TrackerContainer, Void> {
protected RootNoOutputClass(String name, Class<Void> outputType) {
super(name, outputType);
}
@Override
protected Void getExtras() {
return null;
}
};
private static TestProcessor RootNoOutput = new RootNoOutputClass("RootNoOutput", Void.class);
private static class MapOutput {
public String map = "map";
}
private static class RootMapOutClass extends TestProcessor<TrackerContainer, MapOutput> {
protected RootMapOutClass(String name, Class<MapOutput> outputType) {
super(name, outputType);
}
@Override
protected MapOutput getExtras() {
return new MapOutput();
}
}
private static TestProcessor RootMapOut = new RootMapOutClass("RootMapOut", MapOutput.class);
private static class DebugMapOutput {
@InternalValue
public String map = "map";
}
private static class RootDebugMapOutClass extends TestProcessor<TrackerContainer, DebugMapOutput> {
protected RootDebugMapOutClass(String name, Class<DebugMapOutput> outputType) {
super(name, outputType);
}
@Override
protected DebugMapOutput getExtras() {
return new DebugMapOutput();
}
}
private static TestProcessor RootDebugMapOut1 = new RootDebugMapOutClass("RootDebugMapOut1", DebugMapOutput.class);
private static TestProcessor RootDebugMapOut2 = new RootDebugMapOutClass("RootDebugMapOut2", DebugMapOutput.class);
private static class TableAndWidth {
public String table = "tableData";
public int width = 1;
}
private static TestProcessor RootTableAndWidthOut = new TestProcessor<TrackerContainer, TableAndWidth>("RootTableAndWidthOut",
TableAndWidth.class) {
@Override
protected TableAndWidth getExtras() {
return new TableAndWidth();
}
};
private static class MapInput extends TrackerContainer {
public String map = "map";
}
private static class MapInputOutput extends TrackerContainer {
@InputOutputValue
public String map = "map";
}
private static class MapAndWidth extends MapInput {
public int width;
}
private static TestProcessor NeedsMapAndWidthOutputsMap = new TestProcessor<MapAndWidth, MapOutput>("NeedsMapAndWidthOutputsMap",
MapOutput.class) {
@Override
public MapAndWidth createInputParameter() {
return new MapAndWidth();
}
@Override
protected MapOutput getExtras() {
return new MapOutput();
}
};
private static class NeedsMapClass extends TestProcessor<MapInput, Void> {
protected NeedsMapClass(String name, Class<Void> outputType) {
super(name, outputType);
}
@Override
protected Void getExtras() {
return null;
}
@Override
public MapInput createInputParameter() {
return new MapInput();
}
}
private static TestProcessor NeedsMap = new NeedsMapClass("NeedsMap", Void.class);
private static class MapListInput extends TrackerContainer {
public List<String> map = Lists.newArrayList();
}
private static class NeedsMapListClass extends TestProcessor<MapListInput, Void> {
protected NeedsMapListClass(String name, Class<Void> outputType) {
super(name, outputType);
}
@Override
protected Void getExtras() {
return null;
}
@Override
public MapListInput createInputParameter() {
return new MapListInput();
}
}
private static TestProcessor NeedsMapList = new NeedsMapListClass("NeedsMapList", Void.class);
private static class StyleNeedsMapClass extends TestProcessor<MapInputOutput, Void> {
protected StyleNeedsMapClass(String name, Class<Void> outputType) {
super(name, outputType);
}
@Override
protected Void getExtras() {
return null;
}
@Override
public MapInputOutput createInputParameter() {
return new MapInputOutput();
}
}
private static TestProcessor StyleNeedsMap = new StyleNeedsMapClass("StyleNeedsMap", Void.class);
private static class OverviewMapInput extends TrackerContainer {
public String overviewMap = "ov-map";
public String map = "map";
}
private static class NeedsOverviewMapAndMapClass extends TestProcessor<OverviewMapInput, Void> {
protected NeedsOverviewMapAndMapClass(String name, Class<Void> outputType) {
super(name, outputType);
}
@Override
protected Void getExtras() {
return null;
}
@Override
public OverviewMapInput createInputParameter() {
return new OverviewMapInput();
}
}
private static TestProcessor NeedsOverviewMapAndMap = new NeedsOverviewMapAndMapClass("NeedsOverviewMapAndMap", Void.class);
static class TableInput extends TrackerContainer {
public String table;
}
private static TestProcessor NeedsTable = new TestProcessor<TableInput, Void>("NeedsTable", Void.class) {
@Override
protected Void getExtras() {
return null;
}
@Override
public TableInput createInputParameter() {
return new TableInput();
}
};
private static Processor RootOutputExecutionTracker = new AbstractProcessor<Void, TrackerContainer>(TrackerContainer.class) {
@Override
public Void createInputParameter() {
return null;
}
@Nullable
@Override
public TrackerContainer execute(Void values, final ExecutionContext context) throws Exception {
assertNull(values);
final TrackerContainer trackerContainer = new TrackerContainer();
trackerContainer.executionOrder = new TestOrderExecution();
return trackerContainer;
}
@Override
protected void extraValidation(List<Throwable> validationErrors, final Configuration configuration) {
// no checks
}
@Override
public String toString() {
return "RootOutputExecutionTracker";
}
};
private static TestProcessor NeedsMapProducesMap = new TestProcessor<MapInput, MapOutput>("NeedsMapProducesMap",
MapOutput.class) {
@Override
public MapInput createInputParameter() {
return new MapInput();
}
@Override
protected MapOutput getExtras() {
return new MapOutput();
}
};
private static class TableOutput {
public String table = "table";
}
private static TestProcessor NeedsTableProducesTable = new TestProcessor<TableInput, TableOutput>("NeedsTableProducesTable",
TableOutput.class) {
@Override
protected TableOutput getExtras() {
return new TableOutput();
}
@Override
public TableInput createInputParameter() {
return new TableInput();
}
};
private static class MapValuesInput extends MapInput {
public Values values;
}
private static class NeedsValuesAndMap extends TestProcessor<MapValuesInput, Void> {
protected NeedsValuesAndMap() {
super("NeedsTableProducesTable", Void.class);
}
@Override
protected Void getExtras() {
return null;
}
@Override
public MapValuesInput createInputParameter() {
return new MapValuesInput();
}
};
}