I\'ve been struggling with the following problem. I have a series of function objects, each with it\'s own input and output types defined via generic type arguments in java. I w
Here's a way to do it. The run method is not typesafe, but given that the only way to append a pipe is to do it in a type-safe way, the whole chain is type-safe.
public class Chain<S, T> {
private List<Pipe<?, ?>> pipes;
private Chain() {
}
public static <K, L> Chain<K, L> start(Pipe<K, L> pipe) {
Chain<K, L> chain = new Chain<K, L>();
chain.pipes = Collections.<Pipe<?, ?>>singletonList(pipe);;
return chain;
}
public <V> Chain<S, V> append(Pipe<T, V> pipe) {
Chain<S, V> chain = new Chain<S, V>();
chain.pipes = new ArrayList<Pipe<?, ?>>(pipes);
chain.pipes.add(pipe);
return chain;
}
@SuppressWarnings({ "rawtypes", "unchecked" })
public T run(S s) {
Object source = s;
Object target = null;
for (Pipe p : pipes) {
target = p.transform(source);
source = target;
}
return (T) target;
}
public static void main(String[] args) {
Pipe<String, Integer> pipe1 = new Pipe<String, Integer>() {
@Override
public Integer transform(String s) {
return Integer.valueOf(s);
}
};
Pipe<Integer, Long> pipe2 = new Pipe<Integer, Long>() {
@Override
public Long transform(Integer s) {
return s.longValue();
}
};
Pipe<Long, BigInteger> pipe3 = new Pipe<Long, BigInteger>() {
@Override
public BigInteger transform(Long s) {
return new BigInteger(s.toString());
}
};
Chain<String, BigInteger> chain = Chain.start(pipe1).append(pipe2).append(pipe3);
BigInteger result = chain.run("12");
System.out.println(result);
}
}
Here's another way to do it: this way allows for a transformation step to result in a list. For example, a transformation could split a string into multiple substrings. Moreover, it allows for common exception handling code if transforming any of the values produces an exception. It also allows the use of an empty List as a return value instead of an ambiguous null value that has to be tested for to avoid NullPointerException. The main problem with this one is that it does each transformation step in its entirety before moving to the next step, which may not be memory efficient.
public class Chain<IN, MEDIAL, OUT> {
private final Chain<IN, ?, MEDIAL> head;
private final Transformer<MEDIAL, OUT> tail;
public static <I, O> Chain<I, I, O> makeHead(@Nonnull Transformer<I, O> tail) {
return new Chain<>(null, tail);
}
public static <I, M, O> Chain<I, M, O> append(@Nonnull Chain<I, ?, M> head, @Nonnull Transformer<M, O> tail) {
return new Chain<>(head, tail);
}
private Chain(@Nullable Chain<IN, ?, MEDIAL> head, @Nonnull Transformer<MEDIAL, OUT> tail) {
this.head = head;
this.tail = tail;
}
public List<OUT> run(List<IN> input) {
List<OUT> allResults = new ArrayList<>();
List<MEDIAL> headResult;
if (head == null) {
headResult = (List<MEDIAL>) input;
} else {
headResult = head.run(input);
}
for (MEDIAL in : headResult) {
// try/catch here
allResults.addAll(tail.transform(in));
}
return allResults;
}
public static void main(String[] args) {
Transformer<String, Integer> pipe1 = new Transformer<String, Integer>() {
@Override
public List<Integer> transform(String s) {
return Collections.singletonList(Integer.valueOf(s) * 3);
}
};
Transformer<Integer, Long> pipe2 = new Transformer<Integer, Long>() {
@Override
public List<Long> transform(Integer s) {
return Collections.singletonList(s.longValue() * 5);
}
};
Transformer<Long, BigInteger> pipe3 = new Transformer<Long, BigInteger>() {
@Override
public List<BigInteger> transform(Long s) {
return Collections.singletonList(new BigInteger(String.valueOf(s * 7)));
}
};
Chain<String, ?, Integer> chain1 = Chain.makeHead(pipe1);
Chain<String, Integer, Long> chain2 = Chain.append(chain1, pipe2);
Chain<String, Long, BigInteger> chain3 = Chain.append(chain2, pipe3);
List<BigInteger> result = chain3.run(Collections.singletonList("1"));
System.out.println(result);
}
}