/*
* Copyright (c) 2006-2011 Nuxeo SA (http://nuxeo.com/) and others.
*
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* bstefanescu
*/
package org.eclipse.ecr.automation.core.impl;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.Iterator;
import java.util.Map;
import org.eclipse.ecr.automation.OperationContext;
import org.eclipse.ecr.automation.OperationType;
import org.eclipse.ecr.automation.OutputCollector;
import org.eclipse.ecr.automation.core.annotations.OperationMethod;
/**
* A method proxy which accept as input only iterable inputs. At invocation time
* it iterates over the input elements and invoke the real method using the
* current input element as the input.
* <p>
* The result is collected into a {@link OutputCollector} as specified by the
* {@link OperationMethod} annotation.
* <p>
* This proxy is used (instead of the default {@link InvokableMethod}) when an
* operation is defining an output collector in the {@link OperationMethod}
* annotation so that iterable inputs are automatically handled by the chain
* executor.
* <p>
* This specialized implementation is declaring the same consume type as its
* non-iterable counterpart. But at runtime it consumes any Iterable of the
* cosume type.
* <p>
* To correctly generate the operation documentation the
* {@link OperationTypeImpl} is checking if the method is iterable or not
* through {@link #isIterable()} to declare the correct consume type.
*
* @author <a href="mailto:bs@nuxeo.com">Bogdan Stefanescu</a>
*
*/
public class InvokableIteratorMethod extends InvokableMethod {
@SuppressWarnings("rawtypes")
protected Class<? extends OutputCollector> collector;
public InvokableIteratorMethod(OperationType op, Method method,
OperationMethod anno) {
super(op, method, anno);
collector = anno.collector();
if (collector == OutputCollector.class) {
throw new IllegalArgumentException("Not an iterable method");
}
// check the collector match the method signature - to early detect invalid
// operation definitions.
if (consume == Void.TYPE) {
throw new IllegalArgumentException(
"An iterable method must have an argument");
}
Type[] ctypes = IterableInputHelper.findCollectorTypes(collector);
if (!((Class<?>)ctypes[0]).isAssignableFrom(produce)) {
throw new IllegalArgumentException(
"The collector used on "+method+" doesn't match the method return type");
}
// must modify the produced type to fit the real produced type.
try {
produce = (Class<?>)ctypes[1];
} catch (Exception e) {
throw new IllegalStateException("Invalid output collector: "
+ collector + ". No getOutput method found.");
}
// the consumed type is not used in chain compilation so we let it as is
// for now.
}
@Override
public boolean isIterable() {
return true;
}
@Override
public int inputMatch(Class<?> in) {
Class<?> iterableIn = IterableInputHelper.getIterableType(in);
if (iterableIn != null) {
return super.inputMatch(iterableIn);
}
return 0;
}
@SuppressWarnings({ "rawtypes", "unchecked" })
@Override
protected Object doInvoke(OperationContext ctx, Map<String, Object> args,
Object input) throws Exception {
if (input instanceof Iterable == false) {
throw new IllegalStateException(
"An iterable method was called in a non iterable context");
}
OutputCollector list = collector.newInstance();
Iterable<?> iterable = (Iterable<?>) input;
Iterator<?> it = iterable.iterator();
while (it.hasNext()) {
Object in = it.next();
// update context to use as input the current entry
ctx.setInput(in);
list.collect(ctx, super.doInvoke(ctx, args, in));
}
return list.getOutput();
}
}