![JAR search and dependency download from the Maven repository](/logo.png)
com.zarbosoft.interface1.Walk Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of interface Show documentation
Show all versions of interface Show documentation
Data and routines for exposing serializable interfaces
package com.zarbosoft.interface1;
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 io.github.classgraph.ScanResult;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
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 Stream> getDerived(ScanResult scanner, TypeInfo info) {
final java.util.Set subclassNames = new HashSet<>();
final Set> exclude;
final Set> include;
if (info.field != null) {
final Configuration fieldConfig = info.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 Sets
.difference(new HashSet<>(scanner.getSubclasses(info.type.getTypeName()).loadClasses()),
ImmutableSet.of(info.type)
)
.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()))
.sorted(new ChainComparator().lesserFirst(Type::getTypeName).build())
.filter(s -> s.getAnnotation(Configuration.class) != null)
.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,
info.type
));
subclassNames.add(name);
return new Pair<>(name, s);
});
}
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());
}
public 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)
.sorted(new ChainComparator>()
.lesserFirst(p -> p.second.field.getName())
.build())
.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;
}));
}
private static class Context {
public final ScanResult scanner;
public final TypeVisitor visitor;
private Context(final ScanResult scanner, final TypeVisitor visitor) {
this.scanner = scanner;
this.visitor = visitor;
}
}
/**
* Walk a type.
*
* @param scanner
* @param root
* @param visitor
* @param
* @return
*/
public static T walkType(
final ScanResult scanner, final TypeInfo root, A argument, final TypeVisitor visitor
) {
return implementationForType(new Context<>(scanner, visitor), argument, root);
}
private static T implementationForType(
final Context context, A argument, final TypeInfo target
) {
if (target.type == String.class) {
return context.visitor.visitString(argument, target);
} else if ((target.type == int.class) ||
(target.type == Integer.class) ||
(target.type == long.class) ||
(target.type == Long.class)) {
return context.visitor.visitSigned(argument, target);
} else if ((target.type == byte.class) || (target.type == Byte.class)) {
return context.visitor.visitUnsigned(argument, target);
} else if ((target.type == float.class) ||
(target.type == Float.class) ||
(target.type == double.class) ||
(target.type == Double.class)) {
return context.visitor.visitDouble(argument, target);
} else if ((target.type == boolean.class) || (target.type == Boolean.class)) {
return context.visitor.visitBoolean(argument, target);
} else if (((Class>) target.type).isEnum()) {
return context.visitor.visitEnum(argument, target);
} else if (((Class>) target.type).isAssignableFrom(ArrayList.class)) {
if (target.parameters == null)
throw new AssertionError("Unparameterized list!");
Object result = context.visitor.visitListBegin(argument, target);
if (result instanceof StopWalk)
return ((StopWalk) result).value;
return context.visitor.visitListEnd(argument,
target,
implementationForType(context, (A) result, target.parameters[0])
);
} else if (((Class>) target.type).isAssignableFrom(HashSet.class)) {
if (target.parameters == null)
throw new AssertionError("Unparameterized set!");
Object result = context.visitor.visitSetBegin(argument, target);
if (result instanceof StopWalk)
return ((StopWalk) result).value;
return context.visitor.visitSetEnd(argument,
target,
implementationForType(context, (A) result, target.parameters[0])
);
} else if (((Class>) target.type).isAssignableFrom(HashMap.class)) {
if (target.parameters == null)
throw new AssertionError("Unparameterized map!");
if (target.parameters.length != 2) // Can this happen?
throw new AssertionError("Map does not have exactly 2 parameters!");
Object result = context.visitor.visitMapBegin(argument, target);
if (result instanceof StopWalk)
return ((StopWalk) result).value;
return context.visitor.visitMapEnd(argument,
target,
implementationForType(context, (A) result, target.parameters[0]),
implementationForType(context, (A) result, target.parameters[1])
);
} else if (((Class>) target.type).getAnnotation(Configuration.class) != null) {
if (!isConcrete(target)) {
Object result = context.visitor.visitAbstractBegin(argument, target);
if (result instanceof StopWalk)
return ((StopWalk) result).value;
return context.visitor.visitAbstractEnd(argument, target, getDerived(context.scanner, target).map(p -> {
Class s = p.second;
return new Pair, T>(s, (T) implementationForType(context, (A) result, 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
));
}
Object beginResult = context.visitor.visitConcreteBegin(argument, target);
if (beginResult instanceof StopWalk) {
return ((StopWalk) beginResult).value;
} else {
return context.visitor.visitConcreteEnd(argument,
target,
getFields((Class>) target.type).map(p -> {
return new Pair<>(p.second, implementationForType(context,
(A) beginResult,
context.visitor.visitField((A) beginResult, p.second)
));
}).collect(Collectors.toList())
);
}
}
} else
return context.visitor.visitOther(argument, target);
}
public static boolean isPrimitive(TypeInfo type) {
return false ||
type.type == int.class ||
type.type == Integer.class ||
type.type == long.class ||
type.type == Long.class ||
type.type == byte.class ||
type.type == Byte.class ||
type.type == float.class ||
type.type == Float.class ||
type.type == double.class ||
type.type == Double.class ||
type.type == boolean.class ||
type.type == Boolean.class ||
type.type == String.class ||
((Class>) type.type).isEnum() ||
false;
}
/**
* Walk an object.
*
* @param target
* @param value
* @param visitor
*/
public static void walkObject(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.visitSigned((long) (int) value);
} else if ((target.type == long.class) || (target.type == Long.class)) {
visitor.visitSigned((Long) value);
} else if ((target.type == byte.class) || (target.type == Byte.class)) {
visitor.visitUnsigned(Byte.toUnsignedLong((byte) 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 (((Class>) target.type).isAssignableFrom(ArrayList.class)) {
if (target.parameters == null)
throw new AssertionError("Unparameterized list!");
visitor.visitListStart((List) value);
for (final Object subvalue : (List>) value) {
walkObject(target.parameters[0], subvalue, visitor);
}
visitor.visitListEnd((List) value);
} else if (((Class>) target.type).isAssignableFrom(HashSet.class)) {
if (target.parameters == null)
throw new AssertionError("Unparameterized set!");
visitor.visitSetStart((Set) value);
for (final Object subvalue : (Set>) value) {
walkObject(target.parameters[0], subvalue, visitor);
}
visitor.visitSetEnd((Set) value);
} else if (((Class>) target.type).isAssignableFrom(HashMap.class)) {
if (target.parameters == null)
throw new AssertionError("Unparameterized map!");
if (!isPrimitive(target.parameters[0]))
throw new AssertionError(String.format("Interfacable maps must have primitive keys. %s", target));
visitor.visitMapStart((Map) value);
for (final Map.Entry subvalue : ((Map) value).entrySet()) {
visitor.visitKeyBegin(subvalue.getKey());
walkObject(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);
walkObject(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 {
walkObject(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);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy