/*
 * Copyright IBM Corp. and others 2018
 *
 * This program and the accompanying materials are made available under
 * the terms of the Eclipse Public License 2.0 which accompanies this
 * distribution and is available at https://www.eclipse.org/legal/epl-2.0/
 * or the Apache License, Version 2.0 which accompanies this distribution and
 * is available at https://www.apache.org/licenses/LICENSE-2.0.
 *
 * This Source Code may also be made available under the following
 * Secondary Licenses when the conditions for such availability set
 * forth in the Eclipse Public License, v. 2.0 are satisfied: GNU
 * General Public License, version 2 with the GNU Classpath
 * Exception [1] and GNU General Public License, version 2 with the
 * OpenJDK Assembly Exception [2].
 *
 * [1] https://www.gnu.org/software/classpath/license.html
 * [2] https://openjdk.org/legal/assembly-exception.html
 *
 * SPDX-License-Identifier: EPL-2.0 OR Apache-2.0 OR GPL-2.0-only WITH Classpath-exception-2.0 OR GPL-2.0-only WITH OpenJDK-assembly-exception-1.0
 */
package org.openj9.test.lworld;

import org.objectweb.asm.*;

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

import static org.openj9.test.lworld.ValhallaUtils.ACC_STRICT_INIT;

import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.reflect.Field;
import java.security.ProtectionDomain;
import java.util.ArrayList;
import java.util.List;

public class ValueTypeGenerator extends ClassLoader {
	private static ValueTypeGenerator generator;
	
	private static boolean DEBUG = false;
	
	static {
		generator = new ValueTypeGenerator();
	}

	private static class ClassConfiguration {
		private String name;
		private String superName;
		private String[] fields;
		private String nestHost;
		private boolean isVerifiable;
		private boolean isReference;
		private boolean hasNonStaticSynchronizedMethods;
		private int extraClassFlags;

		/**
		 * @see setAccessedContainer
		 */
		private ClassConfiguration accessedContainer;

		/**
		 * @see setValueClassUsedInCode
		 */
		private ClassConfiguration valueClassUsedInCode;

		public ClassConfiguration(String name) {
			this.name = name;
			this.superName = "java/lang/Object";
			this.extraClassFlags = 0;
		}

		public ClassConfiguration(String name, String[] fields) {
			this.name = name;
			this.superName = "java/lang/Object";
			this.fields = fields;
			this.extraClassFlags = 0;
		}

		public String getName() {
			return name;
		}
		
		public void setSuperClassName(String superName) {
			this.superName = superName;
		}
		
		public String getSuperName() {
			return superName;
		}
		
		public void setExtraClassFlags(int extraClassFlags) {
			this.extraClassFlags = extraClassFlags;
		}
		
		public int getExtraClassFlags() {
			return extraClassFlags;
		}

		public String[] getFields() {
			return fields;
		}

		public String getNestHost() {
			return nestHost;
		}

		public void setNestHost(String nestHost) {
			this.nestHost = nestHost;
		}

		public void setIsReference(boolean isReference) { 
			this.isReference = isReference;
			/* Only reference type can have non-static synchronized methods. Value type cannot have one. */
			setHasNonStaticSynchronizedMethods(isReference);
		}

		public boolean isReference() {
			return isReference;
		}

		public void setIsVerifiable(boolean isVerifiable) {
			this.isVerifiable = isVerifiable;
		}

		public boolean isVerifiable() {
			return this.isVerifiable;
		}

		public void setHasNonStaticSynchronizedMethods(boolean hasNonStaticSynchronizedMethods) {
			this.hasNonStaticSynchronizedMethods = hasNonStaticSynchronizedMethods;
		}

		public boolean hasNonStaticSynchronizedMethods() {
			return this.hasNonStaticSynchronizedMethods;
		}

		/**
		 * This method specifies a reference class - whose fields are expected to be of
		 * value types - an instance of which will be an argument to the
		 * {@code testUnresolvedValueTypePutField} and {@code testUnresolvedValueTypeGetField}
		 * methods that will be generated for the current class.  Those methods will perform
		 * {@code PUTFIELD} or {@code GETFIELD} operations, respectively, on the fields of
		 * the {@code accessedContainer} instance.
		 *
		 * The intention is to test delaying resolution of the fields and their types,
		 * particularly its effect on code generated by the JIT compiler.
		 */
		public void setAccessedContainer(ClassConfiguration accessedContainer) {
			this.accessedContainer = accessedContainer;
		}

		/**
		 * @see setAccessedContainer
		 */
		public ClassConfiguration getAccessedContainer() {
			return accessedContainer;
		}

		/**
		 * This method specifies a value type class that will be used in code generated
		 * for the {@code testUnresolvedValueTypeDefaultValue}.
		 * It will conditionally perform initialize the value class.
		 * The instance should be passed to {@code testUnresolvedValueTypeWithField} via an
		 * argument of type {@link java.lang.Object}.
		 *
		 * The value type class will be declared to be a {@code NestMember} of the current
		 * class, and must in turn declare the current class to be its {@code NestHost}.
		 *
		 * The intention is to test delayed resolution of the value type class, particularly
		 * its effect on code generated by the JIT compiler.
		 */
		public void setValueClassUsedInCode(ClassConfiguration valueClassUsedInCode) {
			this.valueClassUsedInCode = valueClassUsedInCode;
		}

		/**
		 * @see setValueClassUsedInCode
		 */
		public ClassConfiguration getValueClassUsedInCode() {
			return valueClassUsedInCode;
		}
	}

