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

io.github.jdcmp.codegen.AbstractAsmGenerator 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.documentation.NotThreadSafe;
import io.github.jdcmp.api.serialization.SerializationDisabledException;
import io.github.jdcmp.api.serialization.SerializationProxyRequiredException;
import io.github.jdcmp.api.spec.Spec;
import io.github.jdcmp.codegen.ClassDefiner.ClassDefinition;
import io.github.jdcmp.codegen.bridge.StaticInitializerBridge;
import io.github.jdcmp.codegen.contract.EventHandler;
import io.github.jdcmp.codegen.customization.AvailableInitializationMode;
import io.github.jdcmp.codegen.customization.AvailableSerializationMode;
import org.objectweb.asm.*;

import java.io.IOException;
import java.io.Serializable;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodHandles.Lookup;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.Callable;

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

/**
 * Main area for bytecode generation.
 *
 * @param  Type whose instances can be compared
 * @param  Type of the generated comparator
 * @param  Type of the user spec
 */
@NotThreadSafe
abstract class AbstractAsmGenerator> {

	protected final U userSpec;

	protected final ImplSpec implSpec;

	private final EventHandler eventHandler;

	protected Consts consts;

	protected AbstractAsmGenerator(U userSpec, ImplSpec implSpec) {
		if (!validate(userSpec)) {
			throw new IllegalArgumentException("Unsupported user specification");
		}

		this.userSpec = userSpec;
		this.implSpec = Objects.requireNonNull(implSpec);
		this.eventHandler = Objects.requireNonNull(implSpec.getEventHandler());
	}

	abstract protected String classNamePrefix();

	abstract protected int classNameSuffix();

	abstract protected Class classToCompare();

	abstract protected Class comparatorClass();

	abstract protected Type specType();

	abstract protected boolean validate(U userSpec);

	abstract protected HashParameters hashParameters();

	abstract protected Collection getters();

	abstract protected boolean strictTypes();

	abstract protected Type getterType();

	abstract protected void addCompatibleSerializationMethod(ClassWriter classWriter, ClassDescription classDescription);

	abstract protected void customize(ClassWriter classWriter, ClassDescription classDescription);

	abstract protected Method getStaticInitializerBridgeMethod();

	public final C createInstance() {
		this.consts = new Consts(this);
		List> strategies = createStrategies();

		if (strategies.isEmpty()) {
			throw new IllegalStateException("Cannot generate class, no strategies are available.");
		}

		return FallbackStrategy.of(strategies).apply(Callable::call);
	}

	private List> createStrategies() {
		ArrayList> strategies = new ArrayList<>(16);
		addVmAnonymousStrategies(strategies);
		addLookupHiddenWithClassDataStrategies(strategies);
		addStaticResolverStrategies(strategies);

		return strategies;
	}

	private void addVmAnonymousStrategies(ArrayList> list) {
		implSpec.getClassDefiners().getVmAnonymousClassDefiner().ifPresent(classDefiner -> {
			for (Instantiator instantiator : implSpec.getInstantiators()) {
				if (instantiator.supportsVmAnonOrHiddenClasses()) {
					list.add(() -> generate(new VmAnonymousStrategy(classDefiner, instantiator)));
				}
			}
		});
	}

	private void addLookupHiddenWithClassDataStrategies(ArrayList> list) {
		if (AvailableInitializationMode.EXTERNAL.equals(implSpec.getInitializationMode())) {
			return;
		}

		implSpec.getClassDefiners().getLookupHiddenClassWithClassDataDefiner().ifPresent(classDefiner -> {
			for (Instantiator instantiator : implSpec.getInstantiators()) {
				if (instantiator.supportsVmAnonOrHiddenClasses()) {
					list.add(() -> generate(new LookupHiddenWithClassDataStrategy(classDefiner, instantiator)));
				}
			}
		});
	}

	private void addStaticResolverStrategies(ArrayList> list) {
		for (ClassDefiner classDefiner : implSpec.getClassDefiners().all()) {
			for (Instantiator instantiator : implSpec.getInstantiators()) {
				list.add(() -> generate(new StaticResolverStrategy(classDefiner, instantiator)));
			}
		}
	}

