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

io.github.jdcmp.codegen.EqualityComparators Maven / Gradle / Ivy

There is a newer version: 0.3
Show newest version
package io.github.jdcmp.codegen;

import io.github.jdcmp.api.HashParameters;
import io.github.jdcmp.api.MissingCriteriaException;
import io.github.jdcmp.api.builder.equality.EqualityFallbackMode;
import io.github.jdcmp.api.builder.equality.EqualityFallbackMode.FallbackMapper;
import io.github.jdcmp.api.comparator.equality.EqualityComparator;
import io.github.jdcmp.api.comparator.equality.SerializableEqualityComparator;
import io.github.jdcmp.api.documentation.NotThreadSafe;
import io.github.jdcmp.api.documentation.ThreadSafe;
import io.github.jdcmp.api.getter.EqualityCriterion;
import io.github.jdcmp.api.getter.SerializableEqualityCriterion;
import io.github.jdcmp.api.serialization.SerializationProxyRequiredException;
import io.github.jdcmp.api.spec.Spec;
import io.github.jdcmp.api.spec.equality.BaseEqualityComparatorSpec;
import io.github.jdcmp.api.spec.equality.EqualityComparatorSpec;
import io.github.jdcmp.api.spec.equality.SerializableEqualityComparatorSpec;
import io.github.jdcmp.codegen.Fallbacks.IdentityFallback;
import io.github.jdcmp.codegen.Fallbacks.SerializableIdentityFallback;
import io.github.jdcmp.codegen.bridge.StaticInitializerBridge;
import org.jetbrains.annotations.Nullable;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Type;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectStreamException;
import java.lang.invoke.MethodHandles.Lookup;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicInteger;

import static org.objectweb.asm.Opcodes.*;

@ThreadSafe
final class EqualityComparators {

	public static  EqualityComparator create(EqualityComparatorSpec userSpec, ImplSpec implSpec) {
		return NonSerializableImpl.create(userSpec, implSpec);
	}

	public static  SerializableEqualityComparator createSerializable(SerializableEqualityComparatorSpec userSpec, ImplSpec implSpec) {
		return SerializableImpl.create(userSpec, implSpec);
	}

	private static final class NonSerializableImpl {

		public static  EqualityComparator create(EqualityComparatorSpec userSpec, ImplSpec implSpec) {
			if (userSpec.hasNoGetters()) {
				return useFallback(userSpec);
			} else if (AsmGenerator.supports(userSpec)) {
				return AsmGenerator.generate(userSpec, implSpec);
			}

			return new ComparatorN<>(userSpec);
		}

		private static  EqualityComparator useFallback(EqualityComparatorSpec userSpec) {
			EqualityFallbackMode fallbackMode = userSpec.getFallbackMode().orElseThrow(MissingCriteriaException::of);

			return fallbackMode.map(new FallbackMapper>() {
				@Override
				public EqualityComparator onIdentity() {
					return new IdentityFallback<>(userSpec);
				}
			});
		}

	}

	private static final class SerializableImpl {

		static  SerializableEqualityComparator create(SerializableEqualityComparatorSpec userSpec, ImplSpec implSpec) {
			if (userSpec.hasNoGetters()) {
				return useFallback(userSpec);
			} else if (AsmGenerator.supports(userSpec)) {
				return AsmGenerator.generateSerializable(userSpec, implSpec);
			}

			return new SerializableComparatorN<>(userSpec, implSpec);
		}

		private static  SerializableEqualityComparator useFallback(SerializableEqualityComparatorSpec userSpec) {
			EqualityFallbackMode fallbackMode = userSpec.getFallbackMode().orElseThrow(MissingCriteriaException::of);

			return fallbackMode.map(new FallbackMapper>() {
				@Override
				public SerializableEqualityComparator onIdentity() {
					return new SerializableIdentityFallback<>(userSpec);
				}
			});
		}

	}

	static final class ComparatorN extends AbstractComparator implements EqualityComparator {

		ComparatorN(EqualityComparatorSpec userSpec) {
			super(userSpec);
		}

	}

	static final class SerializableComparatorN extends AbstractComparator implements SerializableEqualityComparator {

		private static final long serialVersionUID = 1L;

		private final transient SerializableEqualityComparatorSpec userSpec;

		private final transient ImplSpec implSpec;

		SerializableComparatorN(SerializableEqualityComparatorSpec userSpec, ImplSpec implSpec) {
			super(userSpec);
			this.userSpec = Objects.requireNonNull(userSpec);
			this.implSpec = Objects.requireNonNull(implSpec);
		}

		private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
			throw new SerializationProxyRequiredException();
		}

