/* The static initializer */
  static {
    String USERCLASSPACKAGE = System.getProperty("DynamicEventClassPackage", "");

    if (!USERCLASSPACKAGE.equals("")) {
      CLASSPACKAGE = USERCLASSPACKAGE;
    }

    if (CLASSPACKAGE.length() > 0) {
      CLASSPACKAGE = CLASSPACKAGE.replace('\\', '/');
      if (!CLASSPACKAGE.endsWith("/")) {
        CLASSPACKAGE = CLASSPACKAGE + "/";
      }
    }
    WRITEDIRECTORY = System.getProperty("DynamicEventClassWriteDirectory", CLASSPACKAGE);
    if (WRITEDIRECTORY.length() > 0) {
      WRITEDIRECTORY = WRITEDIRECTORY.replace('\\', '/');
      if (!WRITEDIRECTORY.endsWith("/")) {
        WRITEDIRECTORY = WRITEDIRECTORY + "/";
      }
    }
    try {
      EVENTLISTENER = Class.forName("java.util.EventListener");
    } catch (ClassNotFoundException ex) {
      System.err.println(ex.getMessage());
      ex.printStackTrace();
    }

    // start of the Java Class File
    CLASSHEADER = ByteUtility.addBytes(CLASSHEADER, (byte) 0xCA); // magic
    CLASSHEADER = ByteUtility.addBytes(CLASSHEADER, (byte) 0xFE); // magic
    CLASSHEADER = ByteUtility.addBytes(CLASSHEADER, (byte) 0xBA); // magic
    CLASSHEADER = ByteUtility.addBytes(CLASSHEADER, (byte) 0xBE); // magic
    CLASSHEADER = ByteUtility.addBytes(CLASSHEADER, (short) 3); // minor version
    CLASSHEADER = ByteUtility.addBytes(CLASSHEADER, (short) 45); // major version

    // Start the constant pool for base items in all event adapter classes
    BASECPCOUNT = 17; // number of cp items + 1 ( cp item # 0 reserved for JVM )

    // cp item 01
    BASECP = Bytecode.addUtf8(BASECP, "()V");

    // cp item 02
    BASECP = Bytecode.addUtf8(BASECP, "<init>");

    // cp item 03
    BASECP = Bytecode.addUtf8(BASECP, "Code");

    // cp item 04
    BASECP = Bytecode.addUtf8(BASECP, "eventProcessor");

    // cp item 05
    BASECP = Bytecode.addUtf8(BASECP, "java/lang/Object");

    // cp item 06
    BASECP = Bytecode.addUtf8(BASECP, "org/apache/bsf/util/event/EventAdapterImpl");

    // cp item 07
    BASECP = Bytecode.addUtf8(BASECP, "org/apache/bsf/util/event/EventProcessor");

    // cp item 08
    BASECP = Bytecode.addUtf8(BASECP, "(Ljava/lang/String;[Ljava/lang/Object;)V");

    // cp item 09
    BASECP = Bytecode.addUtf8(BASECP, "Lorg/apache/bsf/util/event/EventProcessor;");

    // cp item 10
    BASECP = Bytecode.addClass(BASECP, (short) 5); // Class "java/lang/Object"

    // cp item 11
    BASECP =
        Bytecode.addClass(BASECP, (short) 6); // Class "org/apache/bsf/util/event/EventAdapterImpl"

    // cp item 12
    BASECP =
        Bytecode.addClass(BASECP, (short) 7); // Class "org/apache/bsf/util/event/EventProcessor"

    // cp item 13
    BASECP = Bytecode.addNameAndType(BASECP, (short) 2, (short) 1); // "<init>" "()V"

    // cp item 14
    BASECP =
        Bytecode.addNameAndType(
            BASECP, (short) 4,
            (short) 9); // "eventProcessor" "Lorg/apache/bsf/util/event/EventProcessor;"

    // cp item 15
    BASECP = Bytecode.addFieldRef(BASECP, (short) 11, (short) 14);

    // cp item 16
    BASECP = Bytecode.addMethodRef(BASECP, (short) 11, (short) 13);

    // fixed bytes in middle of class file
    FIXEDCLASSBYTES =
        ByteUtility.addBytes(FIXEDCLASSBYTES, (short) 0x21); // access_flags        (fixed)
    FIXEDCLASSBYTES =
        ByteUtility.addBytes(FIXEDCLASSBYTES, (short) 20); // this_class          (fixed)
    FIXEDCLASSBYTES =
        ByteUtility.addBytes(FIXEDCLASSBYTES, (short) 11); // super_class         (fixed)
    FIXEDCLASSBYTES =
        ByteUtility.addBytes(FIXEDCLASSBYTES, (short) 1); // interface_count     (fixed)
    FIXEDCLASSBYTES =
        ByteUtility.addBytes(FIXEDCLASSBYTES, (short) 19); // interfaces          (fixed)
    FIXEDCLASSBYTES =
        ByteUtility.addBytes(FIXEDCLASSBYTES, (short) 0); // field_count         (fixed)

    // initialization method, constructor
    INITMETHOD = ByteUtility.addBytes(INITMETHOD, (short) 1); // access_flags
    INITMETHOD = ByteUtility.addBytes(INITMETHOD, (short) 2); // name_index "<init>"
    INITMETHOD = ByteUtility.addBytes(INITMETHOD, (short) 1); // descriptor_index "()V"
    INITMETHOD = ByteUtility.addBytes(INITMETHOD, (short) 1); // attribute_count
    INITMETHOD = ByteUtility.addBytes(INITMETHOD, (short) 3); // attribute_name_index "Code"
    INITMETHOD = ByteUtility.addBytes(INITMETHOD, (long) 17); // attribute_length
    INITMETHOD = ByteUtility.addBytes(INITMETHOD, (short) 1); // max_stack
    INITMETHOD = ByteUtility.addBytes(INITMETHOD, (short) 1); // max_locals
    INITMETHOD = ByteUtility.addBytes(INITMETHOD, (long) 5); // code_length
    // code
    INITMETHOD = ByteUtility.addBytes(INITMETHOD, (byte) 0x2A); // aload_0
    INITMETHOD = ByteUtility.addBytes(INITMETHOD, (byte) 0xB7); // invokespecial
    INITMETHOD = ByteUtility.addBytes(INITMETHOD, (short) 16); // method_ref index
    INITMETHOD = ByteUtility.addBytes(INITMETHOD, (byte) 0xB1); // return
    // exception table
    INITMETHOD = ByteUtility.addBytes(INITMETHOD, (short) 0); // exception_table_length
    INITMETHOD = ByteUtility.addBytes(INITMETHOD, (short) 0); // attributes_count
  }
  /* methods that take an EventListener Class Type to create an EventAdapterClass */
  public static Class makeEventAdapterClass(Class listenerType, boolean writeClassFile) {
    DebugLog.stdoutPrintln("EventAdapterGenerator", DebugLog.BSF_LOG_L3);

    if (EVENTLISTENER.isAssignableFrom(listenerType)) {
      boolean exceptionable = false;
      boolean nonExceptionable = false;
      byte constantPool[] = null;
      short cpBaseIndex;
      short cpCount = 0;
      short cpExceptionBaseIndex;
      short exceptionableCount;
      short nonExceptionableCount;

      /* Derive Names */
      String listenerTypeName = listenerType.getName();
      DebugLog.stdoutPrintln("  ListenerTypeName: " + listenerTypeName, DebugLog.BSF_LOG_L3);
      String adapterClassName =
          CLASSPACKAGE
              + (listenerTypeName.endsWith("Listener")
                      ? listenerTypeName.substring(0, listenerTypeName.length() - 8)
                      : listenerTypeName)
                  .replace('.', '_')
              + "Adapter";
      String finalAdapterClassName = adapterClassName;
      Class cached = null;
      int suffixIndex = 0;

      do {
        if (null != (cached = ldr.getLoadedClass(finalAdapterClassName))) {
          DebugLog.stdoutPrintln("cached:  " + cached, DebugLog.BSF_LOG_L3);
          try {
            if (!listenerType.isAssignableFrom(cached))
              finalAdapterClassName = adapterClassName + "_" + suffixIndex++;
            else return cached;
          } catch (VerifyError ex) {
            System.err.println(ex.getMessage());
            ex.printStackTrace();
            return cached;
          }
        }
      } while (cached != null);

      String eventListenerName = listenerTypeName.replace('.', '/');

      /* method stuff */
      java.lang.reflect.Method lms[] = listenerType.getMethods();

      /* ****************************************************************************************** */
      // Listener interface
      // Class name
      cpCount += 4;

      // cp item 17
      constantPool = Bytecode.addUtf8(constantPool, eventListenerName);

      // cp item 18
      constantPool = Bytecode.addUtf8(constantPool, finalAdapterClassName);

      // cp item 19
      constantPool = Bytecode.addClass(constantPool, (short) 17);

      // cp item 20
      constantPool = Bytecode.addClass(constantPool, (short) 18);

      // do we have nonExceptionalble event, exceptionable or both
      for (int i = 0; i < lms.length; ++i) {
        Class exceptionTypes[] = lms[i].getExceptionTypes();
        if (0 < exceptionTypes.length) {
          exceptionable = true;
        } else {
          nonExceptionable = true;
        }
      } /* End for*/

      /* ****************************************************************************************** */
      // optional inclusion of nonexceptional events affects exceptional events indices

      nonExceptionableCount = 0;
      if (nonExceptionable) {
        nonExceptionableCount = 3;
        cpCount += nonExceptionableCount;

        // cp item 21
        constantPool = Bytecode.addUtf8(constantPool, "processEvent");

        // cp item 22
        constantPool = Bytecode.addNameAndType(constantPool, (short) 21, (short) 8);

        // cp item 23
        constantPool = Bytecode.addInterfaceMethodRef(constantPool, (short) 12, (short) 22);
      }

      /* ****************************************************************************************** */
      // optional inclusion of exceptional events affects CP Items which follow for specific methods

      exceptionableCount = 0;
      if (exceptionable) {
        int classIndex = BASECPCOUNT + cpCount + 1;
        int nameIndex = BASECPCOUNT + cpCount + 0;
        int natIndex = BASECPCOUNT + cpCount + 3;

        exceptionableCount = 5;
        cpCount += exceptionableCount;

        // cp item 24 or 21
        constantPool = Bytecode.addUtf8(constantPool, "processExceptionableEvent");

        // cp item 25 or 22
        constantPool = Bytecode.addUtf8(constantPool, "java/lang/Exception");

        // cp item 26 or 23
        constantPool = Bytecode.addClass(constantPool, (short) classIndex);

        // cp item 27 or 24
        constantPool = Bytecode.addNameAndType(constantPool, (short) nameIndex, (short) 8);

        // cp item 28 or 25
        constantPool = Bytecode.addInterfaceMethodRef(constantPool, (short) 12, (short) natIndex);
      }

      // base index for method cp references
      cpBaseIndex = (short) (BASECPCOUNT + cpCount);
      DebugLog.stderrPrintln("cpBaseIndex: " + cpBaseIndex, DebugLog.BSF_LOG_L3);

      for (int i = 0; i < lms.length; ++i) {
        String eventMethodName = lms[i].getName();
        String eventName = lms[i].getParameterTypes()[0].getName().replace('.', '/');
        cpCount += 3;
        // cp items for event methods
        constantPool = Bytecode.addUtf8(constantPool, eventMethodName);
        constantPool = Bytecode.addUtf8(constantPool, ("(L" + eventName + ";)V"));
        constantPool = Bytecode.addString(constantPool, (short) (BASECPCOUNT + cpCount - 3));
      } /* End for*/

      boolean propertyChangeFlag[] = new boolean[lms.length];
      int cpIndexPCE = 0;
      for (int i = 0; i < lms.length; ++i) {
        String eventName = lms[i].getParameterTypes()[0].getName().replace('.', '/');
        // cp items for PropertyChangeEvent special handling
        if (eventName.equalsIgnoreCase("java/beans/PropertyChangeEvent")) {
          propertyChangeFlag[i] = true;
          if (0 == cpIndexPCE) {
            constantPool = Bytecode.addUtf8(constantPool, eventName);
            constantPool = Bytecode.addUtf8(constantPool, "getPropertyName");
            constantPool = Bytecode.addUtf8(constantPool, "()Ljava/lang/String;");
            constantPool = Bytecode.addClass(constantPool, (short) (BASECPCOUNT + cpCount));
            constantPool =
                Bytecode.addNameAndType(
                    constantPool,
                    (short) (BASECPCOUNT + cpCount + 1),
                    (short) (BASECPCOUNT + cpCount + 2));
            constantPool =
                Bytecode.addMethodRef(
                    constantPool,
                    (short) (BASECPCOUNT + cpCount + 3),
                    (short) (BASECPCOUNT + cpCount + 4));
            cpCount += 6;
            cpIndexPCE = BASECPCOUNT + cpCount - 1;
          }
        } else {
          propertyChangeFlag[i] = false;
        }
      } /* End for*/

      cpExceptionBaseIndex = (short) (BASECPCOUNT + cpCount);
      DebugLog.stderrPrintln("cpExceptionBaseIndex: " + cpExceptionBaseIndex, DebugLog.BSF_LOG_L3);

      int excpIndex[][] = new int[lms.length][];
      for (int i = 0; i < lms.length; ++i) {
        Class exceptionTypes[] = lms[i].getExceptionTypes();
        excpIndex[i] = new int[exceptionTypes.length];
        for (int j = 0; j < exceptionTypes.length; j++) {
          constantPool =
              Bytecode.addUtf8(constantPool, exceptionTypes[j].getName().replace('.', '/'));
          constantPool = Bytecode.addClass(constantPool, (short) (BASECPCOUNT + cpCount));
          excpIndex[i][j] = BASECPCOUNT + cpCount + 1;
          cpCount += 2;
        }
      } /* End for*/
      /* end constant pool */

      /* ************************************************************************************************ */
      // put the Class byte array together

      /* start */
      byte newClass[] = CLASSHEADER; // magic, version      (fixed)
      short count = (short) (BASECPCOUNT + cpCount);
      newClass = ByteUtility.addBytes(newClass, count); // constant_pool_count (variable)
      newClass = ByteUtility.addBytes(newClass, BASECP); // constant_pool       (fixed)
      newClass = ByteUtility.addBytes(newClass, constantPool); // constant_pool       (variable)
      newClass = ByteUtility.addBytes(newClass, FIXEDCLASSBYTES); // see FIXEDCLASSBYTES (fixed)
      newClass =
          ByteUtility.addBytes(
              newClass, (short) (lms.length + 1)); // method_count        (variable)
      newClass = ByteUtility.addBytes(newClass, INITMETHOD); // constructor <init>  (fixed)
      // methods

      /* ****************************************************************************************** */
      /* loop over listener methods from listenerType */
      for (int i = 0; i < lms.length; ++i) {
        newClass = ByteUtility.addBytes(newClass, (short) 1); // access_flags             (fixed)
        newClass =
            ByteUtility.addBytes(
                newClass, (short) (cpBaseIndex + 3 * i + 0)); // name_index               (variable)
        newClass =
            ByteUtility.addBytes(
                newClass, (short) (cpBaseIndex + 3 * i + 1)); // descriptor_index         (variable)
        newClass = ByteUtility.addBytes(newClass, (short) 1); // attribute_count          (fixed)
        newClass = ByteUtility.addBytes(newClass, (short) 3); // attribute_name_index code(fixed)

        // Code Attribute Length
        int length = 32;
        if (0 < excpIndex[i].length) {
          length += 5 + 8 * (1 + excpIndex[i].length);
        }
        if (propertyChangeFlag[i]) {
          length += 2;
        }
        newClass =
            ByteUtility.addBytes(newClass, (long) length); // attribute_length         (variable)

        // start code attribute
        newClass = ByteUtility.addBytes(newClass, (short) 6); // max_stack                (fixed)
        newClass = ByteUtility.addBytes(newClass, (short) 3); // max_locals               (fixed)

        // Code Length
        length = 20;
        if (exceptionable && 0 < excpIndex[i].length) {
          length += 5;
        }
        if (propertyChangeFlag[i]) {
          length += 2;
        }
        newClass =
            ByteUtility.addBytes(newClass, (long) length); // code_length              (variable)

        // start code
        newClass = ByteUtility.addBytes(newClass, (byte) 0x2A); // aload_0                  (fixed)
        newClass = ByteUtility.addBytes(newClass, (byte) 0xB4); // getfield                 (fixed)
        newClass = ByteUtility.addBytes(newClass, (short) 15); // index                    (fixed)

        if (propertyChangeFlag[i]) { // the propertyName is passed as the first parameter
          newClass =
              ByteUtility.addBytes(newClass, (byte) 0x2B); // aload_1                  (fixed)
          newClass =
              ByteUtility.addBytes(newClass, (byte) 0xB6); // invokevirtual            (fixed)
          newClass =
              ByteUtility.addBytes(
                  newClass, (short) cpIndexPCE); // methodref                (variable)
        } else { // the eventMethodName is passed as the first parameter
          // Target for method invocation.
          newClass = ByteUtility.addBytes(newClass, (byte) 0x12); // ldc                    (fixed)
          newClass =
              ByteUtility.addBytes(
                  newClass, (byte) (cpBaseIndex + 3 * i + 2)); // index (byte)           (variable)
        }

        newClass = ByteUtility.addBytes(newClass, (byte) 0x04); // iconst_1                 (fixed)
        newClass = ByteUtility.addBytes(newClass, (byte) 0xBD); // anewarray                (fixed)
        newClass = ByteUtility.addBytes(newClass, (short) 10); // Class java/lang/Object   (fixed)
        newClass = ByteUtility.addBytes(newClass, (byte) 0x59); // dup                      (fixed)
        newClass = ByteUtility.addBytes(newClass, (byte) 0x03); // iconst_0                 (fixed)
        newClass = ByteUtility.addBytes(newClass, (byte) 0x2B); // aload_1                  (fixed)
        newClass = ByteUtility.addBytes(newClass, (byte) 0x53); // aastore                  (fixed)
        newClass = ByteUtility.addBytes(newClass, (byte) 0xB9); // invokeinterface          (fixed)

        // index to processEvent or processExceptionableEvent method
        length = 23; // actually an index into cp
        if (exceptionable && nonExceptionable) { // interface method index
          if (0 < lms[i].getExceptionTypes().length) {
            length += 5;
          }
        } else if (exceptionable) {
          length += 2;
        }
        newClass =
            ByteUtility.addBytes(newClass, (short) length); // index (process??????...) (variable)

        newClass = ByteUtility.addBytes(newClass, (byte) 0x03); // iconst_0                 (fixed)
        newClass = ByteUtility.addBytes(newClass, (byte) 0x00); // noop                     (fixed)
        newClass = ByteUtility.addBytes(newClass, (byte) 0xB1); // return                   (fixed)

        if (exceptionable && 0 < excpIndex[i].length) { // exception code
          newClass =
              ByteUtility.addBytes(newClass, (byte) 0x4D); // astore_2                 (fixed)
          newClass =
              ByteUtility.addBytes(newClass, (byte) 0x2C); // aload_2                  (fixed)
          newClass =
              ByteUtility.addBytes(newClass, (byte) 0xBF); // athrow                   (fixed)
          newClass =
              ByteUtility.addBytes(newClass, (byte) 0x57); // pop                      (fixed)
          newClass =
              ByteUtility.addBytes(newClass, (byte) 0xB1); // return                   (fixed)
          // end code

          // exception table
          length = excpIndex[i].length;
          newClass =
              ByteUtility.addBytes(
                  newClass, (short) (1 + length)); // exception_table_length   (variable)
          for (int j = 0; j < length; j++) { // catch exception types and rethrow
            newClass =
                ByteUtility.addBytes(newClass, (short) 0); // start_pc                 (fixed)
            if (propertyChangeFlag[i]) {
              newClass =
                  ByteUtility.addBytes(newClass, (short) 21); // end_pc                   (fixed)
              newClass =
                  ByteUtility.addBytes(newClass, (short) 22); // handler_pc               (fixed)
            } else {
              newClass =
                  ByteUtility.addBytes(newClass, (short) 19); // end_pc                   (fixed)
              newClass =
                  ByteUtility.addBytes(newClass, (short) 20); // handler_pc               (fixed)
            }
            newClass =
                ByteUtility.addBytes(
                    newClass, (short) excpIndex[i][j]); // catch_type               (variable)
          }
          // catch "exception" and trap it
          newClass = ByteUtility.addBytes(newClass, (short) 0); // start_pc                 (fixed)
          if (propertyChangeFlag[i]) {
            newClass =
                ByteUtility.addBytes(newClass, (short) 21); // end_pc                   (fixed)
            newClass =
                ByteUtility.addBytes(newClass, (short) 25); // handler_pc               (fixed)
          } else {
            newClass =
                ByteUtility.addBytes(newClass, (short) 19); // end_pc                   (fixed)
            newClass =
                ByteUtility.addBytes(newClass, (short) 23); // handler_pc               (fixed)
          }
          if (nonExceptionable) {
            newClass = ByteUtility.addBytes(newClass, (short) 26);
          } // catch_type               (fixed)
          else // or
          {
            newClass = ByteUtility.addBytes(newClass, (short) 23);
          } // catch_type               (fixed)
        } else {
          newClass = ByteUtility.addBytes(newClass, (short) 0);
        } // exception_table_length   (fixed)
        // attributes on the code attribute (none)
        newClass = ByteUtility.addBytes(newClass, (short) 0); // attribute_count          (fixed)
        // end code attribute

      } /* End for*/
      // Class Attributes (none for this)
      newClass = ByteUtility.addBytes(newClass, (short) 0); // attribute_count          (fixed)
      /* done */

      DebugLog.stdoutPrintln("adapterName: " + finalAdapterClassName, DebugLog.BSF_LOG_L3);
      DebugLog.stdoutPrintln(
          "cpCount: " + count + " = " + BASECPCOUNT + " + " + cpCount, DebugLog.BSF_LOG_L3);
      DebugLog.stdoutPrintln("methodCount: " + (lms.length + 1), DebugLog.BSF_LOG_L3);
      // output to disk class file
      /* ****************************************************************************************** */

      // now create the class and load it
      // return the Class.

      if (writeClassFile) {
        try {
          FileOutputStream fos =
              new FileOutputStream(WRITEDIRECTORY + finalAdapterClassName + ".class");
          fos.write(newClass);
          fos.close();
        } catch (IOException ex) {
          System.err.println(ex.getMessage());
          ex.printStackTrace();
        }

        try {
          Class ret = ldr.loadClass(finalAdapterClassName);
          DebugLog.stdoutPrintln(
              "EventAdapterGenerator: " + ret.getName() + " dynamically generated",
              DebugLog.BSF_LOG_L3);
          return ret;
        } catch (ClassNotFoundException ex) {
          System.err.println(ex.getMessage());
          ex.printStackTrace();
        }
      }

      try {
        Class ret = ldr.defineClass(finalAdapterClassName, newClass);
        DebugLog.stdoutPrintln(
            "EventAdapterGenerator: " + ret.getName() + " dynamically generated",
            DebugLog.BSF_LOG_L3);
        return ret;
      } catch (Exception ex) {
        System.err.println(ex.getMessage());
        ex.printStackTrace();
      }
    } else {
      System.err.println(
          "EventAdapterGenerator ListenerType invalid: listenerType = " + listenerType);
    }
    return null;
  }