	private C generate(Genesis genesis) {
		byte[] classBytes = genesis.generate();
		Class generatedClass = genesis.define(classBytes);
		C instance = genesis.createInstance(generatedClass);
		handleEvents(generatedClass, classBytes);

		return instance;
	}

	private void handleEvents(Class generatedClass, byte[] classBytes) {
		try {
			eventHandler.onClassInstantiated(generatedClass, classBytes);
		} catch (RuntimeException e) {
			throw e;
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
	}

	protected int getterCount() {
		return getters().size();
	}

	protected boolean isSerializable() {
		return Serializable.class.isAssignableFrom(comparatorClass());
	}

	protected static void insertNumber(MethodVisitor mv, int value) {
		if (value >= -1 && value <= 5) {
			mv.visitInsn(ICONST_0 + value);
		} else if (value >= Byte.MIN_VALUE && value <= Byte.MAX_VALUE) {
			mv.visitIntInsn(BIPUSH, value);
		} else if (value >= Short.MIN_VALUE && value <= Short.MAX_VALUE) {
			mv.visitIntInsn(SIPUSH, value);
		} else {
			mv.visitLdcInsn(value);
		}
	}

	protected static void endReturn(MethodVisitor mv, int returnOpcode) {
		mv.visitInsn(returnOpcode);
		mv.visitMaxs(0, 0);
		mv.visitEnd();
	}

	private abstract class Genesis {

		protected final ClassDescription cd = new ClassDescription();

		protected final Instantiator instantiator;

		protected Genesis(Instantiator instantiator) {
			this.instantiator = Objects.requireNonNull(instantiator);
		}

		abstract protected Class specificDefineClass(byte[] bytes);

		abstract protected int classVersion();

		abstract protected void visitStaticInitializerLoadSpec(MethodVisitor mv);

		public C createInstance(Class generatedClass) {
			return instantiator.newInstance(generatedClass);
		}

		public Class define(byte[] bytes) {
			Class generatedClass = specificDefineClass(bytes);

			if (AvailableInitializationMode.EXTERNAL.equals(implSpec.getInitializationMode())) {
				initializeFieldsExternally(generatedClass);
			}

			return generatedClass;
		}

		private void initializeFieldsExternally(Class generatedClass) {
			ExternalFieldInitializer fieldInitializer = new ExternalFieldInitializer(generatedClass);

			if (isSerializable()) {
				fieldInitializer.addSpec(userSpec);
			}

			fieldInitializer.addClassToCompare(classToCompare());
			fieldInitializer.addGetters(getters());
		}

		public final byte[] generate() {
			ClassWriter cw = createClassWriter();
			addFields(cw);
			addStaticInitializerIfNecessary(cw);
			addConstructorIfNecessary(cw);
			addInterfaceImpl(cw);
			customize(cw, cd);

			return end(cw);
		}

		private byte[] end(ClassWriter cw) {
			cw.visitEnd();

			return cw.toByteArray();
		}

		protected ClassWriter createClassWriter() {
			String[] interfaces = consts.interfaces;
			ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS | ClassWriter.COMPUTE_FRAMES);
			cw.visit(classVersion(), Consts.ACCESS_CLASS, cd.generatedInternalName, cd.generatedSignature, "java/lang/Object", interfaces);

			return cw;
		}

		private void addFields(ClassWriter cw) {
			int access = Consts.ACCESS_FIELD;

			if (isSerializable()) {
				cw.visitField(access, "spec", consts.specDescriptor, consts.specSignature, null).visitEnd();
			}

			String classToCompareSignature = consts.classToCompareSignature;
			cw.visitField(access, "classToCompare", "Ljava/lang/Class;", classToCompareSignature, null).visitEnd();

			String getterDescriptor = consts.getterDescriptor;
			String getterSignature = consts.getterSignature;
			for (int i = 0; i < getterCount(); ++i) {
				cw.visitField(access, "getter" + i, getterDescriptor, getterSignature, null).visitEnd();
			}
		}

		private void addStaticInitializerIfNecessary(ClassWriter cw) {
			implSpec.getInitializationMode().map(new AvailableInitializationMode.InitializationModeMapper() {
				@Override
				public Void onStaticInitializer() {
					addStaticInitializer(cw);
					return null;
				}

				@Override
				public Void onExternal() {
					return null;
				}
			});
		}

		protected final void addStaticInitializer(ClassWriter cw) {
			MethodVisitor mv = cw.visitMethod(ACC_STATIC, "", "()V", null, null);
			mv.visitCode();

			visitStaticInitializerLoadSpec(mv);
			visitStaticInitializationFromSpec(mv);

			endReturn(mv, RETURN);
		}

		private void visitStaticInitializationFromSpec(MethodVisitor mv) {
			FromSpecFieldInitializer init = new FromSpecFieldInitializer(mv, cd.generatedInternalName, consts.specType, consts);

			if (isSerializable()) {
				mv.visitInsn(DUP);
				init.addSpec(userSpec);
			}

			mv.visitInsn(DUP);
			init.addClassToCompare(classToCompare());
			init.addGetters(getters());
		}

		private void addConstructorIfNecessary(ClassWriter cw) {
			if (!instantiator.requiresConstructor()) {
				return;
			}

			MethodVisitor mv = cw.visitMethod(ACC_PRIVATE, "", "()V", null, null);
			mv.visitCode();
			mv.visitVarInsn(ALOAD, 0);
			mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "", "()V", false);
			endReturn(mv, RETURN);
		}

