/* FunctionChain v.2 */
package com.google.common.base;
import static com.google.common.collect.Iterables.addAll;
import static com.google.common.collect.ObjectArrays.newArray;
import java.lang.annotation.Annotation;
import java.lang.reflect.*;
import static java.util.Arrays.asList;
import java.util.*;
/**
* @author ymeymann
* @since Nov 14, 2007 11:22:00 AM
*/
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 <A,B> FunctionChain<A,B> self(Function<A,B> f) {
return self(f, true);
}
public static <A,B> FunctionChain<A,B> self(Function<A,B> f, boolean nullSafe) {
return nullSafe? new NullSafe<A,B>(f) : new FunctionChain<A,B>(f);
}
public static <S> FunctionChain<S,S> self(boolean nullSafe) {
return nullSafe ?
new NullSafe<S,S>(Functions.<S>identity()) :
new FunctionChain<S,S>(Functions.<S>identity());
}
public static boolean isNullSupported(Function<?,?> function) {
//TODO cache this per function class
Type functionInt = findGenericFunctionAncestor(function);
Class[] genericParams = getGenericParameterClasses(functionInt, 1);
if (genericParams.length == 0) genericParams = new Class[] {Object.class};
Method applyMethod = null;
try {
applyMethod = function.getClass().getMethod("apply", genericParams[0]);
} catch (NoSuchMethodException e) {
e.printStackTrace();
}
List<Annotation> l = asList(applyMethod.getParameterAnnotations()[0]);
for (Annotation a: l) {
if (a.annotationType().equals(Nullable.class)) return true;
}
return false;
}
public static Type findGenericFunctionAncestor(Function<?, ?> function) {
Class<?> curClass = function.getClass();
Set<Type> allInt = new LinkedHashSet<Type>();
while (curClass != Object.class) {
Type[] ints = curClass.getGenericInterfaces();
if (ints != null) {
addAll(allInt, asList(ints));
}
curClass = curClass.getSuperclass();
}
Type functionInt = null;
for (Type t: allInt) {
if ((t instanceof Class && t == Function.class) ||
(t instanceof ParameterizedType && ((ParameterizedType)t).getRawType() == Function.class)) {
functionInt = t;
break;
}
}
return functionInt == null ? Function.class : functionInt;
}
public static Class[] getGenericParameterClasses(Type superType) {
return getGenericParameterClasses(superType, Integer.MAX_VALUE);
}
public static Class[] getGenericParameterClasses(Type superType, int parameterCount) {
List<Class<?>> res = new LinkedList<Class<?>>();
if (!(superType instanceof Class)) {
ParameterizedType paramSuperclass = (ParameterizedType) superType;
Type[] args = paramSuperclass.getActualTypeArguments();
int i = 0;
for (Type arg: args) {
if (i >= parameterCount) break;
if (arg instanceof Class<?>) {
res.add((Class<?>) arg);
} else if (arg instanceof ParameterizedType) {
res.add((Class<?>) ((ParameterizedType) arg).getRawType());
} else if (arg instanceof GenericArrayType) {
Type elemType = ((GenericArrayType) arg).getGenericComponentType();
//TODO support for multi-dimensional arrays
Class<?> elemClass = Object.class;
if (elemType instanceof Class<?>)
elemClass = (Class<?>) elemType;
else if (elemType instanceof ParameterizedType)
elemClass = (Class<?>)((ParameterizedType)elemType).getRawType();
res.add(Array.newInstance(elemClass ,0).getClass());
} else {
res.add(Object.class);
}
i++;
} //for
}
return res.toArray(newArray(Class.class, res.size()));
}
public static class NullSafe<A,B> extends FunctionChain<A,B> {
private NullSafe(Function<A,B> f){
super(f);
}
@Override
public <C> NullSafe<A,C> function(final Function<? super B,C> f) {
if (isNullSupported(f)) {
return new NullSafe<A,C>(new Function<A,C>(){
public C apply(@Nullable A from) {
B res = NullSafe.this.apply(from);
return f.apply(res);
}
});
} else {
return new NullSafe<A,C>(new Function<A,C>(){
public C apply(A from) {
B res = NullSafe.this.apply(from);
return res == null ? null : f.apply(res);
}
});
}
}
@Override
public Predicate<A> condition(final Predicate<? super B> p) {
return new Predicate<A>() {
public boolean apply(A a) {
B res = NullSafe.this.apply(a);
return res!= null && p.apply(res);
}
};
}
@Override
public B apply(@Nullable A from) {
if (from == null && !isNullSupported) return null;
return function.apply(from);
}
}
}