All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.zarbosoft.interface1.Walk Maven / Gradle / Ivy

There is a newer version: 2.0.3
Show newest version
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