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

io.activej.serializer.SerializerFactory Maven / Gradle / Ivy

/*
 * Copyright (C) 2020 ActiveJ LLC.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package io.activej.serializer;

import io.activej.codegen.ClassGenerator;
import io.activej.codegen.DefiningClassLoader;
import io.activej.codegen.expression.Expression;
import io.activej.codegen.expression.Expressions;
import io.activej.codegen.expression.Variable;
import io.activej.common.builder.AbstractBuilder;
import io.activej.serializer.annotations.*;
import io.activej.serializer.def.*;
import io.activej.serializer.def.impl.ClassSerializerDef;
import io.activej.serializer.def.impl.SubclassSerializerDef;
import io.activej.types.AnnotationUtils;
import io.activej.types.TypeT;
import io.activej.types.scanner.TypeScannerRegistry;
import io.activej.types.scanner.TypeScannerRegistry.Context;
import org.jetbrains.annotations.Nullable;
import org.objectweb.asm.*;

import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.lang.reflect.*;
import java.net.Inet4Address;
import java.net.Inet6Address;
import java.net.InetAddress;
import java.util.*;
import java.util.function.Function;

import static io.activej.codegen.expression.Expressions.*;
import static io.activej.serializer.def.SerializerDef.*;
import static io.activej.serializer.def.SerializerExpressions.readByte;
import static io.activej.serializer.def.SerializerExpressions.writeByte;
import static io.activej.types.AnnotatedTypes.*;
import static io.activej.types.Utils.getAnnotation;
import static io.activej.types.Utils.hasAnnotation;
import static java.lang.String.format;
import static java.lang.System.identityHashCode;
import static java.lang.reflect.Modifier.*;
import static java.util.Collections.newSetFromMap;
import static java.util.Comparator.naturalOrder;
import static java.util.Objects.requireNonNull;
import static java.util.stream.Collectors.groupingBy;
import static java.util.stream.Collectors.toList;
import static org.objectweb.asm.ClassReader.*;
import static org.objectweb.asm.Type.getType;

/**
 * Scans fields of classes for serialization.
 */
public final class SerializerFactory {
	private final TypeScannerRegistry registry = TypeScannerRegistry.create();

	private Class implementationClass = Object.class;

	private String profile;
	private int encodeVersionMax = Integer.MAX_VALUE;
	private int decodeVersionMin = 0;
	private int decodeVersionMax = Integer.MAX_VALUE;
	private CompatibilityLevel compatibilityLevel = CompatibilityLevel.LEVEL_4;

	private final Map>> extraSubclassesMap = new HashMap<>();

	private SerializerFactory() {
	}

	/**
	 * Creates a new instance of {@link  SerializerFactory}
	 */
	public static SerializerFactory defaultInstance() {
		return builder().build();
	}

