/*
* Copyright (C) 2012 Jason Gedge <http://www.gedge.ca>
*
* This file is part of the OpGraph project.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
/**
*
*/
package ca.gedge.opgraph.nodes.iteration;
import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
import ca.gedge.opgraph.InputField;
import ca.gedge.opgraph.OpContext;
import ca.gedge.opgraph.OpGraph;
import ca.gedge.opgraph.OpNode;
import ca.gedge.opgraph.OpNodeInfo;
import ca.gedge.opgraph.OutputField;
import ca.gedge.opgraph.Processor;
import ca.gedge.opgraph.exceptions.ProcessingException;
import ca.gedge.opgraph.nodes.general.MacroNode;
import ca.gedge.opgraph.validators.CollectionValidator;
/**
* A special macro node that loops over {@link List} inputs. When a field is
* published from an internal node, the published field will accept any
* {@link List} that contains elements of types accepted by the internal field.
*/
@OpNodeInfo(
name="For Each",
description="A macro operation in which the macro is executed based on collections given as input.",
category="Iteration"
)
public class ForEachNode extends MacroNode {
/** {@link OpContext} key for the current iteration */
public static final String CURRENT_ITERATION_KEY = "currentIteration";
/** {@link OpContext} key for the max number of iterations */
public static final String MAX_ITERATIONS_KEY = "maxIterations";
/**
* Constructs a new macro with no source file and a default graph.
*/
public ForEachNode() {
super(null, null);
}
/**
* Constructs a new macro with no source file and a specified graph.
*
* @param graph the graph
*
* @throws NullPointerException if the graph is <code>null</code>
*/
public ForEachNode(OpGraph graph) {
super(null, graph);
}
/**
* Constructs a macro node from the given source file and DAG.
*
* @param source the source file (see {@link #getSource()}
* @param graph the graph
*/
public ForEachNode(File source, OpGraph graph) {
super(source, graph);
}
/**
* Constructs a context mapping for this macro's published inputs. Inputs contained
* in the given context will be mapped to their appropriate node/input field in the
* internal graph this macro is using. The returned mapping will have the given context
* as the global context (i.e., the context mapped to by the <code>null</code> key).
*
* @param context the macro's local context
*/
private void mapInputs(OpContext context, int iteration) {
// Put in information about the iteration
context.put(CURRENT_ITERATION_KEY, iteration);
// Child contexts
for(PublishedInput publishedInput : publishedInputs) {
final OpContext local = context.getChildContext(publishedInput.destinationNode);
final List<?> data = (List<?>)context.get(publishedInput);
final Object value = (iteration < data.size() ? data.get(iteration) : null);
local.put(publishedInput.nodeInputField, value);
}
}
/**
* Maps published outputs from a given context mapping to a given context.
*
* @param contextsMap the context mapping to map outputs from
* @param context the context to map outputs to
*/
private void mapOutputs(OpContext context, int iteration) {
// Grab mapped outputs and put them in our context
for(PublishedOutput publishedOutput : publishedOutputs) {
final OpContext sourceContext = context.findChildContext(publishedOutput.sourceNode);
if(sourceContext != null) {
final Object result = sourceContext.get(publishedOutput.nodeOutputField);
if(context.containsKey(publishedOutput)) {
final ArrayList<Object> objects = new ArrayList<Object>((ArrayList<?>)context.get(publishedOutput));
objects.add(result);
context.put(publishedOutput, objects);
} else {
final ArrayList<Object> objects = new ArrayList<Object>();
objects.add(result);
context.put(publishedOutput, objects);
}
}
}
}
//
// Overrides
//
@Override
public InputField publish(String key, OpNode destination, InputField field) {
final InputField published = super.publish(key, destination, field);
published.setValidator(new CollectionValidator(published.getValidator()));
return published;
}
@Override
public OutputField publish(String key, OpNode source, OutputField field) {
final OutputField published = super.publish(key, source, field);
published.setOutputType(Collection.class);
return published;
}
@Override
public void operate(OpContext context) throws ProcessingException {
// First, find the biggest list we have
int maxIterations = 0;
for(PublishedInput field : getPublishedInputs()) {
final Collection<?> data = (Collection<?>)context.get(field);
maxIterations = Math.max(maxIterations, data.size());
}
// Process
if(graph != null) {
final Processor processor = new Processor(graph);
context.put(MAX_ITERATIONS_KEY, maxIterations);
for(int iteration = 0; iteration < maxIterations; ++iteration) {
processor.reset(context);
// The reset call above could clear out the context, so map after
mapInputs(context, iteration);
// Now run the graph
processor.stepAll();
if(processor.getError() != null)
throw processor.getError();
// Map the published outputs from the child nodes back into context
mapOutputs(context, iteration);
}
}
}
//
// CustomProcessing
//
@Override
public CustomProcessor getCustomProcessor() {
return new CustomProcessor() {
private OpContext context;
private OpNode nextNode;
private Iterator<OpNode> nodeIter;
private int iteration = 0;
private int maxIterations = 0;
@Override
public void remove() {
throw new UnsupportedOperationException("remove not supported");
}
@Override
public OpNode next() {
if(!hasNext())
throw new NoSuchElementException();
final OpNode node = nextNode;
nextNode = null;
return node;
}
@Override
public boolean hasNext() {
if(nextNode != null)
return true;
if(!nodeIter.hasNext() && iteration < maxIterations) {
mapOutputs(context, iteration);
++iteration;
if(iteration < maxIterations) {
nodeIter = graph.getVertices().iterator();
mapInputs(context, iteration);
}
}
if(nodeIter.hasNext())
nextNode = nodeIter.next();
return (nextNode != null);
}
@Override
public void initialize(OpContext context) {
this.maxIterations = 0;
this.context = context;
this.nodeIter = graph.getVertices().iterator();
// First, find the biggest list we have
for(PublishedInput field : getPublishedInputs()) {
if(context.containsKey(field)) {
final Collection<?> data = (Collection<?>)context.get(field);
this.maxIterations = Math.max(this.maxIterations, data.size());
}
}
context.put(MAX_ITERATIONS_KEY, maxIterations);
mapInputs(context, 0);
}
@Override
public void terminate(OpContext context) {}
};
}
}