package nl.ipo.cds.attributemapping.executer;
import java.lang.reflect.Type;
import java.util.AbstractList;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import nl.ipo.cds.attributemapping.AttributeMapperUtils;
import nl.ipo.cds.attributemapping.MapperContext;
import nl.ipo.cds.attributemapping.MappingDestination;
import nl.ipo.cds.attributemapping.MappingSource;
import nl.ipo.cds.attributemapping.operations.Operation;
import nl.ipo.cds.attributemapping.operations.OperationInput;
import nl.ipo.cds.attributemapping.operations.OperationInputType;
import nl.ipo.cds.attributemapping.operations.OperationType;
/**
* Executer for attribute mapping operations. Creates an execution plan to efficiently invoke the same
* tree of mapping operations multiple times.
*
* An executer is specific to a given operation tree and mapper context. Furthermore it is not threadsafe,
* nor is the execute method reentrant.
*/
public class Executer implements AutoCloseable {
private final Operation rootOperation;
private final Object[] registers;
private final ExecutionStep[] executionPlan;
private final MapperContext context;
public Executer (final Operation rootOperation, final MapperContext context) throws MappingValidationException, OperationExecutionException {
this.rootOperation = rootOperation;
this.context = context;
final List<Operation> operations = flattenOperationTree (rootOperation);
validateOperations (operations);
this.registers = new Object[operations.size ()];
this.executionPlan = buildExecutionPlan (operations);
// Invoke the before methods:
for (final ExecutionStep step: executionPlan) {
step.executer.before ();
}
}
public Operation getRootOperation () {
return rootOperation;
}
public MapperContext getMapperContext () {
return context;
}
public void execute (final MappingSource source, final MappingDestination destination) throws OperationExecutionException {
for (final ExecutionStep step: executionPlan) {
registers[step.outputRegister] = step.executer.execute (source, destination, step.inputRegisters);
}
}
@Override
public void close () throws OperationExecutionException {
// Invoke the "after" methods:
for (final ExecutionStep step: executionPlan) {
step.executer.after ();
}
}
private void validateOperations (final List<Operation> operations) throws MappingValidationException {
for (final Operation operation: operations) {
validateOperation (operation);
}
}
private void validateOperation (final Operation operation) throws MappingValidationException {
final OperationType operationType = operation.getOperationType ();
final List<OperationInputType> inputTypes = operationType.getInputs ();
final List<OperationInput> inputs = operation.getInputs ();
if (inputTypes.size () > 0 && inputTypes.get (inputTypes.size () - 1).isVariableInputCount ()) {
// Variable arguments:
if (inputs.size () < inputTypes.size () - 1) {
throw new MappingValidationException (String.format ("Invalid number of inputs for operation %s, expected at least %d (variable arguments)", operationType.getName (), inputTypes.size () - 1));
}
} else {
if (inputs.size () != inputTypes.size ()) {
throw new MappingValidationException (String.format ("Invalid number of inputs for operation %s, expected at %d", operationType.getName (), inputTypes.size ()));
}
}
for (int i = 0; i < inputs.size (); ++ i) {
final OperationInput input = inputs.get (i);
final OperationInputType inputType = inputTypes.get (Math.min (inputTypes.size () - 1, i));
validateInput (operation, input, inputType);
}
}
private void validateInput (final Operation operation, final OperationInput input, final OperationInputType inputType) throws MappingValidationException {
final Type effectiveType = input.getOperation ().getOperationType ().getReturnType ();
final Type expectedType = inputType.getInputType ();
if (!AttributeMapperUtils.areTypesAssignable (effectiveType, expectedType)) {
throw new MappingValidationException (String.format (
"Input %s of operation %s has an incompatible type %s, expected %s (output of operation %s)",
inputType.getName (),
operation.getOperationType ().getName (),
effectiveType,
expectedType,
input.getOperation ().getOperationType ().getName ()
));
}
}
/**
* Creates an execution plan for the given list of operations. The execution plan
* has an "execution step" for each operation. An execution step in turn has a reference
* to an executor specific to the operation type and a reference to the registers that
* serve as inputs.
*
* @param operations
* @return An array of execution steps that execute the attribute mapping.
* @throws MappingValidationException
*/
private ExecutionStep[] buildExecutionPlan (final List<Operation> operations) throws MappingValidationException {
final Map<Operation, Integer> operationIndex = new HashMap<Operation, Integer> ();
final ExecutionStep[] executionSteps = new ExecutionStep[operations.size ()];
for (int i = 0; i < operations.size (); ++ i) {
final Operation operation = operations.get (i);
final List<OperationInput> inputs = operation.getInputs ();
// Store the index of this operation, later operations may use it as an input:
operationIndex.put (operation, i);
// List the indices of the input registers:
final int[] inputRegisters = new int[inputs.size ()];
for (int j = 0; j < inputs.size (); ++ j) {
inputRegisters[j] = operationIndex.get (inputs.get (j).getOperation ());
}
// Create an executer for this operation:
final OperationExecuter executer = operation.getOperationType ().createExecuter (operation.getOperationProperties (), context);
if (executer == null) {
throw new MappingValidationException (String.format ("Operation type %s did not return an executer.", operation.getOperationType ().getName ()));
}
executionSteps[i] = new ExecutionStep (
operation,
executer,
i,
new RegisterList (inputRegisters)
);
}
return executionSteps;
}
/**
* Flattens the operation tree into an execution order that processes inputs of each
* operation first.
*
* @param rootOperation
* @return The list of operations, in depth-first order.
*/
private List<Operation> flattenOperationTree (final Operation rootOperation) {
final List<Operation> operations = new ArrayList<Operation> ();
final LinkedList<Operation> fringe = new LinkedList<Operation> ();
fringe.addLast (rootOperation);
while (!fringe.isEmpty ()) {
final Operation operation = fringe.pollFirst ();
operations.add (operation);
for (final OperationInput input: operation.getInputs ()) {
fringe.addLast (input.getOperation ());
}
}
Collections.reverse (operations);
return operations;
}
@Override
public String toString () {
final StringBuilder builder = new StringBuilder ();
for (final ExecutionStep s: executionPlan) {
if (builder.length () > 0) {
builder.append ("\n");
}
builder.append (s.toString ());
}
return builder.toString ();
}
private static class ExecutionStep {
public final Operation operation;
public final OperationExecuter executer;
public final int outputRegister;
public final RegisterList inputRegisters;
public ExecutionStep (final Operation operation, final OperationExecuter executer, final int outputRegister, final RegisterList inputRegisters) {
this.operation = operation;
this.executer = executer;
this.outputRegister = outputRegister;
this.inputRegisters = inputRegisters;
}
@Override
public String toString () {
final StringBuilder b = new StringBuilder ();
for (final int i: inputRegisters.getOffsets ()) {
if (b.length() > 0) {
b.append (", ");
}
b.append (String.format ("%d", i));
}
return String.format ("reg.%d = invoke %s(%s) (props: %s)", outputRegister, operation.getOperationType ().getName (), b.toString (), operation.getOperationProperties ());
}
}
private class RegisterList extends AbstractList<Object> {
private final int[] indices;
public RegisterList (final int[] indices) {
this.indices = indices;
}
@Override
public Object get (final int index) {
return registers[indices[index]];
}
@Override
public int size () {
return indices.length;
}
public int[] getOffsets () {
return indices;
}
}
}