	/**
	 * Creates a builder of {@link  SerializerFactory}
	 */
	public static Builder builder() {
		SerializerFactory factory = new SerializerFactory();
		return factory.new Builder()
			.with(boolean.class, ctx -> SerializerDefs.ofBoolean(false))
			.with(char.class, ctx -> SerializerDefs.ofChar(false))
			.with(byte.class, ctx -> SerializerDefs.ofByte(false))
			.with(short.class, ctx -> SerializerDefs.ofShort(false))
			.with(int.class, ctx -> SerializerDefs.ofInt(false))
			.with(long.class, ctx -> SerializerDefs.ofLong(false))
			.with(float.class, ctx -> SerializerDefs.ofFloat(false))
			.with(double.class, ctx -> SerializerDefs.ofDouble(false))

			.with(Boolean.class, ctx -> SerializerDefs.ofBoolean(true))
			.with(Character.class, ctx -> SerializerDefs.ofChar(true))
			.with(Byte.class, ctx -> SerializerDefs.ofByte(true))
			.with(Short.class, ctx -> SerializerDefs.ofShort(true))
			.with(Integer.class, ctx -> SerializerDefs.ofInt(true))
			.with(Long.class, ctx -> SerializerDefs.ofLong(true))
			.with(Float.class, ctx -> SerializerDefs.ofFloat(true))
			.with(Double.class, ctx -> SerializerDefs.ofDouble(true))

			.initialize(builder -> {
				for (Type type : new Type[]{
					boolean[].class, char[].class, byte[].class, short[].class, int[].class, long[].class, float[].class, double[].class,
					Object[].class}) {
					builder.with(type, ctx -> SerializerDefs.ofArray(ctx.scanTypeArgument(0)));
				}
			})

			.with(Inet4Address.class, ctx -> SerializerDefs.ofInet4Address())
			.with(Inet6Address.class, ctx -> SerializerDefs.ofInet6Address())
			.with(InetAddress.class, ctx -> SerializerDefs.ofInetAddress())

			.with(Enum.class, ctx -> {
				for (Method method : ctx.getRawType().getDeclaredMethods()) {
					if (AnnotationUtils.hasAnnotation(Serialize.class, method.getAnnotations())) {
						return factory.scan(ctx);
					}
				}
				for (Field field : ctx.getRawType().getDeclaredFields()) {
					if (AnnotationUtils.hasAnnotation(Serialize.class, field.getAnnotations())) {
						return factory.scan(ctx);
					}
				}
				//noinspection unchecked,rawtypes
				return SerializerDefs.ofEnum((Class) ctx.getRawType());
			})

			.with(String.class, ctx -> {
				SerializeStringFormat a = getAnnotation(ctx.getAnnotations(), SerializeStringFormat.class);
				return SerializerDefs.ofString(a == null ? StringFormat.UTF8 : a.value());
			})

			.with(Collection.class, ctx -> SerializerDefs.ofCollection(ctx.scanTypeArgument(0), Collection.class, ArrayList.class))
			.with(Queue.class, ctx -> SerializerDefs.ofCollection(ctx.scanTypeArgument(0), Queue.class, ArrayDeque.class))

			.with(List.class, ctx -> SerializerDefs.ofList(ctx.scanTypeArgument(0)))
			.with(ArrayList.class, ctx -> SerializerDefs.ofCollection(ctx.scanTypeArgument(0), ArrayList.class, ArrayList.class))
			.with(LinkedList.class, ctx -> SerializerDefs.ofLinkedList(ctx.scanTypeArgument(0)))

			.with(Map.class, ctx -> SerializerDefs.ofMap(ctx.scanTypeArgument(0), ctx.scanTypeArgument(1)))
			.with(HashMap.class, ctx -> SerializerDefs.ofHashMap(ctx.scanTypeArgument(0), ctx.scanTypeArgument(1), HashMap.class, HashMap.class))
			.with(LinkedHashMap.class, ctx -> SerializerDefs.ofHashMap(ctx.scanTypeArgument(0), ctx.scanTypeArgument(1), LinkedHashMap.class, LinkedHashMap.class))
			.with(EnumMap.class, ctx -> SerializerDefs.ofEnumMap(ctx.scanTypeArgument(0), ctx.scanTypeArgument(1)))

			.with(Set.class, ctx -> SerializerDefs.ofSet(ctx.scanTypeArgument(0)))
			.with(HashSet.class, ctx -> SerializerDefs.ofHashSet(ctx.scanTypeArgument(0), HashSet.class, HashSet.class))
			.with(LinkedHashSet.class, ctx -> SerializerDefs.ofHashSet(ctx.scanTypeArgument(0), LinkedHashSet.class, LinkedHashSet.class))
			.with(EnumSet.class, ctx -> SerializerDefs.ofEnumSet(ctx.scanTypeArgument(0)))

			.with(Object.class, factory::scan);
	}

	public final class Builder extends AbstractBuilder {
		private Builder() {}

		/**
		 * Adds a mapping to resolve a {@link SerializerDef} for a given {@link TypeT}
		 *
		 * @param typeT a type token
		 * @param fn    a mapping to resolve a serializer
		 */
		public Builder with(TypeT typeT, TypeScannerRegistry.Mapping fn) {
			checkNotBuilt(this);
			return with(typeT.getType(), fn);
		}