	private static byte[] generateClass(ClassConfiguration config) {
		String className = config.getName();
		String superName = config.getSuperName();
		String[] fields = config.getFields();
		int extraClassFlags = config.getExtraClassFlags();

		String nestHost = config.getNestHost();

		ClassConfiguration valueClassConfig = config.getValueClassUsedInCode();
		String valueUsedInCode = (valueClassConfig != null) ? valueClassConfig.getName() : null;
		String[] valueFields = (valueClassConfig != null) ? valueClassConfig.getFields() : null;

		ClassConfiguration containerClassConfig = config.getAccessedContainer();
		String containerUsedInCode = (containerClassConfig != null) ? containerClassConfig.getName() : null;
		String[] containerFields = (containerClassConfig != null) ? containerClassConfig.getFields() : null;

		boolean isVerifiable = config.isVerifiable();
		boolean isRef = config.isReference();
		boolean addSyncMethods = config.hasNonStaticSynchronizedMethods();

		ClassWriter cw = new ClassWriter(0);
		FieldVisitor fv;
		MethodVisitor mv;
		String classFileName = className + ".class";

		if (isRef) {
			cw.visit(ValhallaUtils.VALUE_TYPE_CLASS_FILE_VERSION, ACC_PUBLIC + ACC_FINAL + ValhallaUtils.ACC_IDENTITY + extraClassFlags, className, null, superName, null);
		} else {
			cw.visit(ValhallaUtils.VALUE_TYPE_CLASS_FILE_VERSION, ACC_PUBLIC + ACC_FINAL + extraClassFlags, className, null, superName, null);
		}

		cw.visitSource(className + ".java", null);

		if (!isRef) {
			cw.visitAttribute(new ValhallaUtils.ImplicitCreationAttribute());
		}

		if (nestHost != null) {
			cw.visitNestHost(nestHost);
		}

		if (valueUsedInCode != null) {
			cw.visitNestMember(valueUsedInCode);
		}
		
		int makeMaxLocal = 0;
		String makeValueSig = "";
		String makeValueGenericSig = "";
		boolean[] isStrictInitField = new boolean[fields.length];
		for (int i = 0; i < fields.length; i++) {
			String s = fields[i];
			String[] nameAndSigValue = s.split(":");
			final int fieldModifiers;
			if (isRef) {
				fieldModifiers = ACC_PUBLIC;
			} else {
				if ((nameAndSigValue.length > 2) && nameAndSigValue[2].equals("static")) {
					fieldModifiers = ACC_PUBLIC + ACC_STATIC;
				} else if ((nameAndSigValue.length > 3) && nameAndSigValue[3].equals("volatile")) {
					fieldModifiers = ACC_PUBLIC + ACC_FINAL + ACC_VOLATILE + ACC_STRICT_INIT;
				} else {
					fieldModifiers = ACC_PUBLIC + ACC_FINAL + ACC_STRICT_INIT;
				}
			}
			fv = cw.visitField(fieldModifiers, nameAndSigValue[0], nameAndSigValue[1], null, null);
			if ((nameAndSigValue.length > 2) && nameAndSigValue[2].equals("NR")) {
				fv.visitAttribute(new ValhallaUtils.NullRestrictedAttribute());
			}
			fv.visitEnd();
			isStrictInitField[i] = (fieldModifiers & ACC_STRICT_INIT) != 0;
			if ((nameAndSigValue.length <= 2) || !nameAndSigValue[2].equals("static")) {
				makeValueSig += nameAndSigValue[1];
				makeValueGenericSig += "Ljava/lang/Object;";
				if (nameAndSigValue[1].equals("J") || nameAndSigValue[1].equals("D")) {
					makeMaxLocal += 2;
				} else {
					makeMaxLocal += 1;
				}
			}

			generateFieldMethods(cw, nameAndSigValue, className, isVerifiable, isRef);
		}
		
		addInit(cw, className, fields, isStrictInitField);
		if ("" != makeValueSig) {
			addInitWithArgs(cw, className, makeValueSig, fields, makeMaxLocal);
		}
		makeObject(cw, className, makeValueSig, fields, makeMaxLocal);
		if (!isVerifiable) {
			makeGeneric(cw, className, "makeObjectGeneric", "makeObject", makeValueSig, makeValueGenericSig, fields, makeMaxLocal);
		}
		makeDefaultValue(cw, className, makeValueSig, fields, makeMaxLocal);
		makeDefaultValueGeneric(cw, className, makeValueSig, fields, makeMaxLocal);
		if (isRef) {
			testMonitorExitOnObject(cw, className, fields);
			testMonitorEnterAndExitWithRefType(cw, className, fields);
			testCheckCastNullableTypeOnNull(cw, className, fields);
			if (valueUsedInCode != null) {
				testUnresolvedValueTypeDefaultValue(cw, className, valueUsedInCode);
			}
			if (containerFields != null) {
				testUnresolvedValueTypePutField(cw, className, containerUsedInCode, containerFields);
				testUnresolvedValueTypeGetField(cw, className, containerUsedInCode, containerFields);
			}
		} else {
			testCheckCastNullRestrictedTypeOnNull(cw, className, fields);
			testCheckCastNullRestrictedTypeOnNonNullType(cw, className, fields);
		}
		test2DMultiANewArray(cw, className);
		addStaticSynchronizedMethods(cw);
		if (addSyncMethods) {
			addSynchronizedMethods(cw);
		}
		cw.visitEnd();
		
		byte[] bytes = cw.toByteArray();
		generateClassFile(classFileName, bytes);
		
		return bytes;
		
	}

	private static void addStaticSynchronizedMethods(ClassWriter cw) {
		MethodVisitor mv = cw.visitMethod(ACC_PUBLIC + ACC_STATIC + ACC_SYNCHRONIZED, "staticSynchronizedMethodReturnInt", "()I", null, null);
		mv.visitCode();
		mv.visitInsn(ICONST_1);
		mv.visitInsn(IRETURN);
		mv.visitMaxs(1, 0);
		mv.visitEnd();
	}
	
	private static void addSynchronizedMethods(ClassWriter cw) {
		MethodVisitor mv = cw.visitMethod(ACC_PUBLIC + ACC_SYNCHRONIZED, "synchronizedMethodReturnInt", "()I", null, null);
		mv.visitCode();
		mv.visitInsn(ICONST_1);
		mv.visitInsn(IRETURN);
		mv.visitMaxs(1, 1);
		mv.visitEnd();
	}
	
	private static void generateFieldMethods(ClassWriter cw, String[] nameAndSigValue, String className, boolean isVerifiable, boolean isRef) {
		if ((nameAndSigValue.length > 2) && nameAndSigValue[2].equals("static")) {
			generateSetterStatic(cw, nameAndSigValue, className);
			generateGetterStatic(cw, nameAndSigValue, className);
			if (!isVerifiable) {
				generateStaticSetterGeneric(cw, nameAndSigValue, className);
				generateStaticGetterGeneric(cw, nameAndSigValue, className);
			}
		} else {
			generateGetter(cw, nameAndSigValue, className);
			if (isRef) {
				generateSetter(cw, nameAndSigValue, className);
			}
			if (!isVerifiable) {
				generateGetterGeneric(cw, nameAndSigValue, className);
				if (isRef) {
					generateSetterGeneric(cw, nameAndSigValue, className);
				}
			}
		}
	}

	private static void addInit(ClassWriter cw, String className, String[] fields, boolean[] isStrictInitField) {
		MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
		mv.visitCode();
		boolean needsWideStack = false;
		for (int i = 0; i < fields.length; i++) {
			if (!isStrictInitField[i]) continue;
			String[] nameAndSigValue = fields[i].split(":");
			String sig = nameAndSigValue[1];
			mv.visitVarInsn(ALOAD, 0);
			switch (sig.charAt(0)) {
				case 'J':
					mv.visitInsn(LCONST_0);
					needsWideStack = true;
					break;
				case 'D':
					mv.visitInsn(DCONST_0);
					needsWideStack = true;
					break;
				case 'F':
					mv.visitInsn(FCONST_0);
					break;
				case 'I':
				case 'S':
				case 'B':
				case 'C':
				case 'Z':
					mv.visitInsn(ICONST_0);
					break;
				default:
					boolean isNullRestricted = (nameAndSigValue.length > 2) && "NR".equals(nameAndSigValue[2]);
					if (isNullRestricted) {
						String fieldTypeName = sig.substring(1, sig.length() - 1);
						mv.visitTypeInsn(NEW, fieldTypeName);
						mv.visitInsn(DUP);
						mv.visitMethodInsn(INVOKESPECIAL, fieldTypeName, "<init>", "()V", false);
						needsWideStack = true;
					} else {
						mv.visitInsn(ACONST_NULL);
					}
					break;
			}
			mv.visitFieldInsn(PUTFIELD, className, nameAndSigValue[0], sig);
		}
		mv.visitVarInsn(ALOAD, 0);
		mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
		mv.visitInsn(RETURN);
		int maxStack = needsWideStack ? 3 : 2;
		mv.visitMaxs(maxStack, 1);
		mv.visitEnd();
	}
	

