content: package org.aion.avm.core.arraywrapping;
  
  import org.aion.avm.RuntimeMethodFeeSchedule;
  
  import java.util.*;
  import a.ArrayElement;
  import org.aion.avm.utilities.Utilities;
  
  import i.PackageConstants;
  import i.RuntimeAssertionError;
  import org.objectweb.asm.ClassWriter;
  import org.objectweb.asm.Label;
  import org.objectweb.asm.MethodVisitor;
  import org.objectweb.asm.Opcodes;
  
  
  
  public class ArrayWrappingClassGenerator implements Opcodes {
      static private boolean DEBUG = false;
  
      static private String SHADOW_ARRAY = PackageConstants.kArrayWrapperSlashPrefix + "Array";
  
      public static byte[] arrayWrappingFactory(String request, ClassLoader loader){
  
          if (request.startsWith(PackageConstants.kArrayWrapperUnifyingDotPrefix + "_")){
              return genWrapperInterface(request, loader);
          }
  
          // we only handle class generation request prefixed with a.$
          if (request.startsWith(PackageConstants.kArrayWrapperDotPrefix + "$")){
              return genWrapperClass(request, loader);
          }
  
          return null;
      }
  
      private static byte[] genWrapperInterface(String requestInterface, ClassLoader loader) {
          // A wrapper interface backs all classes and interfaces made into arrays (as well as the interface implemented by those).
          // We do this to generalize the solution to a type unification problem (issue-82).
          // The responsibility of this wrapper interface is to represent all the type relationship of the class/interface element type, within the array.
          // This means that all the interfaces of that class/interface and any superclass of that class must be realized here, as an interface wrapper relationship.
          
  
          if (DEBUG) {
              System.out.println("*********************************");
              System.out.println("requestInterface : " + requestInterface);
          }
  
          String wrapperInterfaceSlashName = Utilities.fulllyQualifiedNameToInternalName(requestInterface);
          // Get element class and array dim
          String elementInterfaceSlashName = wrapperInterfaceSlashName.substring((PackageConstants.kArrayWrapperUnifyingSlashPrefix).length());
          int dim = ArrayNameMapper.getPrefixSize(elementInterfaceSlashName, '_');
          String elementInterfaceDotName = ArrayNameMapper.getElementInterfaceName(requestInterface);
  
          Class<?> elementClass = null;
          try {
              elementClass = loader.loadClass(elementInterfaceDotName);
          } catch (ClassNotFoundException e) {
              throw RuntimeAssertionError.unreachable("No valid component : " + elementInterfaceDotName);
          }
  
          // Handle the element interfaces.
          Class<?>[] superInterfaceClasses =  elementClass.getInterfaces();
          List<String> elementInterfaceWrapperNames = new ArrayList<>();
          for (Class<?> curI : superInterfaceClasses){
              String superInterfaceDotName = ArrayNameMapper.buildArrayDescriptor(dim, typeDescriptorForClass(curI));
              String superInterfaceSlashName = Utilities.fulllyQualifiedNameToInternalName(superInterfaceDotName);
              String superInterfaceWrapperSlashName = ArrayNameMapper.getInterfaceWrapper(superInterfaceSlashName);
              elementInterfaceWrapperNames.add(superInterfaceWrapperSlashName);
          }
  
          // Handle the element superclass (if not an interface).
          if (!elementClass.isInterface() && !elementClass.getName().equals("java.lang.Object")) {
              Class<?> elementSuperClass = elementClass.getSuperclass();
              String superClassDotName = ArrayNameMapper.buildArrayDescriptor(dim, typeDescriptorForClass(elementSuperClass));
              String slashName = Utilities.fulllyQualifiedNameToInternalName(superClassDotName);
              elementInterfaceWrapperNames.add(ArrayNameMapper.getInterfaceWrapper(slashName));
          }
  
          // Handle if we have a multi-dimensional IObject interface wrapper to point to its lower dimensional self.
          if (ArrayNameMapper.isIObjectInterfaceFormat(elementInterfaceSlashName)) {
              String slashName = elementInterfaceSlashName.substring(1);  // remove a _ from name
              String fullSlashName = PackageConstants.kArrayWrapperUnifyingSlashPrefix + slashName;
              elementInterfaceWrapperNames.add(ArrayNameMapper.getInterfaceWrapper(fullSlashName));
          }
  
          // Handle if we have a multi-dimensional Object interface wrapper to point to its lower dimensional self.
          if (ArrayNameMapper.isObjectInterfaceFormat(elementInterfaceSlashName)) {
              String slashName = elementInterfaceSlashName.substring(1);
              String fullSlashName = PackageConstants.kArrayWrapperUnifyingSlashPrefix + slashName;
              String interfaceName = ArrayNameMapper.getInterfaceWrapper(fullSlashName);
              elementInterfaceWrapperNames.add(interfaceName);
          }
  
          // Handle _IObject unifying type so that it unifies under IObjectArray.
          String IObject1D = PackageConstants.kArrayWrapperUnifyingSlashPrefix + "_L" + PackageConstants.kInternalSlashPrefix + "IObject";
          if (wrapperInterfaceSlashName.equals(IObject1D)) {
              elementInterfaceWrapperNames.add(PackageConstants.kInternalSlashPrefix + "IObjectArray");
          }
  
          if (DEBUG) {
              System.out.println("Generating interface : " + wrapperInterfaceSlashName);
              for (String s : elementInterfaceWrapperNames) {
                  System.out.println("Interfaces : " + s);
              }
              System.out.println("Wrapper Dimension : " + dim);
              System.out.println("*********************************");
          }
  
          return generateInterfaceBytecode(wrapperInterfaceSlashName, elementInterfaceWrapperNames.toArray(new String[elementInterfaceWrapperNames.size()]));
  
      }
  
      private static byte[] generateInterfaceBytecode(String wrapperInterfaceSlashName, String[] superInterfaces) {
          ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS);
          classWriter.visit(V10, ACC_PUBLIC | ACC_ABSTRACT | ACC_INTERFACE , wrapperInterfaceSlashName, null, "java/lang/Object", superInterfaces);
          classWriter.visitEnd();
          return classWriter.toByteArray();
      }
  
      private static byte[] genWrapperClass(String requestClass, ClassLoader loader) {
          if (DEBUG) {
              System.out.println("*********************************");
              System.out.println("requestClass : " + requestClass);
          }
  
          // Class name in bytecode
          String wrapperClassSlashName = Utilities.fulllyQualifiedNameToInternalName(requestClass);
  
          // Get element class and array dim
          String elementClassSlashName = wrapperClassSlashName.substring(PackageConstants.kArrayWrapperDotPrefix.length());
          int dim = ArrayNameMapper.getPrefixSize(elementClassSlashName, '$');
          String elementClassDotName = ArrayNameMapper.getClassWrapperElementName(requestClass);
  
          // Default super class is ObjectArray
  
          byte[] bytecode = null;
          // If element is not primitive type, we need to find its super class
          if (!ArrayNameMapper.isPrimitiveElement(elementClassDotName)) {
              // Element is NOT primitive.
              Class<?> elementClass = null;
              try {
                  elementClass = loader.loadClass(elementClassDotName);
              } catch (ClassNotFoundException e) {
                  throw RuntimeAssertionError.unreachable("No valid component : " + elementClassDotName);
              }
  
              // All of these ObjectArray classes are of the same shape:  subclass ObjectArray and implement their own single interface wrapper.
              String interfaceDotName = ArrayNameMapper.buildArrayDescriptor(dim, typeDescriptorForClass(elementClass));
              String interfaceSlashName = Utilities.fulllyQualifiedNameToInternalName(interfaceDotName);
              String interfaceWrapperSlashName = ArrayNameMapper.getInterfaceWrapper(interfaceSlashName);
  
              String superClassSlashName = PackageConstants.kArrayWrapperSlashPrefix + "ObjectArray";
              bytecode = generateClassBytecode(wrapperClassSlashName, superClassSlashName, dim, new String[] {interfaceWrapperSlashName});
  
              if (DEBUG) {
                  System.out.println("Generating class : " + wrapperClassSlashName);
                  System.out.println("Superclass class : " + superClassSlashName);
                  System.out.println("Backing Interfaces : " + interfaceWrapperSlashName);
                  System.out.println("Wrapper Dimension : " + dim);
                  System.out.println("*********************************");
              }
          }else{
              // Element IS primitive
              bytecode = generateClassBytecode(wrapperClassSlashName, PackageConstants.kArrayWrapperSlashPrefix + "ObjectArray", dim, null);
              if (DEBUG) {
                  System.out.println("Generating Prim Class : " + wrapperClassSlashName);
                  System.out.println("Wrapper Dimension : " + dim);
                  System.out.println("*********************************");
              }
          }
          
          // If this is null, an incomplete code path was added.
          RuntimeAssertionError.assertTrue(null != bytecode);
          return bytecode;
      }
  
      private static byte[] generateClassBytecode(String wrapperClassSlashName, String superClassSlashName, int dimensions, String[] superInterfaceSlashNames){
          ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS);
          classWriter.visit(V10, ACC_PUBLIC | ACC_SUPER, wrapperClassSlashName, null, superClassSlashName, superInterfaceSlashNames);
          // Static factory for one dimensional array
          // We always generate one D factory for corner case like int[][][] a = new int[10][][];
          genSingleDimensionFactory(classWriter, wrapperClassSlashName, 1);
  
          if (dimensions > 1) {
              //Static factory for multidimensional array
              genMultiDimensionFactory(classWriter, wrapperClassSlashName, dimensions);
          }
  
          //Constructor
          genConstructor(classWriter, superClassSlashName);
  
          //Clone
          genClone(classWriter, wrapperClassSlashName);
  
          classWriter.visitEnd();
  
          return classWriter.toByteArray();
      }
  
      private static void genSingleDimensionFactory(ClassWriter cw, String wrapper, int d){
          String facDesc = ArrayNameMapper.getFactoryDescriptor(wrapper, d);
          MethodVisitor mv = cw.visitMethod(ACC_PUBLIC | ACC_STATIC, "initArray", facDesc, null, null);
          mv.visitCode();
          mv.visitTypeInsn(NEW, wrapper);
          mv.visitInsn(DUP);
          mv.visitVarInsn(ILOAD, 0);
          mv.visitMethodInsn(INVOKESPECIAL, wrapper, "<init>", "(I)V", false);
  
          // Charge energy
          mv.visitVarInsn(ILOAD, 0);
          mv.visitIntInsn(BIPUSH, ArrayElement.REF.getEnergy());
          mv.visitMethodInsn(INVOKESTATIC, SHADOW_ARRAY, "chargeEnergyInitArray", "(II)V", false);
  
          mv.visitInsn(ARETURN);
          mv.visitMaxs(3, 1);
          mv.visitEnd();
      }
  
      private static void genMultiDimensionFactory(ClassWriter cw, String wrapper, int dim) {
          // Code template for $$$MyObject.initArray (3D array of MyObject)
          // Note that for D = n array, n dimension parameter will be passed into initArray
          //
          // public static $$$MyObj initArray(int d0, int d1, int d2){
          //    $$$MyObj ret = new $$$MyObj(d0);
          //    for (int i = 0; i < d0; i++) {
          //        ret.set(i, $$MyObj.initArray(d1, d2));
          //    }
          //    return ret;
          // }
  
          for (int d = 2; d <= dim; d++) {
              String facDesc = ArrayNameMapper.getFactoryDescriptor(wrapper, d);
              MethodVisitor mv = cw.visitMethod(ACC_PUBLIC | ACC_STATIC, "initArray", facDesc, null, null);
              mv.visitCode();
  
              // Create new wrapper object with d0 LVT[0]
              mv.visitTypeInsn(NEW, wrapper);
              mv.visitInsn(DUP);
              mv.visitVarInsn(ILOAD, 0);
              mv.visitMethodInsn(INVOKESPECIAL, wrapper, "<init>", "(I)V", false);
  
              // Charge energy
              mv.visitVarInsn(ILOAD, 0);
              mv.visitIntInsn(BIPUSH, ArrayElement.REF.getEnergy());
              mv.visitMethodInsn(INVOKESTATIC, SHADOW_ARRAY, "chargeEnergyInitArray", "(II)V", false);
  
              // Wrapper OBJ to return
              // Now LVT[0] ~ LVT[d-1] hold all dimension data, LVT[d] hold wrapper object.
              mv.visitVarInsn(ASTORE, d);
  
              // Initialize counter to LVT[d+1]
              mv.visitInsn(ICONST_0);
              mv.visitVarInsn(ISTORE, d + 1);
  
              // For loop head label
              Label forLoopHead = new Label();
              mv.visitLabel(forLoopHead);
  
              // Stack map frame for for loop
              // Append [wrapper, int] to current frame
              mv.visitFrame(Opcodes.F_APPEND, 2, new Object[]{wrapper, Opcodes.INTEGER}, 0, null);
  
              // Load counter LVT[d + 1]
              // Load current dimension LVT[0]
              mv.visitVarInsn(ILOAD, d + 1);
              mv.visitVarInsn(ILOAD, 0);
  
              // compare counter to current dimension
              Label forLoopTail = new Label();
              mv.visitJumpInsn(IF_ICMPGE, forLoopTail);
  
              // Load wrapper object LVT[d]
              mv.visitVarInsn(ALOAD, d);
              // Load counter LVT[d+1]
              mv.visitVarInsn(ILOAD, d + 1);
              // Load rest of the dimension data LVT[1] ~ LVT[d-1]
              for (int j = 1; j < d; j++) {
                  mv.visitVarInsn(ILOAD, j);
              }
  
              // Call child wrapper factory, child wrapper will pop last d - 1 stack slot as argument.
              // Child wrapper factory descriptor will be constructed here.
              String childWrapper;
              String childFacDesc;
              childWrapper = wrapper.substring((PackageConstants.kArrayWrapperSlashPrefix + "$").length());
              RuntimeAssertionError.assertTrue(childWrapper.startsWith("$"));
              char[] childArray = childWrapper.toCharArray();
              for (int i = 0; childArray[i] == '$'; i++) {
                  childArray[i] = '[';
              }
              childWrapper = new String(childArray);
              childWrapper = ArrayNameMapper.getPreciseArrayWrapperDescriptor(childWrapper);
              childFacDesc = ArrayNameMapper.getFactoryDescriptor(childWrapper, d - 1);
  
              mv.visitMethodInsn(INVOKESTATIC, childWrapper, "initArray", childFacDesc, false);
  
              // Call set
              mv.visitMethodInsn(INVOKEVIRTUAL, wrapper, "set", "(ILjava/lang/Object;)V", false);
  
              // Increase counter LVT[d+1]
              mv.visitIincInsn(d + 1, 1);
  
              mv.visitJumpInsn(GOTO, forLoopHead);
              mv.visitLabel(forLoopTail);
  
              // Chop off the counter from stack map frame
              mv.visitFrame(Opcodes.F_CHOP, 1, null, 0, null);
  
              // Load wrapper object LVT[d]
              mv.visitVarInsn(ALOAD, d);
  
              mv.visitInsn(ARETURN);
  
              // maxStack is d + 1
              // maxLVT is d + 2
              // We can use class writer to calculate them anyway
              mv.visitMaxs(d + 1, d + 2);
              mv.visitEnd();
          }
      }
  
      private static void genConstructor(ClassWriter cw, String superName){
          String initName = "<init>";
          
          MethodVisitor methodVisitor = cw.visitMethod(ACC_PUBLIC, initName, "(I)V", null, null);
          methodVisitor.visitCode();
          methodVisitor.visitVarInsn(ALOAD, 0);
          methodVisitor.visitVarInsn(ILOAD, 1);
          methodVisitor.visitMethodInsn(INVOKESPECIAL, superName, initName, "(I)V", false);
          methodVisitor.visitInsn(RETURN);
          methodVisitor.visitMaxs(2, 2);
          methodVisitor.visitEnd();
  
          methodVisitor = cw.visitMethod(ACC_PUBLIC, initName, "([Ljava/lang/Object;)V", null, null);
          methodVisitor.visitCode();
          methodVisitor.visitVarInsn(ALOAD, 0);
          methodVisitor.visitMethodInsn(INVOKESPECIAL, superName, initName, "()V", false);
          methodVisitor.visitVarInsn(ALOAD, 0);
          methodVisitor.visitVarInsn(ALOAD, 1);
          methodVisitor.visitFieldInsn(PUTFIELD, PackageConstants.kArrayWrapperSlashPrefix + "ObjectArray", "underlying", "[Ljava/lang/Object;");
          methodVisitor.visitInsn(RETURN);
          methodVisitor.visitMaxs(2, 2);
          methodVisitor.visitEnd();
  
          methodVisitor = cw.visitMethod(ACC_PUBLIC, initName, "()V", null, null);
          methodVisitor.visitCode();
          methodVisitor.visitVarInsn(ALOAD, 0);
          methodVisitor.visitMethodInsn(INVOKESPECIAL, superName, initName, "()V", false);
          methodVisitor.visitInsn(RETURN);
          methodVisitor.visitMaxs(1, 1);
          methodVisitor.visitEnd();
  
          // Create the deserialization constructor (as seen in AutomaticGraphVisitor).
          String deserializationConstructorDescriptor = "(Ljava/lang/Void;I)V";
          methodVisitor = cw.visitMethod(Opcodes.ACC_PUBLIC, initName, deserializationConstructorDescriptor, null, null);
          methodVisitor.visitCode();
          methodVisitor.visitVarInsn(Opcodes.ALOAD, 0);
          methodVisitor.visitVarInsn(Opcodes.ALOAD, 1);
          methodVisitor.visitVarInsn(Opcodes.ILOAD, 2);
          methodVisitor.visitMethodInsn(Opcodes.INVOKESPECIAL, superName, initName, deserializationConstructorDescriptor, false);
          methodVisitor.visitInsn(Opcodes.RETURN);
          methodVisitor.visitMaxs(4, 4);
          methodVisitor.visitEnd();
      }
  
      private static void genClone(ClassWriter cw, String wrapper) {
          String cloneMethodName = "avm_clone";
          String cloneMethodDesc = "()Li/IObject;";
          MethodVisitor methodVisitor = cw.visitMethod(ACC_PUBLIC, cloneMethodName, cloneMethodDesc, null, null);
  
          // energy charge
          methodVisitor.visitLdcInsn(RuntimeMethodFeeSchedule.ObjectArray_avm_clone);
          methodVisitor.visitVarInsn(ALOAD, 0);
          methodVisitor.visitMethodInsn(INVOKEVIRTUAL, wrapper, "length", "()I", false);
          methodVisitor.visitMethodInsn(INVOKESTATIC, SHADOW_ARRAY, "chargeEnergyClone", "(II)V", false);
  
          // lazyLoad
          methodVisitor.visitVarInsn(ALOAD, 0);
          methodVisitor.visitMethodInsn(INVOKEVIRTUAL, wrapper, "lazyLoad", "()V", false);
  
          methodVisitor.visitCode();
          methodVisitor.visitTypeInsn(NEW, wrapper);
          methodVisitor.visitInsn(DUP);
          methodVisitor.visitVarInsn(ALOAD, 0);
          methodVisitor.visitFieldInsn(GETFIELD, wrapper, "underlying", "[Ljava/lang/Object;");
          methodVisitor.visitVarInsn(ALOAD, 0);
          methodVisitor.visitFieldInsn(GETFIELD, wrapper, "underlying", "[Ljava/lang/Object;");
          methodVisitor.visitInsn(ARRAYLENGTH);
          methodVisitor.visitMethodInsn(INVOKESTATIC, "java/util/Arrays", "copyOf", "([Ljava/lang/Object;I)[Ljava/lang/Object;", false);
          methodVisitor.visitMethodInsn(INVOKESPECIAL, wrapper, "<init>", "([Ljava/lang/Object;)V", false);
          methodVisitor.visitInsn(ARETURN);
          methodVisitor.visitMaxs(4, 1);
          methodVisitor.visitEnd();
      }
  
      private static String typeDescriptorForClass(Class<?> clazz) {
          return 'L' + clazz.getName() + ';';
      }
  
  }
  