		/**
		 * Adds a mapping to resolve a {@link SerializerDef} for a given {@link Type}
		 *
		 * @param type a type
		 * @param fn   a mapping to resolve a serializer
		 */
		@SuppressWarnings("PointlessBooleanExpression")
		public Builder with(Type type, TypeScannerRegistry.Mapping fn) {
			checkNotBuilt(this);
			registry.with(type, ctx -> {
				Class rawClass = ctx.getRawType();
				SerializerDef serializerDef;
				SerializeClass annotationClass;
				if (false ||
					(annotationClass = getAnnotation(ctx.getAnnotations(), SerializeClass.class)) != null ||
					(annotationClass = getAnnotation(rawClass.getAnnotations(), SerializeClass.class)) != null) {
					if (annotationClass.value() != SerializerDef.class) {
						try {
							serializerDef = annotationClass.value().getDeclaredConstructor().newInstance();
						} catch (
							InstantiationException |
							IllegalAccessException |
							NoSuchMethodException |
							InvocationTargetException e
						) {
							throw new RuntimeException(e);
						}
					} else {
						SubclassSerializerDef.Builder subclassBuilder = SubclassSerializerDef.builder(rawClass);
						LinkedHashSet> subclassesSet = new LinkedHashSet<>(List.of(annotationClass.subclasses()));
						subclassesSet.addAll(extraSubclassesMap.getOrDefault(rawClass, List.of()));
						subclassesSet.addAll(extraSubclassesMap.getOrDefault(annotationClass.subclassesId(), List.of()));
						for (Class subclass : subclassesSet) {
							subclassBuilder.withSubclass(subclass, ctx.scan(subclass));
						}
						serializerDef = subclassBuilder
							.withStartIndex(annotationClass.subclassesIdx())
							.build();
					}
				} else if (extraSubclassesMap.containsKey(rawClass)) {
					SubclassSerializerDef.Builder subclassBuilder = SubclassSerializerDef.builder(rawClass);
					for (Class subclass : extraSubclassesMap.get(rawClass)) {
						subclassBuilder.withSubclass(subclass, ctx.scan(subclass));
					}
					serializerDef = subclassBuilder.build();
				} else {
					serializerDef = fn.apply(ctx);
				}

				if (hasAnnotation(ctx.getAnnotations(), SerializeVarLength.class)) {
					serializerDef = ((SerializerDefWithVarLength) serializerDef).ensureVarLength();
				}

				SerializeFixedSize annotationFixedSize;
				if ((annotationFixedSize = getAnnotation(ctx.getAnnotations(), SerializeFixedSize.class)) != null) {
					serializerDef = ((SerializerDefWithFixedSize) serializerDef).ensureFixedSize(annotationFixedSize.value());
				}

				if (hasAnnotation(ctx.getAnnotations(), SerializeNullable.class)) {
					serializerDef = serializerDef instanceof SerializerDefWithNullable ?
						((SerializerDefWithNullable) serializerDef).ensureNullable(compatibilityLevel) : SerializerDefs.ofNullable(serializerDef);
				}

				return serializerDef;
			});
			return this;
		}

		/**
		 * Adds an implementation class for the serializer
		 *
		 * @param implementationClass an implementation class
		 */
		public Builder withImplementationClass(Class implementationClass) {
			checkNotBuilt(this);
			SerializerFactory.this.implementationClass = implementationClass;
			return this;
		}

		/**
		 * Sets a given {@link CompatibilityLevel} for the serializer. This method should be used
		 * to ensure backwards compatibility with previous versions of serializers
		 *
		 * @param compatibilityLevel a compatibility level
		 */
		public Builder withCompatibilityLevel(CompatibilityLevel compatibilityLevel) {
			checkNotBuilt(this);
			SerializerFactory.this.compatibilityLevel = compatibilityLevel;
			return this;
		}