	/*
	 * This function should only be called in the
	 * TestMonitorExitOnValueType test and
	 * TestMonitorExitWithRefType test
	 */
	private static void testMonitorExitOnObject(ClassWriter cw, String className, String[] fields) {
		MethodVisitor mv = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "testMonitorExitOnObject", "(Ljava/lang/Object;)V", null, null);
		mv.visitCode();
		mv.visitVarInsn(ALOAD, 0);
		mv.visitInsn(MONITOREXIT);
		mv.visitInsn(RETURN);
		mv.visitMaxs(1, 1);
		mv.visitEnd();
	}

	/*
	 * This function should only be called in the
	 * TestMonitorEnterAndExitWithRefType test
	 */
	private static void testMonitorEnterAndExitWithRefType(ClassWriter cw, String className, String[] fields) {
		MethodVisitor mv = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "testMonitorEnterAndExitWithRefType", "(Ljava/lang/Object;)V", null, null);
		mv.visitCode();
		mv.visitVarInsn(ALOAD, 0);
		mv.visitInsn(DUP);
		mv.visitInsn(MONITORENTER);
		mv.visitInsn(MONITOREXIT);
		mv.visitInsn(RETURN);
		mv.visitMaxs(2,1);
		mv.visitEnd();
	}

	private static void addInitWithArgs(ClassWriter cw, String className, String makeValueSig, String[]fields, int makeMaxLocal) {
		boolean doubleDetected = false;
		int makeRefArgsAndLocals = makeMaxLocal + 1; //extra slot is to store the ref being created
		MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "<init>", "(" + makeValueSig + ")V", null, null);
		mv.visitCode();
		for (int i = 0, count = 1; i < fields.length; i++) {
			mv.visitVarInsn(ALOAD, 0);
			String[] nameAndSigValue = fields[i].split(":");
			if ((nameAndSigValue.length < 3) ||  !(nameAndSigValue[2].equals("static"))) {
				switch (nameAndSigValue[1]) {
				case "D":
					mv.visitVarInsn(DLOAD, count);
					doubleDetected = true;
					count += 2;
					break;
				case "I":
				case "Z":
				case "B":
				case "C":
				case "S":
					mv.visitVarInsn(ILOAD, count);
					count++;
					break;
				case "F":
					mv.visitVarInsn(FLOAD, count);
					count++;
					break;
				case "J":
					mv.visitVarInsn(LLOAD, count);
					doubleDetected = true;
					count += 2;
					break;
				default:
					mv.visitVarInsn(ALOAD, count);
					count++;
					break;
				}
				mv.visitFieldInsn(PUTFIELD, className, nameAndSigValue[0], nameAndSigValue[1]);
			}
		}
		mv.visitVarInsn(ALOAD, 0);
		mv.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
		mv.visitInsn(RETURN);
		int maxStack = ((doubleDetected) ? 3 : 2);
		mv.visitMaxs(maxStack, makeRefArgsAndLocals);
		mv.visitEnd();
	}
	
	private static void makeValueTypeDefaultValue(ClassWriter cw, String valueName, String makeValueSig, String[] fields, int makeMaxLocal) {
		MethodVisitor mv = cw.visitMethod(ACC_PUBLIC  + ACC_STATIC, "makeValueTypeDefaultValue", "()Ljava/lang/Object;", null, null);
		mv.visitCode();
		mv.visitTypeInsn(NEW, valueName);
		mv.visitInsn(DUP);
		mv.visitMethodInsn(INVOKESPECIAL, valueName, "<init>", "()V", false);
		mv.visitInsn(ARETURN);
		mv.visitMaxs(2, 0);
		mv.visitEnd();
	}

	private static void testCheckCastNullRestrictedTypeOnNonNullType(ClassWriter cw, String className, String[] fields) {
		MethodVisitor mv = cw.visitMethod(ACC_PUBLIC  + ACC_STATIC, "testCheckCastNullRestrictedTypeOnNonNullType", "()Ljava/lang/Object;", null, null);
		mv.visitCode();
		mv.visitTypeInsn(NEW, className);
		mv.visitInsn(DUP);
		mv.visitMethodInsn(INVOKESPECIAL, className, "<init>", "()V", false);
		mv.visitTypeInsn(CHECKCAST, className);
		mv.visitInsn(ARETURN);
		mv.visitMaxs(2, 2);
		mv.visitEnd();
	}

	private static void testCheckCastNullRestrictedTypeOnNull(ClassWriter cw, String className, String[] fields) {
		MethodVisitor mv = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "testCheckCastNullRestrictedTypeOnNull", "()Ljava/lang/Object;", null, null);
		mv.visitCode();
		mv.visitInsn(ACONST_NULL);
		mv.visitTypeInsn(CHECKCAST, className);
		mv.visitInsn(ARETURN);
		mv.visitMaxs(1, 2);
		mv.visitEnd();
	}

	private static void testCheckCastNullableTypeOnNull(ClassWriter cw, String className, String[] fields) {
		MethodVisitor mv = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "testCheckCastNullableTypeOnNull", "()Ljava/lang/Object;", null, null);
		mv.visitCode();
		mv.visitInsn(ACONST_NULL);
		mv.visitTypeInsn(CHECKCAST, className);
		mv.visitInsn(ARETURN);
		mv.visitMaxs(1, 2);
		mv.visitEnd();
	}

	private static void testUnresolvedValueTypeDefaultValue(ClassWriter cw, String className, String valueUsedInCode) {
		MethodVisitor mv = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "testUnresolvedValueTypeDefaultValue", "(I)Ljava/lang/Object;", null, null);
		mv.visitCode();
		mv.visitVarInsn(ILOAD, 0);
		Label falseLabel = new Label();
		Label endLabel = new Label();
		mv.visitJumpInsn(IFEQ, falseLabel);
		mv.visitTypeInsn(NEW, valueUsedInCode);
		mv.visitInsn(DUP);
		mv.visitMethodInsn(INVOKESPECIAL, valueUsedInCode, "<init>", "()V");
		mv.visitVarInsn(ASTORE, 1);
		mv.visitJumpInsn(GOTO, endLabel);
		mv.visitLabel(falseLabel);
		mv.visitFrame(F_SAME, 1, new Object[] {INTEGER}, 0, new Object[]{});
		mv.visitInsn(ACONST_NULL);
		mv.visitVarInsn(ASTORE, 1);
		mv.visitLabel(endLabel);
		mv.visitFrame(F_APPEND, 1, new Object[] {"java/lang/Object"}, 1, new Object[] {"java/lang/Object"});
		mv.visitVarInsn(ALOAD, 1);
		mv.visitInsn(ARETURN);
		mv.visitMaxs(2, 2);
		mv.visitEnd();
	}

	private static void testUnresolvedValueTypeGetField(ClassWriter cw, String className, String containerClassName, String[] containerFields) {
		MethodVisitor mv = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "testUnresolvedValueTypeGetField", "(IL"+containerClassName+";)Ljava/lang/Object;", null, null);
		mv.visitCode();
		mv.visitVarInsn(ILOAD, 0);
		int fieldCount = containerFields.length;
		Label endLabel = new Label();
		Label defaultLabel = new Label();
		Label[] caseLabels = new Label[fieldCount];
		for (int i = 0; i < fieldCount; i++) {
			caseLabels[i] = new Label();
		}
		mv.visitTableSwitchInsn(0, fieldCount-1, defaultLabel, caseLabels);
		for (int i = 0; i < fieldCount; i++) {
			String[] nameAndSigValue = containerFields[i].split(":");
			mv.visitLabel(caseLabels[i]);
			mv.visitFrame(F_SAME, 3, new Object[] {INTEGER, containerClassName, "java/lang/Object"}, 0, new Object[]{});
			mv.visitVarInsn(ALOAD, 1);
			mv.visitFieldInsn(GETFIELD, containerClassName, nameAndSigValue[0], nameAndSigValue[1]);
			mv.visitJumpInsn(GOTO, endLabel);
		}
		mv.visitLabel(defaultLabel);
		mv.visitFrame(F_SAME, 3, new Object[] {INTEGER, containerClassName, "java/lang/Object"}, 0, new Object[]{});
		mv.visitInsn(ACONST_NULL);
		mv.visitLabel(endLabel);
		mv.visitFrame(F_SAME1, 3, new Object[] {INTEGER, containerClassName, "java/lang/Object"}, 1, new Object[]{"java/lang/Object"});
		mv.visitInsn(ARETURN);
		mv.visitMaxs(1, 2);
		mv.visitEnd();
	}

	private static void testUnresolvedValueTypePutField(ClassWriter cw, String className, String containerClassName, String[] containerFields) {
		MethodVisitor mv = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "testUnresolvedValueTypePutField", "(IL"+containerClassName+";Ljava/lang/Object;)V", null, null);
		mv.visitCode();
		mv.visitVarInsn(ILOAD, 0);
		int fieldCount = containerFields.length;
		Label endLabel = new Label();
		Label defaultLabel = new Label();
		Label[] caseLabels = new Label[fieldCount];
		for (int i = 0; i < fieldCount; i++) {
			caseLabels[i] = new Label();
		}
		mv.visitTableSwitchInsn(0, fieldCount-1, defaultLabel, caseLabels);
		for (int i = 0; i < fieldCount; i++) {
			String[] nameAndSigValue = containerFields[i].split(":");
			mv.visitLabel(caseLabels[i]);
			mv.visitFrame(F_SAME, 3, new Object[] {INTEGER, containerClassName, "java/lang/Object"}, 0, new Object[]{});
			mv.visitVarInsn(ALOAD, 1);
			mv.visitVarInsn(ALOAD, 2);
			// TODO do I need to check if this (and other cases) are objects or not?
			mv.visitTypeInsn(CHECKCAST, nameAndSigValue[1].substring(1, nameAndSigValue[1].length() - 1));
			mv.visitFieldInsn(PUTFIELD, containerClassName, nameAndSigValue[0], nameAndSigValue[1]);
			mv.visitJumpInsn(GOTO, endLabel);
		}
		mv.visitLabel(defaultLabel);
		mv.visitLabel(endLabel);
		mv.visitFrame(F_SAME, 3, new Object[] {INTEGER, containerClassName, "java/lang/Object"}, 0, new Object[]{});
		mv.visitInsn(RETURN);
		mv.visitMaxs(2, 3);
		mv.visitEnd();
	}

	private static void makeDefaultValue(ClassWriter cw, String className, String makeValueSig, String[] fields, int makeMaxLocal) {
		MethodVisitor mv = cw.visitMethod(ACC_PUBLIC  + ACC_STATIC, "makeDefaultValue", "()L" + className + ";", null, null);
		mv.visitCode();
		mv.visitTypeInsn(NEW, className);
		mv.visitInsn(DUP);
		mv.visitMethodInsn(INVOKESPECIAL, className, "<init>", "()V", false);
		mv.visitInsn(ARETURN);
		mv.visitMaxs(2, 0);
		mv.visitEnd();
	}

	private static void makeDefaultValueGeneric(ClassWriter cw, String className, String makeValueSig, String[] fields, int makeMaxLocal) {
		MethodVisitor mv = cw.visitMethod(ACC_PUBLIC  + ACC_STATIC, "makeDefaultValueGeneric", "()Ljava/lang/Object;", null, null);
		mv.visitCode();
		mv.visitTypeInsn(NEW, className);
		mv.visitInsn(DUP);
		mv.visitMethodInsn(INVOKESPECIAL, className, "<init>", "()V", false);
		mv.visitInsn(ARETURN);
		mv.visitMaxs(2, 0);
		mv.visitEnd();
	}

	private static void makeGeneric(ClassWriter cw, String className, String methodName, String specificMethodName, String makeValueSig, String makeValueGenericSig, String[] fields, int makeMaxLocal) {
		MethodVisitor mv = cw.visitMethod(ACC_PUBLIC  + ACC_STATIC, methodName, "(" + makeValueGenericSig + ")Ljava/lang/Object;", null, new String[] {"java/lang/Exception"});
		mv.visitCode();
		for (int i = 0; i <  fields.length; i++) {
			String[] nameAndSigValue = fields[i].split(":");
			if ((nameAndSigValue.length < 3) ||  !(nameAndSigValue[2].equals("static"))) {
				mv.visitVarInsn(ALOAD, i);
				switch (nameAndSigValue[1]) {
				case "D":
					mv.visitTypeInsn(CHECKCAST, "java/lang/Double");
					mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Double", "doubleValue", "()D", false);
					break;
				case "I":
					mv.visitTypeInsn(CHECKCAST, "java/lang/Integer");
					mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Integer", "intValue", "()I", false);
					break;
				case "Z":
					mv.visitTypeInsn(CHECKCAST, "java/lang/Boolean");
					mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Boolean", "booleanValue", "()Z", false);
					break;
				case "B":
					mv.visitTypeInsn(CHECKCAST, "java/lang/Byte");
					mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Byte", "byteValue", "()B", false);
					break;
				case "C":
					mv.visitTypeInsn(CHECKCAST, "java/lang/Character");
					mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Character", "charValue", "()C", false);
					break;
				case "S":
					mv.visitTypeInsn(CHECKCAST, "java/lang/Short");
					mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Short", "shortValue", "()S", false);
					break;
				case "F":
					mv.visitTypeInsn(CHECKCAST, "java/lang/Float");
					mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Float", "floatValue", "()F", false);
					break;
				case "J":
					mv.visitTypeInsn(CHECKCAST, "java/lang/Long");
					mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Long", "longValue", "()J", false);
					break;
				default:
					String signature = nameAndSigValue[1];
					signature = signature.substring(1, signature.length() - 1);
					mv.visitTypeInsn(CHECKCAST, signature);
					break;
				}
			}
		}
		mv.visitMethodInsn(INVOKESTATIC, className, specificMethodName, "(" + makeValueSig + ")" + getSigFromSimpleName(className), false);
		mv.visitInsn(ARETURN);
		int maxStack = makeMaxLocal;
		if (0 == maxStack) {
			maxStack += 1;
		}
		mv.visitMaxs(maxStack, makeMaxLocal);
		mv.visitEnd();
	}

	private static void makeObject(ClassWriter cw, String className, String makeValueSig, String[]fields, int makeMaxLocal) {
		boolean doubleDetected = false;
		int count = 0;
		MethodVisitor mv = cw.visitMethod(ACC_PUBLIC  + ACC_STATIC, "makeObject", "(" + makeValueSig + ")" + "L" + className + ";", null, null);
		mv.visitCode();
		mv.visitTypeInsn(NEW, className);
		mv.visitInsn(DUP);
		for (int i = 0; i < fields.length; i++) {
			String[] nameAndSigValue = fields[i].split(":");
			if ((nameAndSigValue.length < 3) ||  !(nameAndSigValue[2].equals("static"))) {
				switch (nameAndSigValue[1]) {
				case "D":
					mv.visitVarInsn(DLOAD, count);
					doubleDetected = true;
					count += 2;
					break;
				case "I":
				case "Z":
				case "B":
				case "C":
				case "S":
					mv.visitVarInsn(ILOAD, count);
					count++;
					break;
				case "F":
					mv.visitVarInsn(FLOAD, count);
					count++;
					break;
				case "J":
					mv.visitVarInsn(LLOAD, count);
					doubleDetected = true;
					count += 2;
					break;
				default:
					mv.visitVarInsn(ALOAD, count);
					count++;
					break;
				}
			}
		}
		mv.visitMethodInsn(INVOKESPECIAL, className, "<init>", "(" + makeValueSig + ")V");
		mv.visitInsn(ARETURN);
		int maxStack = ((doubleDetected) ? 3 : 2) + count;
		mv.visitMaxs(maxStack, makeMaxLocal);
		mv.visitEnd();
	}

	private static void generateSetter(ClassWriter cw, String[] nameAndSigValue, String className) {
		boolean doubleDetected = false;
		MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "set" + nameAndSigValue[0], "(" + nameAndSigValue[1] + ")V", null, null);
		mv.visitCode();
		mv.visitVarInsn(ALOAD, 0);
		switch (nameAndSigValue[1]) {
		case "D":
			mv.visitVarInsn(DLOAD, 1);
			doubleDetected = true;
			break;
		case "I":
		case "Z":
		case "B":
		case "C":
		case "S":
			mv.visitVarInsn(ILOAD, 1);
			break;
		case "F":
			mv.visitVarInsn(FLOAD, 1);
			break;
		case "J":
			mv.visitVarInsn(LLOAD, 1);
			doubleDetected = true;
			break;
		default:
			mv.visitVarInsn(ALOAD, 1);
			break;
		}
		mv.visitFieldInsn(PUTFIELD, className, nameAndSigValue[0], nameAndSigValue[1]);
		mv.visitInsn(RETURN);
		int maxStackAndLocals = (doubleDetected ? 3 : 2);
		mv.visitMaxs(maxStackAndLocals, maxStackAndLocals);
		mv.visitEnd();
	}
	
	private static void generateSetterStatic(ClassWriter cw, String[] nameAndSigValue, String className) {
		boolean doubleDetected = false;
		MethodVisitor mv = cw.visitMethod((ACC_PUBLIC | ACC_STATIC), "setStatic" + nameAndSigValue[0], "(" + nameAndSigValue[1] + ")V", null, null);
		mv.visitCode();
		switch (nameAndSigValue[1]) {
		case "D":
			mv.visitVarInsn(DLOAD, 0);
			doubleDetected = true;
			break;
		case "I":
		case "Z":
		case "B":
		case "C":
		case "S":
			mv.visitVarInsn(ILOAD, 0);
			break;
		case "F":
			mv.visitVarInsn(FLOAD, 0);
			break;
		case "J":
			mv.visitVarInsn(LLOAD, 0);
			doubleDetected = true;
			break;
		default:
			mv.visitVarInsn(ALOAD, 0);
			break;
		}
		mv.visitFieldInsn(PUTSTATIC, className, nameAndSigValue[0], nameAndSigValue[1]);
		mv.visitInsn(RETURN);
		int maxStackAndLocals = (doubleDetected ? 2 : 1);
		mv.visitMaxs(maxStackAndLocals, maxStackAndLocals);
		mv.visitEnd();
	}

	private static void generateStaticSetterGeneric(ClassWriter cw, String[] nameAndSigValue, String className) {
		boolean doubleDetected = false;
		MethodVisitor mv = cw.visitMethod(ACC_PUBLIC | ACC_STATIC, "setStaticGeneric" + nameAndSigValue[0], "(Ljava/lang/Object;)V", null, null);
		mv.visitCode();
		mv.visitVarInsn(ALOAD, 0);
		switch (nameAndSigValue[1]) {
		case "D":
			mv.visitTypeInsn(CHECKCAST, "java/lang/Double");
			mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Double", "doubleValue", "()D", false);
			doubleDetected = true;
			break;
		case "I":
			mv.visitTypeInsn(CHECKCAST, "java/lang/Integer");
			mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Integer", "intValue", "()I", false);
		case "Z":
			mv.visitTypeInsn(CHECKCAST, "java/lang/Boolean");
			mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Boolean", "booleanValue", "()Z", false);
		case "B":
			mv.visitTypeInsn(CHECKCAST, "java/lang/Byte");
			mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Byte", "byteValue", "()B", false);
		case "C":
			mv.visitTypeInsn(CHECKCAST, "java/lang/Character");
			mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Character", "charValue", "()C", false);
		case "S":
			mv.visitTypeInsn(CHECKCAST, "java/lang/Short");
			mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Short", "shortValue", "()S", false);
			break;
		case "F":
			mv.visitTypeInsn(CHECKCAST, "java/lang/Float");
			mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Float", "floatValue", "()F", false);
			break;
		case "J":
			mv.visitTypeInsn(CHECKCAST, "java/lang/Long");
			mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Long", "longValue", "()J", false);
			doubleDetected = true;
			break;
		default:
			mv.visitTypeInsn(CHECKCAST, nameAndSigValue[1].substring(1, nameAndSigValue[1].length() - 1));
			break;
		}
		mv.visitMethodInsn(INVOKESTATIC, className, "setStatic" + nameAndSigValue[0], "(" + nameAndSigValue[1] + ")V", false);
		mv.visitInsn(RETURN);
		int maxStack = (doubleDetected ? 2 : 1);
		mv.visitMaxs(maxStack, 1);
		mv.visitEnd();
	}

	private static void generateSetterGeneric(ClassWriter cw, String[] nameAndSigValue, String className) {
		boolean doubleDetected = false;
		MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "setGeneric" + nameAndSigValue[0], "(Ljava/lang/Object;)V", null, null);
		mv.visitCode();
		mv.visitVarInsn(ALOAD, 0);
		mv.visitVarInsn(ALOAD, 1);
		switch (nameAndSigValue[1]) {
		case "D":
			mv.visitTypeInsn(CHECKCAST, "java/lang/Double");
			mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Double", "doubleValue", "()D", false);
			doubleDetected = true;
			break;
		case "I":
			mv.visitTypeInsn(CHECKCAST, "java/lang/Integer");
			mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Integer", "intValue", "()I", false);
			break;
		case "Z":
			mv.visitTypeInsn(CHECKCAST, "java/lang/Boolean");
			mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Boolean", "booleanValue", "()Z", false);
			break;
		case "B":
			mv.visitTypeInsn(CHECKCAST, "java/lang/Byte");
			mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Byte", "byteValue", "()B", false);
			break;
		case "C":
			mv.visitTypeInsn(CHECKCAST, "java/lang/Character");
			mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Character", "charValue", "()C", false);
			break;
		case "S":
			mv.visitTypeInsn(CHECKCAST, "java/lang/Short");
			mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Short", "shortValue", "()S", false);
			break;
		case "F":
			mv.visitTypeInsn(CHECKCAST, "java/lang/Float");
			mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Float", "floatValue", "()F", false);
			break;
		case "J":
			mv.visitTypeInsn(CHECKCAST, "java/lang/Long");
			mv.visitMethodInsn(INVOKEVIRTUAL, "java/lang/Long", "longValue", "()J", false);
			doubleDetected = true;
			break;
		default:
			mv.visitTypeInsn(CHECKCAST, nameAndSigValue[1].substring(1, nameAndSigValue[1].length() - 1));
			break;
		}
		mv.visitMethodInsn(INVOKEVIRTUAL, className, "set" + nameAndSigValue[0], "(" + nameAndSigValue[1] + ")V", false);
		mv.visitInsn(RETURN);
		int maxStack = (doubleDetected ? 3 : 2);
		mv.visitMaxs(maxStack, 2);
		mv.visitEnd();
	}
	
	private static void generateGetter(ClassWriter cw, String[] nameAndSigValue, String className) {
		boolean doubleDetected = false;
		MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "get" + nameAndSigValue[0], "()" + nameAndSigValue[1], null, null);
		mv.visitCode();
		mv.visitVarInsn(ALOAD, 0);
		mv.visitFieldInsn(GETFIELD, className, nameAndSigValue[0], nameAndSigValue[1]);
		switch (nameAndSigValue[1]) {
		case "D":
			mv.visitInsn(DRETURN);
			doubleDetected = true;
			break;
		case "I":
		case "Z":
		case "B":
		case "C":
		case "S":
			mv.visitInsn(IRETURN);
			break;
		case "F":
			mv.visitInsn(FRETURN);
			break;
		case "J":
			mv.visitInsn(LRETURN);
			doubleDetected = true;
			break;
		default:
			mv.visitInsn(ARETURN);
			break;
		}
		int maxStack = (doubleDetected ? 2 : 1);
		mv.visitMaxs(maxStack, 1);
		mv.visitEnd();
	}
	
	private static void generateGetterStatic(ClassWriter cw, String[] nameAndSigValue, String className) {
		boolean doubleDetected = false;
		MethodVisitor mv = cw.visitMethod((ACC_PUBLIC | ACC_STATIC), "getStatic" + nameAndSigValue[0], "()" + nameAndSigValue[1], null, null);
		mv.visitCode();
		mv.visitFieldInsn(GETSTATIC, className, nameAndSigValue[0], nameAndSigValue[1]);
		switch (nameAndSigValue[1]) {
		case "D":
			mv.visitInsn(DRETURN);
			doubleDetected = true;
			break;
		case "I":
		case "Z":
		case "B":
		case "C":
		case "S":
			mv.visitInsn(IRETURN);
			break;
		case "F":
			mv.visitInsn(FRETURN);
			break;
		case "J":
			mv.visitInsn(LRETURN);
			doubleDetected = true;
			break;
		default:
			mv.visitInsn(ARETURN);
			break;
		}
		int maxStack = (doubleDetected ? 2 : 1);
		mv.visitMaxs(maxStack, 0);
		mv.visitEnd();
	}

	private static void generateGetterGeneric(ClassWriter cw, String[] nameAndSigValue, String className) {
		boolean doubleDetected = false;
		MethodVisitor mv = cw.visitMethod(ACC_PUBLIC, "getGeneric" + nameAndSigValue[0], "()Ljava/lang/Object;", null, null);
		mv.visitCode();
		mv.visitVarInsn(ALOAD, 0);
		mv.visitMethodInsn(INVOKEVIRTUAL, className, "get" + nameAndSigValue[0], "()" + nameAndSigValue[1], false);
		switch (nameAndSigValue[1]) {
		case "D":
			mv.visitMethodInsn(INVOKESTATIC, "java/lang/Double", "valueOf", "(D)Ljava/lang/Double;", false);
			doubleDetected = true;
			break;
		case "I":
			mv.visitMethodInsn(INVOKESTATIC, "java/lang/Integer", "valueOf", "(I)Ljava/lang/Integer;", false);
			break;
		case "Z":
			mv.visitMethodInsn(INVOKESTATIC, "java/lang/Boolean", "valueOf", "(Z)Ljava/lang/Boolean;", false);
			break;
		case "B":
			mv.visitMethodInsn(INVOKESTATIC, "java/lang/Byte", "valueOf", "(B)Ljava/lang/Byte;", false);
			break;
		case "C":
			mv.visitMethodInsn(INVOKESTATIC, "java/lang/Character", "valueOf", "(C)Ljava/lang/Character;", false);
			break;
		case "S":
			mv.visitMethodInsn(INVOKESTATIC, "java/lang/Short", "valueOf", "(S)Ljava/lang/Short;", false);
			break;
		case "F":
			mv.visitMethodInsn(INVOKESTATIC, "java/lang/Float", "valueOf", "(F)Ljava/lang/Float;", false);
			break;
		case "J":
			mv.visitMethodInsn(INVOKESTATIC, "java/lang/Long", "valueOf", "(J)Ljava/lang/Long;", false);
			doubleDetected = true;
			break;
		default:
			break;
		}

		mv.visitInsn(ARETURN);
		int maxStack = (doubleDetected ? 2 : 1);
		mv.visitMaxs(maxStack, 1);
		mv.visitEnd();
	}

	private static void generateStaticGetterGeneric(ClassWriter cw, String[] nameAndSigValue, String className) {
		boolean doubleDetected = false;
		MethodVisitor mv = cw.visitMethod(ACC_PUBLIC | ACC_STATIC, "getStaticGeneric" + nameAndSigValue[0], "()Ljava/lang/Object;", null, null);
		mv.visitCode();
		mv.visitMethodInsn(INVOKESTATIC, className, "getStatic" + nameAndSigValue[0], "()" + nameAndSigValue[1], false);
		switch (nameAndSigValue[1]) {
		case "D":
			mv.visitMethodInsn(INVOKESTATIC, "java/lang/Double", "valueOf", "(D)Ljava/lang/Double;", false);
			doubleDetected = true;
			break;
		case "I":
			mv.visitMethodInsn(INVOKESTATIC, "java/lang/Integer", "valueOf", "(I)Ljava/lang/Integer;", false);
			break;
		case "Z":
			mv.visitMethodInsn(INVOKESTATIC, "java/lang/Boolean", "valueOf", "(Z)Ljava/lang/Boolean;", false);
			break;
		case "B":
			mv.visitMethodInsn(INVOKESTATIC, "java/lang/Byte", "valueOf", "(B)Ljava/lang/Byte;", false);
			break;
		case "C":
			mv.visitMethodInsn(INVOKESTATIC, "java/lang/Character", "valueOf", "(C)Ljava/lang/Character;", false);
			break;
		case "S":
			mv.visitMethodInsn(INVOKESTATIC, "java/lang/Short", "valueOf", "(S)Ljava/lang/Short;", false);
			break;
		case "F":
			mv.visitMethodInsn(INVOKESTATIC, "java/lang/Float", "valueOf", "(F)Ljava/lang/Float;", false);
			break;
		case "J":
			mv.visitMethodInsn(INVOKESTATIC, "java/lang/Long", "valueOf", "(J)Ljava/lang/Long;", false);
			doubleDetected = true;
			break;
		default:
			break;
		}

		mv.visitInsn(ARETURN);
		int maxStack = (doubleDetected ? 2 : 1);
		mv.visitMaxs(maxStack, 0);
		mv.visitEnd();
	}
	
	private static String getSigFromSimpleName(String className) {
		return "L" + className + ";";
	}
	
	public static void generateClassFile(String name, byte[] bytes) {
		if (DEBUG) {
			try (FileOutputStream stream = new FileOutputStream(name)) {
				stream.write(bytes);
			}  catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

	public static Class<?> generateValueClass(String name, String[] fields) throws Throwable {
		return generateValueClass(name, fields, null);
	}
	
	public static Class<?> generateValueClass(String name, String superClassName, String[] fields) throws Throwable {
		return generateValueClass(name, superClassName, fields, 0);
	}
	
	public static Class<?> generateValueClass(String name, String superClassName, String[] fields, int extraFlags) throws Throwable {
		ClassConfiguration classConfig = new ClassConfiguration(name, fields);
		classConfig.setSuperClassName(superClassName);
		classConfig.setExtraClassFlags(extraFlags);
		byte[] bytes = generateClass(classConfig);
		return generator.defineClass(name, bytes, 0, bytes.length);
	}
	
	public static Class<?> generateIllegalValueClassWithSynchMethods(String name, String[] fields) throws Throwable {
		ClassConfiguration classConfig = new ClassConfiguration(name, fields);
		classConfig.setHasNonStaticSynchronizedMethods(true);
		byte[] bytes = generateClass(classConfig);
		return generator.defineClass(name, bytes, 0, bytes.length);
	}

	public static Class<?> generateValueClass(String name, String[] fields, String nestHost) throws Throwable {
		ClassConfiguration classConfig = new ClassConfiguration(name, fields);

		if (nestHost != null) {
			classConfig.setNestHost(nestHost);
		}

		byte[] bytes = generateClass(classConfig);
		return generator.defineClass(name, bytes, 0, bytes.length);
	}

	public static Class<?> generateVerifiableValueClass(String name, String[] fields) throws Throwable {
		ClassConfiguration classConfig = new ClassConfiguration(name, fields);
		classConfig.setIsVerifiable(true);

		byte[] bytes = generateClass(classConfig);
		return generator.defineClass(name, bytes, 0, bytes.length);
	}

	public static Class<?> generateRefClass(String name) throws Throwable {
		ClassConfiguration classConfig = new ClassConfiguration(name, new String[0]);
		classConfig.setIsReference(true);

		byte[] bytes = generateClass(classConfig);
		return generator.defineClass(name, bytes, 0, bytes.length);
	}

	public static Class<?> generateRefClass(String name, String[] fields) throws Throwable {
		ClassConfiguration classConfig = new ClassConfiguration(name, fields);
		classConfig.setIsReference(true);

		byte[] bytes = generateClass(classConfig);
		return generator.defineClass(name, bytes, 0, bytes.length);
	}

	public static Class<?> generateRefClass(String name, String[] fields, String valueUsedInCode) throws Throwable {
		ClassConfiguration classConfig = new ClassConfiguration(name, fields);
		ClassConfiguration valueClassConfig = new ClassConfiguration(valueUsedInCode);
		classConfig.setValueClassUsedInCode(valueClassConfig);
		classConfig.setIsReference(true);

		byte[] bytes = generateClass(classConfig);
		return generator.defineClass(name, bytes, 0, bytes.length);
	}

	public static Class<?> generateHostRefClass(String name, String[] fields, String valueUsedInCode, String[] valueFields) throws Throwable {
		ClassConfiguration classConfig = new ClassConfiguration(name, fields);
		ClassConfiguration valueClassConfig = new ClassConfiguration(valueUsedInCode, valueFields);
		classConfig.setValueClassUsedInCode(valueClassConfig);
		classConfig.setIsReference(true);

		byte[] bytes = generateClass(classConfig);
		return generator.defineClass(name, bytes, 0, bytes.length);
	}

	public static Class<?> generateRefClass(String name, String[] fields, String containerClassName, String[] containerFields) throws Throwable {
		ClassConfiguration classConfig = new ClassConfiguration(name, fields);
		ClassConfiguration containerClassConfig = new ClassConfiguration(containerClassName, containerFields);
		containerClassConfig.setIsReference(true);
		classConfig.setAccessedContainer(containerClassConfig);
		classConfig.setIsReference(true);

		byte[] bytes = generateClass(classConfig);
		return generator.defineClass(name, bytes, 0, bytes.length);
	}

	public static void test2DMultiANewArray(ClassWriter cw, String className) {
		MethodVisitor mv = cw.visitMethod(ACC_PUBLIC + ACC_STATIC, "generate2DMultiANewArray", "(II)Ljava/lang/Object;", null, null);
		mv.visitCode();
		mv.visitVarInsn(ILOAD, 0);
		mv.visitVarInsn(ILOAD, 1);
		mv.visitMultiANewArrayInsn("[[" + getSigFromSimpleName(className), 2);
		mv.visitInsn(ARETURN);
		mv.visitMaxs(2, 2);
		mv.visitEnd();
	}

	public static void generateNonInterfaceClassWithMissingFlags(String name) {
		ClassWriter classWriter = new ClassWriter(0);
		classWriter.visit(ValhallaUtils.VALUE_TYPE_CLASS_FILE_VERSION, 0, name, null, "java/lang/Object", null);
		classWriter.visitEnd();
		byte[] bytes = classWriter.toByteArray();
		generator.defineClass(name, bytes, 0, bytes.length);
	}

	private static Class<?> generateAccStaticTestGeneric(String className, int fieldFlags, boolean isRef) {
		String fieldName = "o";
		String fieldDesc = "LObject;";

		ClassWriter classWriter = new ClassWriter(0);
		classWriter.visit(ValhallaUtils.VALUE_TYPE_CLASS_FILE_VERSION,
			ACC_PUBLIC + ACC_FINAL + (isRef ? ValhallaUtils.ACC_IDENTITY : 0),
			className, null, "java/lang/Object", null);
		classWriter.visitField(fieldFlags, fieldName, fieldDesc, null, null);

		classWriter.visitEnd();
		byte[] bytes = classWriter.toByteArray();
		return generator.defineClass(className, bytes, 0, bytes.length);
	}
}
