Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.zarbosoft.interface1.Walk Maven / Gradle / Ivy
package com.zarbosoft.interface1;
import com.google.common.base.Objects;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import com.zarbosoft.rendaw.common.ChainComparator;
import com.zarbosoft.rendaw.common.Common;
import com.zarbosoft.rendaw.common.Pair;
import org.reflections.Reflections;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import static com.zarbosoft.rendaw.common.Common.iterable;
import static com.zarbosoft.rendaw.common.Common.uncheck;
import static java.util.Arrays.stream;
public class Walk {
public static class WalkObjectError extends RuntimeException {
public WalkObjectError(String message, Throwable chain) {
super(message, chain);
}
}
/**
* A helper method to determine the serialized name of an annotated field.
*
* @param field
* @return
*/
public static String decideName(final Field field) {
final Configuration annotation = field.getAnnotation(Configuration.class);
if (annotation == null || annotation.name().equals(""))
return field.getName();
return annotation.name();
}
/**
* A helper method to determine the serialized name of an annotated class.
*
* @param klass
* @return
*/
public static String decideName(final Class> klass) {
final Configuration annotation = klass.getAnnotation(Configuration.class);
if (annotation == null || annotation.name().equals(""))
return klass.getName();
return annotation.name();
}
/**
* A helper method to determine the serialized name of an annotated enum value.
*
* @param value
* @return
*/
public static String decideEnumName(final Enum value) {
return decideName(uncheck(() -> value.getClass().getField(value.name())));
}
public static boolean required(final Field field) {
final Configuration annotation = field.getAnnotation(Configuration.class);
return !annotation.optional();
}
public static boolean isConcrete(TypeInfo info) {
return !((Class>) info.type).isInterface() && !Modifier.isAbstract(((Class>) info.type).getModifiers());
}
public static List, Field>> enumValues(final Class> enumClass) {
return stream(enumClass.getEnumConstants()).map(prevalue -> {
final Enum> value = (Enum>) prevalue;
final Field field = uncheck(() -> enumClass.getField(value.name()));
return new Pair, Field>(value, field);
}).collect(Collectors.toList());
}
private static Stream> getFields(final Class> klass) {
class FieldIterator implements Iterator> {
private Class> at;
public FieldIterator(final Class> klass) {
at = klass;
}
@Override
public boolean hasNext() {
return at != null;
}
@Override
public Class> next() {
final Class> out = at;
at = at.getSuperclass();
return out;
}
}
return Common
.stream(new FieldIterator(klass))
.flatMap(klass2 -> stream(klass2.getDeclaredFields()))
.map(f -> new Pair<>(f.getAnnotation(Configuration.class), new TypeInfo(f)))
.filter(p -> p.first != null)
.map(p -> {
TypeInfo f = p.second;
try {
klass.getMethod(f.field.getName(), f.field.getType());
} catch (final NoSuchMethodException e) {
if ((f.field.getModifiers() & Modifier.PUBLIC) == 0)
throw new AssertionError(String.format("Field %s marked for serialization is not public.",
f
));
}
return p;
});
}
/**
* A class that represents a complete type.
*/
public static class TypeInfo {
/**
* The base type
*/
public final Type type;
/**
* If generic, type parameters (never null). Otherwise null.
*/
public final TypeInfo[] parameters;
/**
* If this refers to the field, the field data (duplicates some info, like type)
*/
public final Field field;
/**
* Internal use only. Although it's okay if you're using it to copy another typeInfo and modify stuff.
*
* @param field
* @param type
* @param parameters
*/
public TypeInfo(Field field, Type type, TypeInfo[] parameters) {
this.type = type;
this.parameters = parameters;
this.field = field;
}
/**
* Internal use only. Although it's okay if you're using it to copy another typeInfo and remove the parameters.
*
* @param field
* @param target
*/
public TypeInfo(final Field field, final Type target) {
if (target instanceof ParameterizedType) {
this.type = ((ParameterizedType) target).getRawType();
parameters = stream(((ParameterizedType) target).getActualTypeArguments())
.map(type1 -> new TypeInfo(type1))
.toArray(TypeInfo[]::new);
} else {
this.type = target;
this.parameters = null;
}
this.field = field;
}
/**
* Construct a TypeInfo that represents a simple type (no generic arguments, not a field).
*
* @param target
*/
public TypeInfo(final Type target) {
this(null, target);
}
/**
* Construct a TypeInfo that represents a generic type.
*
* @param type
* @param parameter
*/
public TypeInfo(final Type type, final TypeInfo... parameter) {
this.type = type;
this.parameters = parameter;
this.field = null;
}
/**
* Construct a TypeInfo that represents a field (including generic types).
*
* @param f
*/
public TypeInfo(final Field f) {
this.field = f;
this.type = f.getType();
if (f.getGenericType() instanceof ParameterizedType)
this.parameters = stream(((ParameterizedType) f.getGenericType()).getActualTypeArguments())
.map(type1 -> new TypeInfo(field, type1))
.toArray(TypeInfo[]::new);
else
this.parameters = null;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (!(obj instanceof TypeInfo))
return false;
return type == ((TypeInfo) obj).type &&
field == ((TypeInfo) obj).field &&
Arrays.equals(parameters, ((TypeInfo) obj).parameters);
}
@Override
public int hashCode() {
return Objects.hashCode(type, parameters, field);
}
}
private static class Context {
public final Reflections reflections;
public final Visitor visitor;
public Set> seen = new HashSet<>();
private Context(final Reflections reflections, final Visitor visitor) {
this.reflections = reflections;
this.visitor = visitor;
}
}
public interface Visitor {
T visitString(TypeInfo field);
T visitLong(TypeInfo field);
T visitDouble(TypeInfo field);
T visitBoolean(TypeInfo field);
T visitEnum(TypeInfo field);
T visitList(TypeInfo field, T inner);
T visitSet(TypeInfo field, T inner);
T visitMap(TypeInfo field, T inner);
T visitAbstract(TypeInfo field, Class> klass, List, T>> derived);
T visitConcreteShort(Field field, Class> klass);
void visitConcrete(TypeInfo field, List> fields);
default T visitOther(TypeInfo field) {
if (field.field == null)
throw new AssertionError(String.format("Uninterfacable value of type or derived type [%s]",
field.type
));
else
throw new AssertionError(String.format("Uninterfacable field of type or derived type [%s] in [%s]",
field.type,
field.field.getDeclaringClass().getName()
));
}
default TypeInfo visitField(TypeInfo field) {
return field;
}
}
public interface ObjectVisitor {
void visitString(String value);
void visitLong(Long value);
void visitDouble(Double value);
void visitBoolean(Boolean value);
void visitEnum(Enum value);
void visitListStart(List value);
void visitListEnd(List value);
void visitSetStart(Set value);
void visitSetEnd(Set value);
void visitMapStart(Map value);
void visitKeyBegin(String key);
void visitKeyEnd(String key);
void visitMapEnd(Map value);
void visitAbstractBegin(Class> klass, Object value);
void visitAbstractEnd(Class> klass, Object value);
boolean visitConcreteBegin(Class> klass, Object value);
void visitFieldBegin(TypeInfo field, Object value);
void visitFieldEnd(TypeInfo field, Object value);
void visitConcreteEnd(Class> klass, Object value);
default void visitOther(final Object value) {
throw new AssertionError(String.format("Uninterfacable field of type or derived type [%s]",
value.getClass()
));
}
default Pair transformValue(TypeInfo fieldInfo, Object value) {
return new Pair<>(fieldInfo, value);
}
}
/**
* Walk a type.
*
* @param reflections
* @param root
* @param visitor
* @param
* @return
*/
public static T walk(
final Reflections reflections, final TypeInfo root, final Visitor visitor
) {
return implementationForType(new Context<>(reflections, visitor), root);
}
private static T implementationForType(
final Context context, final TypeInfo target
) {
if (target.type == String.class) {
return context.visitor.visitString(target);
} else if ((target.type == int.class) ||
(target.type == Integer.class) ||
(target.type == long.class) ||
(target.type == Long.class)) {
return context.visitor.visitLong(target);
} else if ((target.type == float.class) ||
(target.type == Float.class) ||
(target.type == double.class) ||
(target.type == Double.class)) {
return context.visitor.visitDouble(target);
} else if ((target.type == boolean.class) || (target.type == Boolean.class)) {
return context.visitor.visitBoolean(target);
} else if (((Class>) target.type).isEnum()) {
return context.visitor.visitEnum(target);
} else if (List.class.isAssignableFrom((Class>) target.type)) {
if (target.parameters == null)
throw new AssertionError("Unparameterized list!");
return context.visitor.visitList(target, implementationForType(context, target.parameters[0]));
} else if (java.util.Set.class.isAssignableFrom((Class>) target.type)) {
if (target.parameters == null)
throw new AssertionError("Unparameterized set!");
return context.visitor.visitSet(target, implementationForType(context, target.parameters[0]));
} else if (Map.class.isAssignableFrom((Class>) target.type)) {
if (target.parameters == null)
throw new AssertionError("Unparameterized map!");
if (target.parameters.length != 2)
throw new AssertionError("Map does not have exactly 2 parameters!");
if (target.parameters[0].type != String.class)
throw new AssertionError("Interfacable maps must have String keys.");
return context.visitor.visitMap(target, implementationForType(context, target.parameters[1]));
} else if (((Class>) target.type).getAnnotation(Configuration.class) != null) {
if (!isConcrete(target)) {
final java.util.Set subclassNames = new HashSet<>();
final Set> exclude;
final Set> include;
if (target.field != null) {
final Configuration fieldConfig = target.field.getAnnotation(Configuration.class);
exclude = new HashSet<>(Arrays.asList(fieldConfig.exclude()));
include = new HashSet<>(Arrays.asList(fieldConfig.include()));
} else {
exclude = ImmutableSet.of();
include = ImmutableSet.of();
}
return context.visitor.visitAbstract(
target,
(Class>) target.type,
Sets
.difference(
context.reflections.getSubTypesOf((Class>) target.type),
ImmutableSet.of(target)
)
.stream()
.map(s -> (Class>) s)
.filter(s -> exclude.isEmpty() || !exclude.contains(s))
.filter(s -> include.isEmpty() || include.contains(s))
.filter(s -> !Modifier.isAbstract(s.getModifiers()))
.filter(s -> s.getAnnotation(Configuration.class) != null)
.sorted(new ChainComparator().lesserFirst(Type::getTypeName).build())
.map(s -> {
String name = decideName(s);
if (subclassNames.contains(name))
throw new IllegalArgumentException(String.format(
"Specific type [%s] of polymorphic type [%s] is ambiguous.",
name,
target.type
));
subclassNames.add(name);
return new Pair, T>(
s,
(T) implementationForType(context, new TypeInfo(s))
);
})
.collect(Collectors.toList())
);
} else {
try {
((Class>) target.type).getConstructor();
} catch (final NoSuchMethodException e) {
throw new AssertionError(String.format(
"Interface class [%s] has no nullary constructor or constructor is not public (maybe the class isn't static).",
target.type
));
}
if (!context.seen.contains(target.type)) {
context.seen.add((Class>) target.type);
context.visitor.visitConcrete(target, getFields((Class>) target.type).map(p -> {
return new Pair<>(p.second,
implementationForType(context, context.visitor.visitField(p.second))
);
}).collect(Collectors.toList()));
}
return context.visitor.visitConcreteShort(target.field, (Class>) target.type);
}
}
return context.visitor.visitOther(target);
}
/**
* Walk an object.
*
* @param target
* @param value
* @param visitor
*/
public static void walk(TypeInfo target, Object value, final ObjectVisitor visitor) {
if (target.type == String.class) {
visitor.visitString((String) value);
} else if ((target.type == int.class) || (target.type == Integer.class)) {
visitor.visitLong((long) (int) value);
} else if ((target.type == long.class) || (target.type == Long.class)) {
visitor.visitLong((Long) value);
} else if ((target.type == float.class) || (target.type == Float.class)) {
visitor.visitDouble((double) (float) value);
} else if ((target.type == double.class) || (target.type == Double.class)) {
visitor.visitDouble((Double) value);
} else if ((target.type == boolean.class) || (target.type == Boolean.class)) {
visitor.visitBoolean((Boolean) value);
} else if (((Class>) target.type).isEnum()) {
visitor.visitEnum((Enum) value);
} else if (List.class.isAssignableFrom((Class>) target.type)) {
if (target.parameters == null)
throw new AssertionError("Unparameterized list!");
visitor.visitListStart((List) value);
for (final Object subvalue : (List>) value) {
walk(target.parameters[0], subvalue, visitor);
}
visitor.visitListEnd((List) value);
} else if (java.util.Set.class.isAssignableFrom((Class>) target.type)) {
if (target.parameters == null)
throw new AssertionError("Unparameterized set!");
visitor.visitSetStart((Set) value);
for (final Object subvalue : (Set>) value) {
walk(target.parameters[0], subvalue, visitor);
}
visitor.visitSetEnd((Set) value);
} else if (Map.class.isAssignableFrom((Class>) target.type)) {
if (target.parameters == null)
throw new AssertionError("Unparameterized map!");
if (target.parameters[0].type != String.class)
throw new AssertionError("Interfacable maps must have String keys.");
visitor.visitMapStart((Map) value);
for (final Map.Entry subvalue : ((Map) value).entrySet()) {
visitor.visitKeyBegin(subvalue.getKey());
walk(target.parameters[1], subvalue.getValue(), visitor);
visitor.visitKeyEnd(subvalue.getKey());
}
visitor.visitMapEnd((Map) value);
} else if (((Class>) target.type).getAnnotation(Configuration.class) != null) {
if (((Class>) target.type).isInterface() ||
Modifier.isAbstract(((Class>) target.type).getModifiers())) {
visitor.visitAbstractBegin((Class>) target.type, value);
walk(new TypeInfo(value.getClass()), value, visitor);
visitor.visitAbstractEnd((Class>) target.type, value);
} else {
final boolean enter = visitor.visitConcreteBegin((Class>) target.type, value);
if (enter) {
for (Pair pair : iterable(getFields((Class>) target.type))) {
Field field = pair.second.field;
Object subvalue = uncheck(() -> {
try {
return ((Class>) target.type).getMethod(field.getName()).invoke(value);
} catch (final NoSuchMethodException e) {
return field.get(value);
}
});
Configuration configuration = pair.first;
Pair transformed = visitor.transformValue(pair.second, subvalue);
TypeInfo subtarget = transformed.first;
subvalue = transformed.second;
if (subvalue == null && configuration.optional())
continue;
visitor.visitFieldBegin(subtarget, subvalue);
try {
walk(subtarget, subvalue, visitor);
} catch (Exception e) {
throw new WalkObjectError(String.format("Error walking field [%s] in [%s]",
field.getName(),
target.type.getTypeName()
), e);
}
visitor.visitFieldEnd(subtarget, subvalue);
}
visitor.visitConcreteEnd((Class>) target.type, value);
}
}
} else
visitor.visitOther(value);
}
public static class DefaultVisitor implements Visitor {
@Override
public T visitString(final TypeInfo field) {
return null;
}
@Override
public T visitLong(final TypeInfo field) {
return null;
}
@Override
public T visitDouble(final TypeInfo field) {
return null;
}
@Override
public T visitBoolean(final TypeInfo field) {
return null;
}
@Override
public T visitEnum(TypeInfo field) {
return null;
}
@Override
public T visitList(final TypeInfo field, final T inner) {
return null;
}
@Override
public T visitSet(final TypeInfo field, final T inner) {
return null;
}
@Override
public T visitMap(final TypeInfo field, final T inner) {
return null;
}
@Override
public T visitAbstract(
final TypeInfo field, final Class> klass, final List, T>> derived
) {
return null;
}
@Override
public T visitConcreteShort(final Field field, final Class> klass) {
return null;
}
@Override
public void visitConcrete(
final TypeInfo field, final List> fields
) {
}
@Override
public T visitOther(TypeInfo field) {
return null;
}
}
}