		private void addInterfaceImpl(ClassWriter cw) {
			new Hash(cd).addTo(cw);
			new AreEqual(cd).addTo(cw);

			if (isSerializable()) {
				new Serialization(cd).addTo(cw);
			}
		}

	}

	private final class VmAnonymousStrategy extends Genesis {

		private final ClassDefiners.VMAnonymousClassDefiner classDefiner;

		private int constantPoolIndex;

		VmAnonymousStrategy(ClassDefiners.VMAnonymousClassDefiner classDefiner, Instantiator instantiator) {
			super(instantiator);
			this.classDefiner = Objects.requireNonNull(classDefiner);
		}

		@Override
		protected int classVersion() {
			return V1_8;
		}

		@Override
		protected ClassWriter createClassWriter() {
			ClassWriter cw = super.createClassWriter();
			constantPoolIndex = cw.newConst("PLACEHOLDER_SPEC");

			return cw;
		}

		@Override
		protected void visitStaticInitializerLoadSpec(MethodVisitor mv) {
			mv.visitLdcInsn("PLACEHOLDER_SPEC");
			mv.visitTypeInsn(CHECKCAST, consts.specInternalName);
		}

		@Override
		protected Class specificDefineClass(byte[] bytes) {
			Lookup host = implSpec.getLookup();
			ClassDefinition classDefinition = new ClassDefinition(bytes, cd.generatedInternalName, host);
			Object[] constantPoolPatches = createConstantPoolPatches(bytes);

			return classDefiner.defineClass(classDefinition, constantPoolPatches);
		}

		private Object[] createConstantPoolPatches(byte[] bytes) {
			int cpi = constantPoolIndex;
			Object[] constantPoolPatches = new Object[cpi + 1];
			for (int i = 0; i < getterCount(); ++i) {
				constantPoolPatches[cpi] = new Object();
			}

			return constantPoolPatches;
		}

	}

	private final class LookupHiddenWithClassDataStrategy extends Genesis {

		final ClassDefiners.LookupHiddenClassWithClassDataDefiner classDefiner;

		LookupHiddenWithClassDataStrategy(ClassDefiners.LookupHiddenClassWithClassDataDefiner classDefiner, Instantiator instantiator) {
			super(instantiator);
			this.classDefiner = Objects.requireNonNull(classDefiner);
		}

		@Override
		protected int classVersion() {
			return V16;
		}

		@Override
		protected void visitStaticInitializerLoadSpec(MethodVisitor mv) {
			Handle handle = LookupHiddenHolder.CLASS_DATA_HANDLE;

			mv.visitLdcInsn(new ConstantDynamic("_", consts.specDescriptor, handle));
		}

		@Override
		protected Class specificDefineClass(byte[] bytes) {
			ClassDefinition classDefinition = new ClassDefinition(bytes, cd.generatedInternalName, implSpec.getLookup());

			return classDefiner.defineClass(classDefinition, userSpec);
		}

	}

	private static final class LookupHiddenHolder {

		static final Handle CLASS_DATA_HANDLE;

		static final ConstantDynamic CLASS_DATA_CONSTANT_DYNAMIC;

		static final String CLASS_DATA_DESCRIPTOR;

		static {
			try {
				Method classData = MethodHandles.class.getDeclaredMethod("classData", Lookup.class, String.class, Class.class);
				String owner = Type.getInternalName(MethodHandles.class);
				String descriptor = Type.getMethodDescriptor(classData);
				CLASS_DATA_DESCRIPTOR = descriptor;
				CLASS_DATA_HANDLE = new Handle(H_INVOKESTATIC, owner, "classData", descriptor, false);
				CLASS_DATA_CONSTANT_DYNAMIC = null;
			} catch (Exception e) {
				throw new ExceptionInInitializerError(e);
			}
		}

	}

	private final class StaticResolverStrategy extends Genesis {

		private final ClassDefiner classDefiner;

		private final Method bridgeMethod = getStaticInitializerBridgeMethod();

		StaticResolverStrategy(ClassDefiner classDefiner, Instantiator instantiator) {
			super(instantiator);
			this.classDefiner = Objects.requireNonNull(classDefiner);
		}

		@Override
		protected int classVersion() {
			return V1_8;
		}

		@Override
		protected void visitStaticInitializerLoadSpec(MethodVisitor mv) {
			mv.visitMethodInsn(
					INVOKESTATIC,
					"java/lang/invoke/MethodHandles",
					"lookup",
					"()Ljava/lang/invoke/MethodHandles$Lookup;",
					false);
			mv.visitMethodInsn(
					INVOKESTATIC,
					Consts.STATIC_INITIALIZER_BRIDGE_INTERNAL_NAME,
					bridgeMethod.getName(),
					Type.getMethodDescriptor(bridgeMethod),
					false);
		}

		@Override
		protected Class specificDefineClass(byte[] bytes) {
			ClassDefinition classDefinition = new ClassDefinition(bytes, cd.generatedInternalName, implSpec.getLookup());

			return classDefiner.defineClass(classDefinition);
		}

		@Override
		public C createInstance(Class generatedClass) {
			return StaticInitializerBridge.run(generatedClass, userSpec, () -> instantiator.newInstance(generatedClass));
		}

	}

	private final class Serialization {

		private final ClassDescription cd;

		Serialization(ClassDescription cd) {
			this.cd = Objects.requireNonNull(cd);
		}

		void addTo(ClassWriter cw) {
			implSpec.getSerializationMode().map(new AvailableSerializationMode.SerializationModeMapper() {
				@Override
				public Void onCompatible() {
					addCompatibleSerializationMethod(cw, cd);
					return null;
				}

				@Override
				public Void onIncompatible() {
					return null; // do nothing, care neither about supporting nor preventing serialization
				}

				@Override
				public Void onHostile() {
					addHostileSerializationMethods(cw);
					return null;
				}
			});
		}

		private void addHostileSerializationMethods(ClassWriter cw) {
			addHostileReadObject(cw);
			addHostileWriteObject(cw);
		}

		private void addHostileReadObject(ClassWriter classWriter) {
			String exceptionName = Consts.READ_OBJECT_EXCEPTION_NAME;
			MethodVisitor mv = classWriter.visitMethod(ACC_PRIVATE, "readObject",
					"(Ljava/io/ObjectInputStream;)V", null, Consts.READ_OBJECT_EXCEPTIONS);
			mv.visitCode();
			mv.visitVarInsn(ALOAD, 0);
			mv.visitTypeInsn(NEW, exceptionName);
			mv.visitInsn(DUP);
			mv.visitMethodInsn(INVOKESPECIAL, exceptionName, "", "()V", false);
			endReturn(mv, ATHROW);
		}

		private void addHostileWriteObject(ClassWriter cw) {
			String exceptionName = Consts.WRITE_OBJECT_EXCEPTION_NAME;
			MethodVisitor mv = cw.visitMethod(ACC_PRIVATE, "writeObject",
					"(Ljava/io/ObjectOutputStream;)V", null, Consts.WRITE_OBJECT_EXCEPTIONS);
			mv.visitCode();
			mv.visitVarInsn(ALOAD, 0);
			mv.visitTypeInsn(NEW, exceptionName);
			mv.visitInsn(DUP);
			mv.visitMethodInsn(INVOKESPECIAL, exceptionName, "", "()V", false);
			endReturn(mv, ATHROW);
		}

	}

	private final class Hash {

		final String descriptorNoBridge = "(Ljava/lang/Object;)I";

		final ClassDescription cd;

		final String descriptorBridge;

		final int getterCount = getterCount();

		final HashParameters hashParameters = hashParameters();

		final int hashMultiplier = hashParameters.multiplier();

		Hash(ClassDescription cd) {
			this.cd = cd;
			this.descriptorBridge = "(" + consts.classToCompareDescriptor + ")I";
		}

		private void addTo(ClassWriter cw) {
			final String descriptor = implSpec.generateBridgeMethods() ? descriptorBridge : descriptorNoBridge;
			final String signature = "(" + consts.classToCompareDescriptor + ")I";
			final String getterInternalName = getterType().getInternalName();
			final String getterDescriptor = getterType().getDescriptor();
			final String hashDescriptor = descriptorNoBridge;

			MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "hash", descriptor, signature, null);
			mv.visitCode();

			mv.visitVarInsn(ALOAD, 1);
			Label label0 = new Label();
			mv.visitJumpInsn(IFNONNULL, label0);
			mv.visitInsn(ICONST_0);
			mv.visitInsn(IRETURN);
			mv.visitLabel(label0);

			int[] multipliers = calculateMultipliers();
			insertNumber(mv, hashParameters.initialValue() * multipliers[0]);

			for (int i = 0; i < getterCount; ++i) {
				mv.visitFieldInsn(GETSTATIC, cd.generatedInternalName, "getter" + i, getterDescriptor);

				if (strictTypes() && i == 0) {
					mv.visitFieldInsn(GETSTATIC, cd.generatedInternalName, "classToCompare", Consts.CLASS_DESCRIPTOR);
					mv.visitVarInsn(ALOAD, 1);
					mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Class", "cast", "(Ljava/lang/Object;)Ljava/lang/Object;", false);
				} else {
					mv.visitVarInsn(ALOAD, 1);
				}

				mv.visitMethodInsn(INVOKEINTERFACE, getterInternalName, "hash", hashDescriptor, true);
				if (i < getterCount - 1) {
					insertNumber(mv, multipliers[i + 1]);
					mv.visitInsn(IMUL);
				}
				mv.visitInsn(IADD);
			}

			endReturn(mv, IRETURN);

			if (implSpec.generateBridgeMethods()) {
				addBridgeMethod(cw);
			}
		}

		private void addBridgeMethod(ClassWriter cw) {
			MethodVisitor mv = cw.visitMethod(Consts.ACCESS_BRIDGE, "hash", "(Ljava/lang/Object;)I", null, null);
			mv.visitCode();

			mv.visitVarInsn(ALOAD, 0);
			mv.visitVarInsn(ALOAD, 1);
			mv.visitTypeInsn(CHECKCAST, consts.classToCompareInternalName);
			String targetDescriptor = "(" + consts.classToCompareDescriptor + ")I";
			mv.visitMethodInsn(INVOKEVIRTUAL, cd.generatedInternalName, "hash", targetDescriptor, false);

			endReturn(mv, IRETURN);
		}

		private int[] calculateMultipliers() {
			/*
				Pre-calculate the {hashInitialValue * hashMultiplier * ...} chain for each level

				h(n) = h(n - 1) * hashMultiplier + object.hashCode()
				h(0) = hashInitialValue * hashMultiplier + a.hashCode()
				h(1) = h(0) * hashMultiplier + b.hashCode()
					 = (hashInitialValue * hashMultiplier + a.hashCode()) * hashMultiplier + b.hashCode()
					 = hashInitialValue * hashMultiplier * hashMultiplier + a.hashCode() * hashMultiplier + b.hashCode()
					 = CONSTANT + a.hashCode() * hashMultiplier + b.hashCode()

				Trade-off: Balance reduced number of calculations vs increased bytecode size
			 */

			int[] multipliers = new int[getterCount + 1];
			multipliers[getterCount] = 1;
			for (int i = getterCount - 1; i >= 0; --i) {
				multipliers[i] = hashMultiplier * multipliers[i + 1];
			}
			return multipliers;
		}

	}

	private final class AreEqual {

		final String descriptorNoBridge = "(Ljava/lang/Object;Ljava/lang/Object;)Z";

		final ClassDescription cd;

		final String descriptorTypeSafe;

		AreEqual(ClassDescription cd) {
			this.cd = Objects.requireNonNull(cd);
			this.descriptorTypeSafe = "(" + consts.classToCompareDescriptor + "Ljava/lang/Object;)Z";
		}

		private void addTo(ClassWriter cw) {
			final String descriptor = implSpec.generateBridgeMethods() ? descriptorTypeSafe : descriptorNoBridge;
			final String signature = descriptorTypeSafe;
			final String getterInternalName = getterType().getInternalName();
			final String getterDescriptor = getterType().getDescriptor();
			final String areEqualDescriptor = descriptorNoBridge;

			MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "areEqual", descriptor, signature, null);
			mv.visitCode();

			Label label0 = new Label();
			mv.visitLabel(label0);

			if (strictTypes()) {
				mv.visitFieldInsn(GETSTATIC, cd.generatedInternalName, "classToCompare", "Ljava/lang/Class;");
				mv.visitInsn(DUP);
				mv.visitVarInsn(ASTORE, 3);
				mv.visitVarInsn(ALOAD, 1);
				mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Class", "cast", "(Ljava/lang/Object;)Ljava/lang/Object;", false);
			} else {
				mv.visitVarInsn(ALOAD, 1);
			}

			mv.visitVarInsn(ALOAD, 2);
			Label label1 = new Label();
			mv.visitJumpInsn(IF_ACMPNE, label1);

			Label label2 = new Label();
			mv.visitLabel(label2);
			mv.visitInsn(ICONST_1);
			mv.visitInsn(IRETURN);

			mv.visitLabel(label1);
			mv.visitFrame(F_SAME, 0, null, 0, null);
			mv.visitVarInsn(ALOAD, 1);
			Label label3 = new Label();
			mv.visitJumpInsn(IFNULL, label3);
			if (strictTypes()) {
				mv.visitVarInsn(ALOAD, 3);
			} else {
				mv.visitFieldInsn(GETSTATIC, cd.generatedInternalName, "classToCompare", "Ljava/lang/Class;");
			}
			mv.visitVarInsn(ALOAD, 2);
			mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Class", "isInstance", "(Ljava/lang/Object;)Z", false);
			Label label4 = new Label();
			mv.visitJumpInsn(IFNE, label4);
			mv.visitLabel(label3);
			mv.visitFrame(F_SAME, 0, null, 0, null);
			mv.visitInsn(ICONST_0);
			mv.visitInsn(IRETURN);
			mv.visitLabel(label4);
			mv.visitFrame(F_SAME, 0, null, 0, null);
			mv.visitVarInsn(ALOAD, 2);
			mv.visitVarInsn(ASTORE, 3);

			Label label5 = new Label();
			mv.visitLabel(label5);
			mv.visitFieldInsn(GETSTATIC, cd.generatedInternalName, "getter0", getterDescriptor);
			mv.visitVarInsn(ALOAD, 1);
			mv.visitVarInsn(ALOAD, 3);
			mv.visitMethodInsn(INVOKEINTERFACE, getterInternalName, "areEqual", areEqualDescriptor, true);

			Label label6 = new Label();

			for (int i = 1; i < getterCount(); ++i) {
				mv.visitJumpInsn(IFEQ, label6);
				mv.visitFieldInsn(GETSTATIC, cd.generatedInternalName, "getter" + i, getterDescriptor);
				mv.visitVarInsn(ALOAD, 1);
				mv.visitVarInsn(ALOAD, 3);
				mv.visitMethodInsn(INVOKEINTERFACE, getterInternalName, "areEqual", areEqualDescriptor, true);
			}

			mv.visitJumpInsn(IFEQ, label6);

			Label label9 = new Label();
			mv.visitLabel(label9);
			mv.visitInsn(ICONST_1);
			mv.visitInsn(IRETURN);
			mv.visitLabel(label6);
			mv.visitFrame(F_APPEND, 1, new Object[] {"java/lang/Object"}, 0, null);
			mv.visitInsn(ICONST_0);
			mv.visitInsn(IRETURN);

			Label label10 = new Label();
			mv.visitLabel(label10);
			mv.visitLocalVariable("self", "Ljava/lang/Object;", "TT;", label0, label10, 1);
			mv.visitLocalVariable("other", "Ljava/lang/Object;", null, label0, label10, 2);
			mv.visitLocalVariable("o", "Ljava/lang/Object;", "TT;", label5, label10, 3);
			mv.visitMaxs(0, 0);
			mv.visitEnd();

			if (implSpec.generateBridgeMethods()) {
				addBridgeMethod(cw);
			}
		}

		private void addBridgeMethod(ClassWriter cw) {
			MethodVisitor mv = cw.visitMethod(Consts.ACCESS_BRIDGE, "areEqual", "(Ljava/lang/Object;Ljava/lang/Object;)Z", null, null);
			mv.visitCode();

			mv.visitVarInsn(ALOAD, 0);
			mv.visitVarInsn(ALOAD, 1);
			mv.visitTypeInsn(CHECKCAST, consts.classToCompareInternalName);
			mv.visitVarInsn(ALOAD, 2);
			String targetDescriptor = "(" + consts.classToCompareDescriptor + "Ljava/lang/Object;)Z";
			mv.visitMethodInsn(INVOKEVIRTUAL, cd.generatedInternalName, "areEqual", targetDescriptor, false);

			endReturn(mv, IRETURN);
		}

	}

	protected final class ClassDescription {

		private final Class classToCompare = classToCompare();

		final String generatedInternalName = createInternalName();

		final String generatedSignature = createSignature();

		private String createInternalName() {
			String lookupClassName = Type.getInternalName(implSpec.getLookup().lookupClass());
			String classToCompare = classToCompareName();
			String prefix = Objects.requireNonNull(classNamePrefix());
			int suffix = classNameSuffix();

			return lookupClassName + "$$" + prefix + "$$" + classToCompare + "#" + suffix;
		}

		private String createSignature() {
			String signature = "Ljava/lang/Object;L";
			signature += consts.comparatorClassInternalName;
			signature += " clazz) {
			return clazz.isArray() ? arrayName(clazz) : clazz.getName().replace('.', '$');
		}

		private String arrayName(Class arrayClass) {
			Class componentType = arrayClass.getComponentType();

			if (boolean.class == componentType) {
				return "booleanArray";
			} else if (byte.class == componentType) {
				return "byteArray";
			} else if (char.class == componentType) {
				return "charArray";
			} else if (double.class == componentType) {
				return "doubleArray";
			} else if (float.class == componentType) {
				return "floatArray";
			} else if (int.class == componentType) {
				return "intArray";
			} else if (long.class == componentType) {
				return "longArray";
			} else if (short.class == componentType) {
				return "shortArray";
			} else {
				return className(componentType) + "Array";
			}
		}

	}

	private static final class ExternalFieldInitializer implements FieldInitializer {

		private final Class generatedClass;

		ExternalFieldInitializer(Class generatedClass) {
			this.generatedClass = Objects.requireNonNull(generatedClass);
		}

		@Override
		public void addClassToCompare(Object classToCompare) {
			Internals.setStaticFieldVolatile(generatedClass, "classToCompare", classToCompare);
		}

		@Override
		public void addGetters(Collection getters) {
			int i = 0;
			for (Object getter : getters) {
				Internals.setStaticFieldVolatile(generatedClass, "getter" + i++, getter);
			}
		}

		@Override
		public void addSpec(Object spec) {
			Internals.setStaticFieldVolatile(generatedClass, "spec", spec);
		}

	}

	private static final class FromSpecFieldInitializer implements FieldInitializer {

		private final MethodVisitor mv;

		private final String owner;

		private final Type specType;

		private final String specName;

		private final Consts consts;

		FromSpecFieldInitializer(MethodVisitor mv, String owner, Type specType, Consts consts) {
			this.mv = Objects.requireNonNull(mv);
			this.owner = Objects.requireNonNull(owner);
			this.specType = specType;
			this.specName = specType.getInternalName();
			this.consts = Objects.requireNonNull(consts);
		}

		@Override
		public void addClassToCompare(Object classToCompare) {
			mv.visitMethodInsn(INVOKEINTERFACE, specName, "getClassToCompare", "()Ljava/lang/Class;", true);
			mv.visitFieldInsn(PUTSTATIC, owner, "classToCompare", Consts.CLASS_DESCRIPTOR);
		}

		@Override
		public void addGetters(Collection getters) {
			mv.visitMethodInsn(INVOKEINTERFACE, specName, "getGettersAsList", "()Ljava/util/List;", true);
			String getterDescriptor = consts.getterDescriptor;
			int getterCount = getters.size();

			for (int i = 0; i < getterCount; ++i) {
				if (i < getterCount - 1) {
					mv.visitInsn(DUP);
				}
				insertNumber(mv, i);
				mv.visitMethodInsn(INVOKEINTERFACE, "java/util/List", "get", "(I)Ljava/lang/Object;", true);
				mv.visitFieldInsn(PUTSTATIC, owner, "getter" + i, getterDescriptor);
			}
		}

		@Override
		public void addSpec(Object spec) {
			mv.visitFieldInsn(PUTSTATIC, owner, "spec", specType.getDescriptor());
		}

	}

	protected interface FieldInitializer {

		void addClassToCompare(Object classToCompare);

		void addGetters(Collection getters);

		void addSpec(Object spec);

	}

	protected static final class Consts {

		public static final Type CLASS_TYPE = Type.getType(Class.class);

		public static final String CLASS_DESCRIPTOR = CLASS_TYPE.getDescriptor();

		public static final int ACCESS_CLASS = ACC_FINAL + ACC_SUPER + ACC_SYNTHETIC;

		public static final int ACCESS_FIELD = ACC_PRIVATE + ACC_FINAL + ACC_STATIC + ACC_TRANSIENT;

		public static final int ACCESS_BRIDGE = ACC_PUBLIC | ACC_BRIDGE | ACC_SYNTHETIC;

		public static final String STATIC_INITIALIZER_BRIDGE_INTERNAL_NAME = Type.getInternalName(StaticInitializerBridge.class);

		public static final String[] READ_OBJECT_EXCEPTIONS = new String[] {
				Type.getInternalName(IOException.class),
				Type.getInternalName(ClassNotFoundException.class)
		};

		public static final String READ_OBJECT_EXCEPTION_NAME = Type.getInternalName(SerializationProxyRequiredException.class);

		public static final String[] WRITE_OBJECT_EXCEPTIONS = new String[] {
				Type.getInternalName(IOException.class)
		};

		public static final String WRITE_OBJECT_EXCEPTION_NAME = Type.getInternalName(SerializationDisabledException.class);

		public final String[] interfaces;

		public final String classToCompareDescriptor;

		public final String classToCompareInternalName;

		public final String classToCompareSignature;

		public final Class comparatorClass;

		public final String comparatorClassInternalName;

		public final Type getterType;

		public final String getterDescriptor;

		public final String getterInternalName;

		public final String getterSignature;

		public final Type specType;

		public final String specInternalName;

		public final String specDescriptor;

		public final String specSignature;

		Consts(AbstractAsmGenerator generator) {
			Class comparatorClass = generator.comparatorClass();
			String comparatorClassInternalName = Type.getInternalName(comparatorClass);
			Class classToCompare = generator.classToCompare();

			this.interfaces = new String[] {comparatorClassInternalName};
			this.classToCompareDescriptor = Type.getDescriptor(classToCompare);
			this.classToCompareInternalName = Type.getInternalName(classToCompare);
			this.classToCompareSignature = signature("java/lang/Class");
			this.comparatorClass = comparatorClass;
			this.comparatorClassInternalName = comparatorClassInternalName;
			this.getterType = generator.getterType();
			this.getterDescriptor = getterType.getDescriptor();
			this.getterInternalName = getterType.getInternalName();
			this.getterSignature = signature(getterInternalName);
			this.specType = generator.specType();
			this.specInternalName = specType.getInternalName();
			this.specDescriptor = specType.getDescriptor();
			this.specSignature = signature(specInternalName);
		}

		private String signature(String genericTypeInternalName) {
			return "L" + genericTypeInternalName + "<" + classToCompareDescriptor + ">;";
		}

	}

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy