package com.google.common.base;
import java.util.Arrays;
import java.util.List;
import java.lang.annotation.Annotation;
/**
* @author ymeymann@gmail.com
*/
public class FunctionChain<A,B> implements Function<A,B> {
final Function<A,B> function;
final boolean isNullSupported;
private FunctionChain(Function<A,B> f){
function = f;
isNullSupported = isNullSupported(function);
}
public <C> FunctionChain<A,C> function(final Function<? super B,C> f) {
if (isNullSupported(f)) {
return new FunctionChain<A,C>(new Function<A,C>(){
public C apply(@Nullable A from) {
return f.apply(FunctionChain.this.apply(from));
}
});
} else {
return new FunctionChain<A,C>(new Function<A,C>(){
public C apply(A from) {
return f.apply(FunctionChain.this.apply(from));
}
});
}
}
public Predicate<A> condition(final Predicate<? super B> p) {
return new Predicate<A>() {
public boolean apply(A a) {
return p.apply(FunctionChain.this.apply(a));
}
};
}
public B apply(A from) {
return function.apply(from);
}
/**
* With this method, instead of FunctionChain.<Foo>self() one can write self(Foo.class)
*/
public static <S> FunctionChain<S,S> self(Class<S> c) {
return self(true);
}
public static <S> FunctionChain<S,S> self(Class<S> c, boolean nullSafe) {
return self(nullSafe);
}
public static <S> FunctionChain<S,S> self() {
return self(true);
}
public static <S> FunctionChain<S,S> self(boolean nullSafe) {
return nullSafe ?
new NullSafeFunctionChain<S,S>(Functions.<S>identity()) :
new FunctionChain<S,S>(Functions.<S>identity());
}
public static class NullSafeFunctionChain<A,B> extends FunctionChain<A,B> {
private NullSafeFunctionChain(Function<A,B> f){
super(f);
}
@Override
public <C> NullSafeFunctionChain<A,C> function(final Function<? super B,C> f) {
if (isNullSupported(f)) {
return new NullSafeFunctionChain<A,C>(new Function<A,C>(){
public C apply(@Nullable A from) {
B res = NullSafeFunctionChain.this.apply(from);
return f.apply(res);
}
});
} else {
return new NullSafeFunctionChain<A,C>(new Function<A,C>(){
public C apply(A from) {
B res = NullSafeFunctionChain.this.apply(from);
return res == null ? null : f.apply(res);
}
});
}
}
@Override
public B apply(@Nullable A from) {
if (from == null && !isNullSupported) return null;
return function.apply(from);
}
}
public static boolean isNullSupported(Function<?,?> function) {
try {
List<Annotation> l = Arrays.asList(function.getClass().getMethod("apply", Object.class).getParameterAnnotations()[0]);
for (Annotation a: l) {
if (a.annotationType().equals(Nullable.class)) return true;
}
} catch (Exception e) {
e.printStackTrace(); //it should never happen
}
return false;
}
}