docs: private static void add_invokeMethod(ClassWriter cw,
                                           String tieClassName,
                                           String[] idlNames)
      {
          GeneratorAdapter mg;
  
          if (TraceComponent.isAnyTracingEnabled() && tc.isDebugEnabled())
              Tr.debug(tc, INDENT + "adding method : _invoke " +
                           "(Ljava/lang/String;Lorg/omg/CORBA/portable/InputStream;" +
                           "Lorg/omg/CORBA/portable/ResponseHandler;)" +
                           "Lorg/omg/CORBA/portable/OutputStream;");
  
          // -----------------------------------------------------------------------
          // public OutputStream _invoke( String method,
          //                              InputStream _in,
          //                              ResponseHandler reply )
          //    throws SystemException
          // -----------------------------------------------------------------------
  
          Type returnType = TYPE_CORBA_OutputStream;
          Type[] argTypes = new Type[] { TYPE_String,
                                        TYPE_CORBA_InputStream,
                                        TYPE_CORBA_ResponseHandler };
          Type[] exceptionTypes = new Type[] { TYPE_CORBA_SystemException };
  
          org.objectweb.asm.commons.Method m =
                          new org.objectweb.asm.commons.Method("_invoke",
                                          returnType,
                                          argTypes);
  
          // Create an ASM GeneratorAdapter object for the ASM Method, which
          // makes generating dynamic code much easier... as it keeps track
          // of the position of the arguements and local variables, etc.
          mg = new GeneratorAdapter(ACC_PUBLIC, m, null, exceptionTypes, cw);
  
          // -----------------------------------------------------------------------
          // Begin Method Code...
          // -----------------------------------------------------------------------
          mg.visitCode();
  
          /*
           * mg.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
           * mg.visitTypeInsn(NEW, "java/lang/StringBuilder");
           * mg.visitInsn(DUP);
           * mg.visitMethodInsn(INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "()V");
           * mg.visitLdcInsn("_invoke called for ");
           * mg.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;");
           * mg.visitVarInsn(ALOAD, 1);
           * mg.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;");
           * mg.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;");
           * mg.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println",
           * "(Ljava/lang/String;)V");
           */
  
          // -----------------------------------------------------------------------
          // try
          // {
          // -----------------------------------------------------------------------
          Label try_begin = new Label();
          mg.visitLabel(try_begin);
  
          // -----------------------------------------------------------------------
          //   InputStream in = (org.omg.CORBA_2_3.portable.InputStream) _in;
          // -----------------------------------------------------------------------
          int in = mg.newLocal(TYPE_CORBA_2_3_InputStream);
          mg.loadArg(1);
          mg.checkCast(TYPE_CORBA_2_3_InputStream);
          mg.storeLocal(in);
  
          // -----------------------------------------------------------------------
          // The next code to 'generate' is a switch statement, which is basically
          // what '_invoke' is all about.  The code will 'switch' based on the
          // hashCode of the OMG IDL name of the method invoked, and each 'case'
          // will determine the specific method for that hashCode, and delegate
          // the work to a private method by that name. However, before adding the
          // 'switch' statement, a few things need to be set up:
          //
          // First, in bytecode, all of the 'switch' statement keys (hashCode
          // values in this case) must be in ascending order. So, all of the OMG
          // IDL names for the methods must be sorted in hashcode ascending order.
          //
          // Second, the 'switch' bytecode requires arrays of the possible key
          // values (hashCodes), and labels (case jump points). So, pre-create
          // the arrays and labels (in ascending order).  These arrays will be
          // created with a length equal to the number of methods, since this will
          // be the most likely scenario: one 'case' per method.
          //
          // Finally, if two or more methods happen to have the same hashCode,
          // then reduce the size of the 'switch/case' arrays.  Note that each
          // method will still have a unique label, just not all will be
          // 'case' statements.
          // -----------------------------------------------------------------------
  
          // Clone the array returned by reflections (so the caller isn't affected)
          // and then use the java Arrays utility method to perform the sort.  A
          // special hash sort comparator is used that knows how to sort objects
          // based on the hashCode, rather than 'natural order'.
          int numMethods = idlNames.length;
          String[] sortedNames = idlNames.clone();
          Arrays.sort(sortedNames, new HashSorter<String>());
  
          int numCases = 0; // might not equal numMethods!
          int[] case_keys = new int[numMethods]; // idl hashCodes
          Label[] case_labels = new Label[numMethods];
          Label case_default = new Label();
          Label[] method_labels = new Label[numMethods];
  
          // Create a Label for every method, and add that Label and corresponding
          // hashcode to the lists for the 'case' statements if the hashCode is not
          // a duplicate.
          for (int i = 0; i < numMethods; ++i)
          {
              Label method_label = new Label();
              method_labels[i] = method_label;
              int hashCode = sortedNames[i].hashCode();
              if (i == 0 ||
                  hashCode != sortedNames[i - 1].hashCode())
              {
                  case_keys[numCases] = hashCode;
                  case_labels[numCases] = method_label;
                  ++numCases;
              }
          }
  
          // Reduce the sizes of the 'switch/case' statement arrays if some
          // methods had the same hashCode.
          if (numCases != numMethods)
          {
              int[] new_keys = new int[numCases];
              System.arraycopy(case_keys, 0, new_keys, 0, numCases);
              case_keys = new_keys;
  
              Label[] new_labels = new Label[numCases];
              System.arraycopy(case_labels, 0, new_labels, 0, numCases);
              case_labels = new_labels;
          }
  
          // -----------------------------------------------------------------------
          //   switch ( method.hashCode() )
          //   {
          // -----------------------------------------------------------------------
          mg.loadArg(0);
          mg.visitMethodInsn(INVOKEVIRTUAL, "java/lang/String", "hashCode", "()I");
          mg.visitLookupSwitchInsn(case_default, case_keys, case_labels);
  
          // -----------------------------------------------------------------------
          //     // For all methods.... add the following case:
          //     // Note: if multiple methods have the same hash code, then there
          //     //       would be multiple 'if' statements for that 'case'
          //     case <hash code>:
          //       if ( method.equals( <idl name> ) )
          //       {
          //         return <idl name>( in, reply );     // private delegate method
          //       }
          //
          //       // Note: no break statement. Fall through... to the 'default'
          //       // for the BadOperationException... bytcode is much much easier
          //       // without the break statements.
          // -----------------------------------------------------------------------
          for (int i = 0; i < numMethods; ++i)
          {
              mg.visitLabel(method_labels[i]);
              mg.loadArg(0);
              mg.push(sortedNames[i]);
              mg.visitMethodInsn(INVOKEVIRTUAL, "java/lang/String",
                                 "equals", "(Ljava/lang/Object;)Z");
              int nextMethod = i + 1;
              Label next_label = case_default;
              if (nextMethod < numMethods)
                  next_label = method_labels[nextMethod];
              mg.visitJumpInsn(IFEQ, next_label);
              mg.loadThis();
              mg.loadLocal(in);
              mg.loadArg(2);
              mg.visitMethodInsn(INVOKESPECIAL, tieClassName, sortedNames[i],
                                 "(Lorg/omg/CORBA_2_3/portable/InputStream;Lorg/omg/CORBA/portable/ResponseHandler;)Lorg/omg/CORBA/portable/OutputStream;");
              mg.returnValue();
          }
  
          // -----------------------------------------------------------------------
          //     default:
          // -----------------------------------------------------------------------
          mg.visitLabel(case_default);
  
          // -----------------------------------------------------------------------
          //       throw new BAD_OPERATION( method );
          // -----------------------------------------------------------------------
          // Note: RMIC uses the default constructor. The method name is passed
          //       here to provide a more meaningful exception.
          mg.visitTypeInsn(NEW, "org/omg/CORBA/BAD_OPERATION");
          mg.visitInsn(DUP);
          mg.loadArg(0);
          mg.visitMethodInsn(INVOKESPECIAL, "org/omg/CORBA/BAD_OPERATION",
                             "<init>", "(Ljava/lang/String;)V");
          mg.visitInsn(ATHROW);
  
          // -----------------------------------------------------------------------
          //   }     // end switch
          // }     // end try
          // -----------------------------------------------------------------------
          Label try_end = new Label();
          mg.visitLabel(try_end);
  
          // -----------------------------------------------------------------------
          // catch ( SystemException ex )
          // {
          //   throw ex;
          // }
          // -----------------------------------------------------------------------
          Label catch_SysException_label = try_end;
          int ex = mg.newLocal(TYPE_CORBA_SystemException);
          mg.storeLocal(ex);
          mg.loadLocal(ex);
          mg.visitInsn(ATHROW);
  
          // -----------------------------------------------------------------------
          // catch ( Throwable th )
          // {
          //   throw new UnknownException( th );
          // }
          // -----------------------------------------------------------------------
          Label catch_Throwable_label = new Label();
          mg.visitLabel(catch_Throwable_label);
          int th = mg.newLocal(TYPE_Throwable);
          mg.storeLocal(th);
          mg.visitTypeInsn(NEW, "org/omg/CORBA/portable/UnknownException");
          mg.visitInsn(DUP);
          mg.loadLocal(th);
          mg.visitMethodInsn(INVOKESPECIAL,
                             "org/omg/CORBA/portable/UnknownException",
                             "<init>", "(Ljava/lang/Throwable;)V");
          mg.visitInsn(ATHROW);
  
          // -----------------------------------------------------------------------
          // All Try-Catch-Finally definitions for above
          // -----------------------------------------------------------------------
          mg.visitTryCatchBlock(try_begin, try_end,
                                catch_SysException_label,
                                "org/omg/CORBA/SystemException");
          mg.visitTryCatchBlock(try_begin, try_end,
                                catch_Throwable_label,
                                "java/lang/Throwable");
  
          // -----------------------------------------------------------------------
          // End Method Code...
          // -----------------------------------------------------------------------
          mg.endMethod(); // GeneratorAdapter accounts for visitMaxs(x,y)
          mg.visitEnd();
      }