		private Object writeReplace() throws ObjectStreamException {
			implSpec.getSerializationMode().throwIfPrevented();

			return userSpec.toSerializedForm();
		}

	}

	static abstract class AbstractComparator implements EqualityComparator {

		private final Spec userSpec;

		protected AbstractComparator(Spec userSpec) {
			this.userSpec = Objects.requireNonNull(userSpec);
		}

		@Override
		public final int hash(@Nullable T object) {
			if (object == null) {
				return 0;
			}

			Spec spec = this.userSpec;

			if (spec.useStrictTypes()) {
				spec.getClassToCompare().cast(object);
			}

			HashParameters hashParameters = spec.getHashParameters();
			final int p = hashParameters.multiplier();
			int h = hashParameters.initialValue();

			for (EqualityCriterion getter : spec.getGetters()) {
				h = h * p + getter.hash(object);
			}

			return h;
		}

		@Override
		public final boolean areEqual(@Nullable T self, @Nullable Object other) {
			if (self == other) {
				return true;
			} else if (self == null || other == null) {
				return false;
			}

			Spec spec = this.userSpec;
			Class classToCompare = spec.getClassToCompare();

			if (spec.useStrictTypes()) {
				classToCompare.cast(self);
			}

			if (!classToCompare.isInstance(other)) {
				return false;
			}

			@SuppressWarnings("unchecked")
			T o = (T) other;

			for (EqualityCriterion getter : spec.getGetters()) {
				if (!getter.areEqual(self, o)) {
					return false;
				}
			}

			return true;
		}

	}

	@NotThreadSafe
	private static final class AsmGenerator, G extends EqualityCriterion>
			extends AbstractAsmGenerator> {

		private static final int MAX_SUPPORTED_GETTERS = 32;

		private static final AtomicInteger INSTANCE_COUNTER = new AtomicInteger();

		private static final Method SPEC_TO_SERIALIZED_FORM;

		private static final String SPEC_TO_SERIALIZED_FORM_DESCRIPTOR;

		private static final Method STATIC_INITIALIZER_BRIDGE;

		private static final Method STATIC_INITIALIZER_BRIDGE_SERIALIZABLE;

		private static final Type SPEC_NONSERIALIZABLE_TYPE = Type.getType(EqualityComparatorSpec.class);

		private static final Type SPEC_SERIALIZABLE_TYPE = Type.getType(SerializableEqualityComparatorSpec.class);

		private static final Type GETTER_NONSERIALIZABLE_TYPE = Type.getType(EqualityCriterion.class);

		private static final Type GETTER_SERIALIZABLE_TYPE = Type.getType(SerializableEqualityCriterion.class);

		static {
			try {
				SPEC_TO_SERIALIZED_FORM = SerializableEqualityComparatorSpec.class.getDeclaredMethod("toSerializedForm");
				SPEC_TO_SERIALIZED_FORM_DESCRIPTOR = Type.getMethodDescriptor(SPEC_TO_SERIALIZED_FORM);
				STATIC_INITIALIZER_BRIDGE = StaticInitializerBridge.class.getDeclaredMethod("equality", Lookup.class);
				STATIC_INITIALIZER_BRIDGE_SERIALIZABLE = StaticInitializerBridge.class.getDeclaredMethod("equalitySerializable", Lookup.class);
			} catch (Exception e) {
				throw new ExceptionInInitializerError(e);
			}
		}

		private final Class comparatorType;

		static boolean supports(Spec spec) {
			int getterCount = spec.getGetterCount();

			return getterCount > 0 && getterCount <= AsmGenerator.MAX_SUPPORTED_GETTERS;
		}

		static  EqualityComparator generate(EqualityComparatorSpec userSpec, ImplSpec implSpec) {
			AsmGenerator, EqualityCriterion> generator = new AsmGenerator<>(
					userSpec,
					implSpec,
					EqualityComparator.class);

			return generator.createInstance();
		}

		static  SerializableEqualityComparator generateSerializable(SerializableEqualityComparatorSpec userSpec, ImplSpec implSpec) {
			AsmGenerator, SerializableEqualityCriterion> generator = new AsmGenerator<>(
					userSpec,
					implSpec,
					SerializableEqualityComparator.class);

			return generator.createInstance();
		}

		private AsmGenerator(BaseEqualityComparatorSpec userSpec, ImplSpec implSpec, Class comparatorType) {
			super(userSpec, implSpec);
			this.comparatorType = Objects.requireNonNull(comparatorType);
		}

		@Override
		protected String classNamePrefix() {
			return "GeneratedEqualityComparator";
		}

		@Override
		protected int classNameSuffix() {
			return INSTANCE_COUNTER.getAndIncrement();
		}

		@Override
		protected Class classToCompare() {
			return userSpec.getClassToCompare();
		}

		@Override
		protected Type specType() {
			return isSerializable() ? SPEC_SERIALIZABLE_TYPE : SPEC_NONSERIALIZABLE_TYPE;
		}

		@Override
		protected Class comparatorClass() {
			return comparatorType;
		}

		@Override
		protected boolean validate(BaseEqualityComparatorSpec userSpec) {
			return supports(userSpec);
		}

		@Override
		protected HashParameters hashParameters() {
			return userSpec.getHashParameters();
		}

		@Override
		protected boolean strictTypes() {
			return userSpec.useStrictTypes();
		}

		@Override
		protected void addCompatibleSerializationMethod(ClassWriter cw, ClassDescription cd) {
			MethodVisitor mv = cw.visitMethod(ACC_PRIVATE, "writeReplace", "()Ljava/lang/Object;", null, null);
			mv.visitCode();

			mv.visitFieldInsn(GETSTATIC, cd.generatedInternalName, "spec", consts.specDescriptor);
			mv.visitMethodInsn(
					INVOKEINTERFACE,
					consts.specInternalName,
					SPEC_TO_SERIALIZED_FORM.getName(),
					SPEC_TO_SERIALIZED_FORM_DESCRIPTOR,
					true);

			endReturn(mv, ARETURN);
		}

		@Override
		protected void customize(ClassWriter classWriter, ClassDescription classDescription) {
		}

		@Override
		protected Collection getters() {
			return userSpec.getGetters();
		}

		@Override
		protected Type getterType() {
			return isSerializable() ? GETTER_SERIALIZABLE_TYPE : GETTER_NONSERIALIZABLE_TYPE;
		}

		@Override
		protected Method getStaticInitializerBridgeMethod() {
			return isSerializable() ? STATIC_INITIALIZER_BRIDGE_SERIALIZABLE : STATIC_INITIALIZER_BRIDGE;
		}

	}

	private EqualityComparators() {
		throw new AssertionError("No instances");
	}

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy