package de.take_weiland.mods.commons.internal.prop;
import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.Iterables;
import com.google.common.reflect.TypeToken;
import de.take_weiland.mods.commons.SerializationMethod;
import de.take_weiland.mods.commons.serialize.Property;
import javax.annotation.Nullable;
import java.lang.annotation.Annotation;
import java.lang.invoke.MethodHandle;
import java.lang.reflect.*;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
/**
* @author diesieben07
*/
public abstract class AbstractProperty<T, MEM extends AccessibleObject & Member & AnnotatedElement> implements Property<T, MEM> {
public static <T> Property<T, ?> newProperty(Member member) {
if (member instanceof Field) {
return new FieldProperty<>((Field) member);
} else if (member instanceof Method) {
return new MethodProperty<>((Method) member);
} else {
throw new IllegalArgumentException();
}
}
public static Function<Member, Property<?, ?>> getPropertyFunction() {
return new Function<Member, Property<?, ?>>() {
@Override
public Property<?, ?> apply(Member input) {
return newProperty(input);
}
};
}
public static List<Property<?, ?>> allProperties(Class<?> clazz, Class<? extends Annotation> annotation) {
return allPropertiesLazy(clazz, annotation).toList();
}
public static FluentIterable<Property<?, ?>> allPropertiesLazy(Class<?> clazz, Class<? extends Annotation> annotation) {
return getPropertySourceClasses(clazz)
.transformAndConcat(getDirectProperties(isAnnotatedWith(annotation)));
}
private static Function<Class<?>, Iterable<Property<?, ?>>> getDirectProperties(final Predicate<Member> filter) {
return new Function<Class<?>, Iterable<Property<?, ?>>>() {
@Nullable
@Override
public Iterable<Property<?, ?>> apply(Class<?> input) {
return getDirectProperties(input, filter);
}
};
}
private static Iterable<Property<?, ?>> getDirectProperties(Class<?> clazz, Predicate<Member> filter) {
Iterable<Member> methods = Arrays.<Member>asList(clazz.getDeclaredMethods());
Iterable<Member> fields = Arrays.<Member>asList(clazz.getDeclaredFields());
return FluentIterable.from(Iterables.concat(methods, fields))
.filter(filter)
.transform(getPropertyFunction());
}
private static Predicate<Member> isAnnotatedWith(final Class<? extends Annotation> annotation) {
return new Predicate<Member>() {
@Override
public boolean apply(Member input) {
return ((AnnotatedElement) input).isAnnotationPresent(annotation);
}
};
}
private static FluentIterable<Class<?>> getPropertySourceClasses(Class<?> clazz) {
FluentIterable<Class<?>> newIfaces = FluentIterable.from(Arrays.asList(clazz.getInterfaces()))
.filter(isNewInterface(clazz));
return FluentIterable.from(
Iterables.concat(Collections.singleton(clazz), newIfaces)
);
}
private static Predicate<Class<?>> isNewInterface(final Class<?> currentClass) {
return new Predicate<Class<?>>() {
@Override
public boolean apply(Class<?> input) {
return !input.isAssignableFrom(currentClass.getSuperclass());
}
};
}
final MEM member;
private final SerializationMethod.Method desiredMethod;
private TypeToken<T> genericType;
private MethodHandle getter;
private MethodHandle setter;
AbstractProperty(MEM member) {
this.member = member;
SerializationMethod annotation = member.getAnnotation(SerializationMethod.class);
desiredMethod = annotation == null ? SerializationMethod.Method.DEFAULT : annotation.value();
}
@Override
public final MEM getMember() {
return member;
}
@Override
public final SerializationMethod.Method getDesiredMethod() {
return desiredMethod;
}
@SuppressWarnings("unchecked")
@Override
public final TypeToken<T> getType() {
return genericType == null ? (genericType = (TypeToken<T>) resolveType()) : genericType;
}
@Override
public final MethodHandle getGetter() {
if (getter == null) {
member.setAccessible(true);
try {
getter = resolveGetter();
} catch (IllegalAccessException e) {
throw new AssertionError(e); // impossible
}
}
return getter;
}
@Override
public MethodHandle getSetter() {
if (setter == null) {
member.setAccessible(true);
try {
setter = resolveSetter();
} catch (IllegalAccessException e) {
throw new AssertionError(e); // impossible
}
}
return setter;
}
abstract TypeToken<?> resolveType();
abstract MethodHandle resolveGetter() throws IllegalAccessException;
abstract MethodHandle resolveSetter() throws IllegalAccessException;
@Override
public final <A extends Annotation> A getAnnotation(Class<A> annotationClass) {
return member.getAnnotation(annotationClass);
}
@Override
public final boolean hasAnnotation(Class<? extends Annotation> annotation) {
return member.isAnnotationPresent(annotation);
}
@Override
public final String toString() {
return "TypeSpec of type " + getType();
}
}