/* 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;
  }