		/**
		 * Sets maximal encode version
		 * 

* This method is used to ensure compatibility between different versions of serialized objects * * @param encodeVersionMax a maximal encode version */ public Builder withEncodeVersion(int encodeVersionMax) { checkNotBuilt(this); SerializerFactory.this.encodeVersionMax = encodeVersionMax; return this; } /** * Sets both minimal and maximal decode versions * *

* This method is used to ensure compatibility between different versions of serialized objects * * @param decodeVersionMin a minimal decode version * @param decodeVersionMax a maximal decode version */ public Builder withDecodeVersions(int decodeVersionMin, int decodeVersionMax) { checkNotBuilt(this); SerializerFactory.this.decodeVersionMin = decodeVersionMin; SerializerFactory.this.decodeVersionMax = decodeVersionMax; return this; } /** * Sets maximal encode version as well as both minimal and maximal decode versions * *

* This method is used to ensure compatibility between different versions of serialized objects * * @param encodeVersionMax a maximal encode version * @param decodeVersionMin a minimal decode version * @param decodeVersionMax a maximal decode version */ public Builder withVersions(int encodeVersionMax, int decodeVersionMin, int decodeVersionMax) { checkNotBuilt(this); SerializerFactory.this.encodeVersionMax = encodeVersionMax; SerializerFactory.this.decodeVersionMin = decodeVersionMin; SerializerFactory.this.decodeVersionMax = decodeVersionMax; return this; } /** * Sets a serializer profile * * @param profile a serializer profile */ public Builder withProfile(String profile) { checkNotBuilt(this); SerializerFactory.this.profile = profile; return this; } /** * Sets subclasses to be serialized. * Uses custom string id to identify subclasses *

* Order of subclasses matters. To keep serializers compatible, the order of subclasses should not change * * @param subclassesId an id of subclasses * @param subclasses actual subclasses classes * @param a parent of subclasses */ public Builder withSubclasses(String subclassesId, List> subclasses) { checkNotBuilt(this); //noinspection unchecked,rawtypes extraSubclassesMap.put(subclassesId, (List) subclasses); return this; } /** * Sets subclasses to be serialized. * Uses parent class to identify subclasses *

* Order of subclasses matters. To keep serializers compatible, the order of subclasses should not change * * @param type a parent class of subclasses * @param subclasses actual subclasses classes * @param a parent type of subclasses */ public Builder withSubclasses(Class type, List> subclasses) { checkNotBuilt(this); //noinspection unchecked,rawtypes extraSubclassesMap.put(type, (List) subclasses); return this; } @Override protected SerializerFactory doBuild() { return SerializerFactory.this; } } /** * Builds a {@link BinarySerializer} out of {@code this} {@link SerializerFactory}. * * @see #toClassGenerator(AnnotatedType) */ public BinarySerializer create(DefiningClassLoader classLoader, Type type) { return this.toClassGenerator(type).generateClassAndCreateInstance(classLoader); } public BinarySerializer create(Type type) { return create(DefiningClassLoader.create(), type); } /** * Builds a {@link BinarySerializer} out of {@code this} {@link SerializerFactory}. * * @see #toClassGenerator(AnnotatedType) */ public ClassGenerator> toClassGenerator(Type type) { return toClassGenerator(toSerializerDef(type)); } public SerializerDef toSerializerDef(Type type) { return toSerializerDef(annotatedTypeOf(type)); } /** * Builds a {@link BinarySerializer} out of {@code this} {@link SerializerFactory}. * * @see #toClassGenerator(AnnotatedType) */ public BinarySerializer create(DefiningClassLoader classLoader, Class type) { return toClassGenerator(type).generateClassAndCreateInstance(classLoader); } public BinarySerializer create(Class type) { return create(DefiningClassLoader.create(), type); } /** * Builds a {@link BinarySerializer} out of {@code this} {@link SerializerFactory}. * * @see #toClassGenerator(AnnotatedType) */ public ClassGenerator> toClassGenerator(Class type) { return toClassGenerator(toSerializerDef(type)); } public SerializerDef toSerializerDef(Class type) { return toSerializerDef(annotatedTypeOf(type)); } /** * Builds a {@link BinarySerializer} out of {@code this} {@link SerializerFactory}. * * @see #toClassGenerator(AnnotatedType) */ public BinarySerializer create(DefiningClassLoader classLoader, TypeT typeT) { return toClassGenerator(typeT).generateClassAndCreateInstance(classLoader); } public BinarySerializer create(TypeT typeT) { return create(DefiningClassLoader.create(), typeT); } /** * Builds a {@link BinarySerializer} out of {@code this} {@link SerializerFactory}. * * @see #toClassGenerator(AnnotatedType) */ public ClassGenerator> toClassGenerator(TypeT typeT) { return toClassGenerator(toSerializerDef(typeT)); } public SerializerDef toSerializerDef(TypeT typeT) { return toSerializerDef(typeT.getAnnotatedType()); } /** * Builds a {@link BinarySerializer} out of {@code this} {@link SerializerFactory}. *

* * @param type a type data that would be serialized * @return a generated {@link BinarySerializer} */ public BinarySerializer create(DefiningClassLoader classLoader, AnnotatedType type) { return this.toClassGenerator(type).generateClassAndCreateInstance(classLoader); } public BinarySerializer create(AnnotatedType type) { return create(DefiningClassLoader.create(), type); } /** * Builds a {@link BinarySerializer} out of {@code this} {@link SerializerFactory}. *

* * @param type a type data that would be serialized * @return a generated {@link BinarySerializer} */ public ClassGenerator> toClassGenerator(AnnotatedType type) { return toClassGenerator(toSerializerDef(type)); } private SerializerDef toSerializerDef(AnnotatedType type) { return registry.scanner(new HashMap<>()).scan(type); } /** * Builds a {@link BinarySerializer} out of some {@link SerializerDef}. * * @param serializerDef a {@link SerializerDef} that would be used to create a {@link BinarySerializer} * @return a generated {@link BinarySerializer} */ public BinarySerializer create(DefiningClassLoader classLoader, SerializerDef serializerDef) { //noinspection unchecked return (BinarySerializer) toClassGenerator(serializerDef).generateClassAndCreateInstance(classLoader); } /** * Converts a {@link SerializerDef} into a {@link ClassGenerator} of {@link BinarySerializer} * * @param serializer a serializer definition * @param a type of data to be serialized by a {@link BinarySerializer} * @return a {@link ClassGenerator} of {@link BinarySerializer} */ public ClassGenerator> toClassGenerator(SerializerDef serializer) { //noinspection unchecked ClassGenerator>.Builder classGenerator = ClassGenerator.builder((Class>) implementationClass, BinarySerializer.class); Set collectedVersions = new HashSet<>(); Set visited = newSetFromMap(new IdentityHashMap<>()); Visitor visitor = new Visitor() { @Override public void visit(String serializerId, SerializerDef visitedSerializer) { if (!visited.add(visitedSerializer)) return; collectedVersions.addAll(visitedSerializer.getVersions()); visitedSerializer.accept(this); } }; visitor.visit(serializer); Integer encodeVersion = collectedVersions.stream() .filter(v -> v <= encodeVersionMax) .max(naturalOrder()) .orElse(null); List decodeVersions = collectedVersions.stream() .filter(v -> v >= decodeVersionMin && v <= decodeVersionMax) .sorted() .collect(toList()); defineEncoders(classGenerator, serializer, encodeVersion); defineDecoders(classGenerator, serializer, decodeVersions); return classGenerator.build(); } private void defineEncoders(ClassGenerator.Builder classGenerator, SerializerDef serializer, @Nullable Integer encodeVersion) { StaticEncoders staticEncoders = staticEncoders(classGenerator, encodeVersion != null ? encodeVersion : 0, compatibilityLevel); classGenerator.withMethod("encode", int.class, List.of(byte[].class, int.class, Object.class), let(cast(arg(2), serializer.getEncodeType()), data -> encoderImpl(serializer, encodeVersion, staticEncoders, arg(0), arg(1), data))); classGenerator.withMethod("encode", void.class, List.of(BinaryOutput.class, Object.class), let(call(arg(0), "array"), buf -> let(call(arg(0), "pos"), pos -> let(cast(arg(1), serializer.getEncodeType()), data -> sequence( encoderImpl(serializer, encodeVersion, staticEncoders, buf, pos, data), call(arg(0), "pos", pos)))))); } private Expression encoderImpl(SerializerDef serializer, @Nullable Integer encodeVersion, StaticEncoders staticEncoders, Expression buf, Variable pos, Variable data) { return sequence( encodeVersion != null ? writeByte(buf, pos, value((byte) (int) encodeVersion)) : voidExp(), serializer.encode(staticEncoders, buf, pos, data, encodeVersion != null ? encodeVersion : 0, compatibilityLevel), pos); } private void defineDecoders( ClassGenerator.Builder classGenerator, SerializerDef serializer, List decodeVersions ) { Integer latestVersion = decodeVersions.isEmpty() ? null : decodeVersions.get(decodeVersions.size() - 1); StaticDecoders latestStaticDecoders = staticDecoders(classGenerator, latestVersion == null ? 0 : latestVersion); classGenerator.withMethod("decode", Object.class, List.of(BinaryInput.class), decodeImpl(serializer, latestVersion, latestStaticDecoders, arg(0))); classGenerator.withMethod("decode", Object.class, List.of(byte[].class, int.class), let(constructor(BinaryInput.class, arg(0), arg(1)), in -> decodeImpl(serializer, latestVersion, latestStaticDecoders, in))); classGenerator.withMethod("decodeEarlierVersions", serializer.getDecodeType(), List.of(BinaryInput.class, byte.class), () -> { List listKey = new ArrayList<>(); List listValue = new ArrayList<>(); for (int i = decodeVersions.size() - 2; i >= 0; i--) { int version = decodeVersions.get(i); listKey.add(value((byte) version)); listValue.add(call(self(), "decodeVersion" + version, arg(0))); } Expression result = throwException(CorruptedDataException.class, concat(value("Unsupported version: "), arg(1), value(", supported versions: " + decodeVersions))); for (int i = listKey.size() - 1; i >= 0; i--) { result = ifEq(arg(1), listKey.get(i), listValue.get(i), result); } return result; }); for (int i = decodeVersions.size() - 2; i >= 0; i--) { int version = decodeVersions.get(i); classGenerator.withMethod("decodeVersion" + version, serializer.getDecodeType(), List.of(BinaryInput.class), sequence(serializer .defineDecoder(staticDecoders(classGenerator, version), version, compatibilityLevel) .decode(arg(0)))); } } private Expression decodeImpl( SerializerDef serializer, Integer latestVersion, StaticDecoders staticDecoders, Expression in ) { return latestVersion == null ? serializer.decode( staticDecoders, in, 0, compatibilityLevel) : let(readByte(in), version -> ifEq(version, value((byte) (int) latestVersion), serializer.decode( staticDecoders, in, latestVersion, compatibilityLevel), call(self(), "decodeEarlierVersions", in, version))); } private static StaticEncoders staticEncoders(ClassGenerator.Builder classGenerator, int version, CompatibilityLevel compatibilityLevel) { return new StaticEncoders() { final Map, String> defined = new HashMap<>(); @Override public Encoder define(SerializerDef serializerDef) { List key = List.of(identityHashCode(serializerDef), version, compatibilityLevel); String methodName = defined.get(key); if (methodName == null) { for (int i = 1; ; i++) { methodName = "encode_" + serializerDef.getEncodeType().getSimpleName() .replace('[', 's') .replace(']', '_') + (i == 1 ? "" : "_" + i); if (defined.values().stream().noneMatch(methodName::equals)) break; } defined.put(key, methodName); classGenerator.withStaticMethod(methodName, int.class, List.of(byte[].class, int.class, serializerDef.getEncodeType()), sequence( serializerDef.encode(this, BUF, POS, VALUE, version, compatibilityLevel), POS)); } String finalMethodName = methodName; return (buf, pos, value) -> Expressions.set(pos, staticCallSelf(finalMethodName, buf, pos, value)); } }; } private StaticDecoders staticDecoders(ClassGenerator.Builder classGenerator, int version) { return new StaticDecoders() { final Map, String> defined = new HashMap<>(); @Override public Decoder define(SerializerDef serializerDef) { List key = List.of(identityHashCode(serializerDef), version, compatibilityLevel); String methodName = defined.get(key); if (methodName == null) { for (int i = 1; ; i++) { methodName = "decode_" + serializerDef.getDecodeType().getSimpleName() .replace('[', 's') .replace(']', '_') + ("_V" + version) + (i == 1 ? "" : "_" + i); if (defined.values().stream().noneMatch(methodName::equals)) break; } defined.put(key, methodName); classGenerator.withStaticMethod(methodName, serializerDef.getDecodeType(), List.of(BinaryInput.class), serializerDef.decode(this, IN, version, compatibilityLevel)); } String finalMethodName = methodName; return in -> staticCallSelf(finalMethodName, in); } }; } @SuppressWarnings("unchecked") private SerializerDef scan(Context ctx) { Map cache = (Map) ctx.getContextValue(); SerializerDef serializerDef = cache.get(ctx.getType()); if (serializerDef != null) return serializerDef; ForwardingSerializerDefImpl forwardingSerializerDef = new ForwardingSerializerDefImpl(); cache.put(ctx.getType(), forwardingSerializerDef); SerializerDef serializer = doScan(ctx); forwardingSerializerDef.serializerDef = serializer; return serializer; } private SerializerDef doScan(Context ctx) { Class rawClass = ctx.getRawType(); if (rawClass.isAnonymousClass()) throw new IllegalArgumentException("Class " + rawClass.getName() + " should not be anonymous"); if (rawClass.isLocalClass()) throw new IllegalArgumentException("Class " + rawClass.getName() + " should not be local"); if (rawClass.getEnclosingClass() != null && !Modifier.isStatic(rawClass.getModifiers())) throw new IllegalArgumentException("Class " + rawClass.getName() + "should not be an inner class"); ClassSerializerDef.Builder classSerializerBuilder = ClassSerializerDef.builder(rawClass); if (rawClass.getAnnotation(SerializeRecord.class) != null) { if (!rawClass.isRecord()) { throw new IllegalArgumentException( "Non-record type '" + rawClass.getName() + "' annotated with @SerializeRecord annotation"); } scanRecord(ctx, classSerializerBuilder); } else { scanStaticFactoryMethods(ctx, classSerializerBuilder); if (!Modifier.isAbstract(rawClass.getModifiers())) { scanConstructors(ctx, classSerializerBuilder); } scanClass(ctx, classSerializerBuilder); classSerializerBuilder.withMatchingSetters(); } return classSerializerBuilder.build(); } private void scanClass(Context ctx, ClassSerializerDef.Builder classSerializerBuilder) { AnnotatedType annotatedClassType = ctx.getAnnotatedType(); Class rawClassType = getRawType(annotatedClassType); Function, AnnotatedType> bindings = getTypeBindings(annotatedClassType)::get; if (rawClassType.getSuperclass() != Object.class) { scanClass(ctx.push(bind(rawClassType.getAnnotatedSuperclass(), bindings)), classSerializerBuilder); } List memberSerializers = new ArrayList<>(); scanFields(ctx, bindings, memberSerializers); scanGetters(ctx, bindings, memberSerializers); scanSetters(ctx, classSerializerBuilder); resolveMembersOrder(ctx.getRawType(), memberSerializers); addMemberSerializersToSerializerBuilder(classSerializerBuilder, memberSerializers); } private void scanRecord(Context ctx, ClassSerializerDef.Builder classSerializerBuilder) { Function, AnnotatedType> bindings = getTypeBindings(ctx.getAnnotatedType())::get; List memberSerializers = new ArrayList<>(); Class rawType = ctx.getRawType(); int order = 1; for (RecordComponent recordComponent : rawType.getRecordComponents()) { String name = recordComponent.getName(); Method method; try { method = rawType.getMethod(name); } catch (NoSuchMethodException e) { throw new IllegalArgumentException(e); } MemberSerializer memberSerializer = new MemberSerializer(method, order++, Serialize.DEFAULT_VERSION, Serialize.DEFAULT_VERSION); memberSerializer.serializer = ctx.scan(bind(recordComponent.getAnnotatedType(), bindings)); memberSerializers.add(memberSerializer); } classSerializerBuilder.withConstructor(rawType.getConstructors()[0], Arrays.stream(rawType.getRecordComponents()).map(RecordComponent::getName).toList()); resolveMembersOrder(ctx.getRawType(), memberSerializers); addMemberSerializersToSerializerBuilder(classSerializerBuilder, memberSerializers); } private void scanFields( Context ctx, Function, AnnotatedType> bindings, List memberSerializers ) { for (Field field : ctx.getRawType().getDeclaredFields()) { @Nullable SerializerFactory.MemberSerializer memberSerializer = findAnnotations(field, field.getAnnotations()); if (memberSerializer == null) continue; if (!isPublic(field.getModifiers())) throw new IllegalArgumentException(format("Field %s must be public", field)); if (isStatic(field.getModifiers())) throw new IllegalArgumentException(format("Field %s must not be static", field)); if (isTransient(field.getModifiers())) throw new IllegalArgumentException(format("Field %s must not be transient", field)); memberSerializer.serializer = ctx.scan(bind(field.getAnnotatedType(), bindings)); memberSerializers.add(memberSerializer); } } private void scanGetters( Context ctx, Function, AnnotatedType> bindings, List memberSerializers ) { for (Method method : ctx.getRawType().getDeclaredMethods()) { if (method.isBridge()) continue; @Nullable SerializerFactory.MemberSerializer memberSerializer = findAnnotations(method, method.getAnnotations()); if (memberSerializer == null) continue; if (!isPublic(method.getModifiers())) throw new IllegalArgumentException(format("Getter %s must be public", method)); if (isStatic(method.getModifiers())) throw new IllegalArgumentException(format("Getter %s must not be static", method)); if (method.getReturnType() == Void.TYPE || method.getParameterTypes().length != 0) throw new IllegalArgumentException(format("%s must be getter", method)); memberSerializer.serializer = ctx.scan(bind(method.getAnnotatedReturnType(), bindings)); memberSerializers.add(memberSerializer); } } private void scanSetters(Context ctx, ClassSerializerDef.Builder classSerializerBuilder) { for (Method method : ctx.getRawType().getDeclaredMethods()) { if (isStatic(method.getModifiers())) continue; if (method.getParameterTypes().length != 0) { List fields = extractFields(method); if (fields.size() == method.getParameterTypes().length) { classSerializerBuilder.withSetter(method, fields); } else { if (!fields.isEmpty()) throw new IllegalArgumentException("Fields should not be empty"); } } } } private void scanStaticFactoryMethods(Context ctx, ClassSerializerDef.Builder classSerializerBuilder) { Class factoryClassType = ctx.getRawType(); for (Method factory : factoryClassType.getDeclaredMethods()) { if (ctx.getRawType() != factory.getReturnType()) { continue; } if (factory.getParameterTypes().length != 0) { List fields = extractFields(factory); if (fields.size() == factory.getParameterTypes().length) { classSerializerBuilder.withStaticFactoryMethod(factory, fields); } else { if (!fields.isEmpty()) throw new IllegalArgumentException(format("@Deserialize is not fully specified for %s", fields)); } } } } private List extractFields(Method method) { List fields = new ArrayList<>(method.getParameterTypes().length); for (int i = 0; i < method.getParameterTypes().length; i++) { Annotation[] parameterAnnotations = method.getParameterAnnotations()[i]; Deserialize annotation = getAnnotation(parameterAnnotations, Deserialize.class); if (annotation != null) { String field = annotation.value(); fields.add(field); } } return fields; } private void scanConstructors(Context ctx, ClassSerializerDef.Builder classSerializerBuilder) { boolean found = false; for (Constructor constructor : ctx.getRawType().getDeclaredConstructors()) { List fields = new ArrayList<>(constructor.getParameterTypes().length); for (int i = 0; i < constructor.getParameterTypes().length; i++) { Deserialize annotation = getAnnotation(constructor.getParameterAnnotations()[i], Deserialize.class); if (annotation != null) { String field = annotation.value(); fields.add(field); } } if (constructor.getParameterTypes().length != 0 && fields.size() == constructor.getParameterTypes().length ) { if (found) throw new IllegalArgumentException(format("Duplicate @Deserialize constructor %s", constructor)); found = true; classSerializerBuilder.withConstructor(constructor, fields); } else { if (!fields.isEmpty()) throw new IllegalArgumentException(format("@Deserialize is not fully specified for %s", fields)); } } } private static void addMemberSerializersToSerializerBuilder(ClassSerializerDef.Builder classSerializerBuilder, List memberSerializers) { Set orders = new HashSet<>(); for (MemberSerializer memberSerializer : memberSerializers) { if (!orders.add(memberSerializer.order)) throw new IllegalArgumentException(format("Duplicate order %s for %s", memberSerializer.order, memberSerializer)); } Collections.sort(memberSerializers); for (MemberSerializer memberSerializer : memberSerializers) { if (memberSerializer.member instanceof Method) { classSerializerBuilder.withGetter((Method) memberSerializer.member, memberSerializer.serializer, memberSerializer.added, memberSerializer.removed); } else { classSerializerBuilder.withField((Field) memberSerializer.member, memberSerializer.serializer, memberSerializer.added, memberSerializer.removed); } } } private static void resolveMembersOrder(Class clazz, List memberSerializers) { if (memberSerializers.stream().noneMatch(f -> f.order == Integer.MIN_VALUE)) { return; } if (memberSerializers.stream().anyMatch(f -> f.order != Integer.MIN_VALUE)) { throw new IllegalArgumentException("Found mixed explicit and auto-ordering properties in " + clazz); } Map> membersMap = memberSerializers.stream().collect(groupingBy(MemberSerializer::getName, toList())); String pathToClass = clazz.getName().replace('.', '/') + ".class"; try (InputStream classInputStream = clazz.getClassLoader().getResourceAsStream(pathToClass)) { ClassReader cr = new ClassReader(requireNonNull(classInputStream)); cr.accept(new ClassVisitor(Opcodes.ASM8) { int index = 0; @Override public FieldVisitor visitField(int access, String name, String descriptor, String signature, Object value) { List list = membersMap.get(name); if (list == null) return null; for (MemberSerializer memberSerializer : list) { if (!(memberSerializer.member instanceof Field)) continue; memberSerializer.order = index++; break; } return null; } @Override public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) { List list = membersMap.get(name); if (list == null) return null; for (MemberSerializer memberSerializer : list) { if (!(memberSerializer.member instanceof Method)) continue; if (!descriptor.equals(getType((Method) memberSerializer.member).getDescriptor())) continue; memberSerializer.order = index++; break; } return null; } }, SKIP_CODE | SKIP_DEBUG | SKIP_FRAMES); } catch (IOException e) { throw new IllegalArgumentException(e); } } private @Nullable SerializerFactory.MemberSerializer findAnnotations(Member member, Annotation[] annotations) { int added = Serialize.DEFAULT_VERSION; int removed = Serialize.DEFAULT_VERSION; Serialize serialize = getAnnotation(annotations, Serialize.class); if (serialize != null) { added = serialize.added(); removed = serialize.removed(); } SerializeProfiles profiles = getAnnotation(annotations, SerializeProfiles.class); if (profiles != null) { if (!List.of(profiles.value()).contains(profile == null ? "" : profile)) { return null; } int addedProfile = getProfileVersion(profiles.value(), profiles.added()); if (addedProfile != SerializeProfiles.DEFAULT_VERSION) { added = addedProfile; } int removedProfile = getProfileVersion(profiles.value(), profiles.removed()); if (removedProfile != SerializeProfiles.DEFAULT_VERSION) { removed = removedProfile; } } return serialize != null ? new MemberSerializer(member, serialize.order(), added, removed) : null; } private int getProfileVersion(String[] profiles, int[] versions) { if (profiles == null || profiles.length == 0) { return SerializeProfiles.DEFAULT_VERSION; } for (int i = 0; i < profiles.length; i++) { if (Objects.equals(profile, profiles[i])) { if (i < versions.length) { return versions[i]; } return SerializeProfiles.DEFAULT_VERSION; } } return SerializeProfiles.DEFAULT_VERSION; } public static final class MemberSerializer implements Comparable { final Member member; int order; final int added; final int removed; // final TypedModsMap mods; SerializerDef serializer; private MemberSerializer(Member member, int order, int added, int removed) { this.member = member; this.order = order; this.added = added; this.removed = removed; } public String getName() { return member.getName(); } private int fieldRank() { return member instanceof Method ? 2 : 1; } @Override public int compareTo(MemberSerializer o) { int result = Integer.compare(this.order, o.order); if (result != 0) { return result; } result = Integer.compare(fieldRank(), o.fieldRank()); if (result != 0) { return result; } result = getName().compareTo(o.getName()); return result; } @Override public String toString() { return member.getClass().getSimpleName() + " " + getName(); } } public static class ForwardingSerializerDefImpl extends ForwardingSerializerDef { SerializerDef serializerDef; @Override protected SerializerDef serializer() { return serializerDef; } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy