docs: private static void addEJBMethod(ClassWriter cw,
                                       String className,
                                       String implClassName,
                                       EJBWrapperType wrapperType,
                                       Method method,
                                       String implMethodName,
                                       int methodId,
                                       boolean isRmiRemote,
                                       boolean aroundInvoke,
                                       boolean isStatelessCreate)
                      throws EJBConfigurationException
      {
          GeneratorAdapter mg;
          String methodName = method.getName();
          String methodSignature = MethodAttribUtils.jdiMethodSignature(method);
          String EjbPreInvoke = isStatelessCreate ? "EjbPreInvokeForStatelessCreate"
                          : "EjbPreInvoke";
          String EjbPostInvoke = isStatelessCreate ? "EjbPostInvokeForStatelessCreate"
                          : "postInvoke";
          String setUncheckedException = isRmiRemote ? "setUncheckedException"
                          : "setUncheckedLocalException";
  
          if (TraceComponent.isAnyTracingEnabled() && tc.isDebugEnabled())
              Tr.debug(tc, INDENT + "adding method : " +
                           methodName + " " + methodSignature +
                           " : aroundInvoke = " + aroundInvoke);
  
          // Determine the list of 'checked' exceptions, which will need to have
          // catch blocks in the wrapper... and also perform validation.
          // Note that RemoteException is never a 'checked' exception, and
          // exceptions that are subclasses of other 'checked' exceptions
          // will be eliminated, to avoid 'unreachable' code.
          Class<?>[] methodExceptions = method.getExceptionTypes();
          Class<?>[] checkedExceptions = DeploymentUtil.getCheckedExceptions(method,
                                                                             isRmiRemote,
                                                                             DeploymentUtil.DeploymentTarget.WRAPPER, // d660332
                                                                             wrapperType); // RTC116527
  
          // Convert the return value, arguments, and exception classes to
          // ASM Type objects, and create the ASM Method object which will
          // be used to actually add the method and method code.
          Type returnType = Type.getType(method.getReturnType());
          Type[] argTypes = getTypes(method.getParameterTypes());
          Type[] exceptionTypes = getTypes(methodExceptions);
  
          org.objectweb.asm.commons.Method m =
                          new org.objectweb.asm.commons.Method(methodName,
                                          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 arguments and local variables, etc.
          mg = new GeneratorAdapter(ACC_PUBLIC, m, null, exceptionTypes, cw);
  
          // -----------------------------------------------------------------------
          // Begin Method Code...
          // -----------------------------------------------------------------------
          mg.visitCode();
  
          // -----------------------------------------------------------------------
          // <return type> returnValue = false | 0 | null;
          // -----------------------------------------------------------------------
          int returnValue = -1;
          if (returnType != Type.VOID_TYPE)
          {
              returnValue = mg.newLocal(returnType);
              switch (returnType.getSort())
              {
                  case Type.BOOLEAN:
                      mg.push(false);
                      break;
  
                  case Type.CHAR:
                  case Type.BYTE:
                  case Type.SHORT:
                  case Type.INT:
                      mg.push(0);
                      break;
  
                  case Type.FLOAT:
                      mg.push((float) 0);
                      break;
  
                  case Type.LONG:
                      mg.push((long) 0);
                      break;
  
                  case Type.DOUBLE:
                      mg.push((double) 0);
                      break;
  
                  // ARRAY & OBJECT - push a null on the stack
                  default:
                      mg.visitInsn(ACONST_NULL);
                      break;
              }
              mg.storeLocal(returnValue);
          }
  
          int s = -1;
          if (wrapperType != MDB_PROXY && wrapperType != MDB_NO_METHOD_INTERFACE_PROXY) {
              // -----------------------------------------------------------------------
              // EJSDeployedSupport s = new EJSDeployedSupport();
              // -----------------------------------------------------------------------
              s = mg.newLocal(TYPE_EJSDeployedSupport);
              mg.newInstance(TYPE_EJSDeployedSupport);
              mg.dup();
              mg.visitMethodInsn(INVOKESPECIAL, "com/ibm/ejs/container/EJSDeployedSupport", "<init>", "()V");
              mg.storeLocal(s);
          }
          // For MDBs we need to call MessageEndpointBase.checkState() outside
          // the try/catch/finally block.
          // This will verify we are in a valid state to process this invocation.
          // An IllegalStateException is thrown if we are not in a valid state or
          // if we detect the RA violated the JCA 1.5 specification.
          else
          {
              // -----------------------------------------------------------------------
              // this.checkState(methodId, null, MDB_BUSINESS_METHOD);
              // -----------------------------------------------------------------------
              loadWrapperBase(mg, className, wrapperType);
              mg.push(methodId);
              mg.visitInsn(ACONST_NULL);
              mg.push(3);
              mg.visitMethodInsn(wrapperType == MDB_NO_METHOD_INTERFACE_PROXY ? INVOKEVIRTUAL : INVOKESPECIAL, MESSAGE_ENDPOINT_BASE_STRING,
                                 "checkState",
                                 "(ILjava/lang/reflect/Method;B)V");
          }
  
          // -----------------------------------------------------------------------
          // try
          // {
          // -----------------------------------------------------------------------
          Label main_try_begin = new Label();
          mg.visitLabel(main_try_begin);
  
          Label if_JaccArgs_End = null;
  
          // -----------------------------------------------------------------------
          // Object[] args;
          // -----------------------------------------------------------------------
          int args = mg.newLocal(TYPE_Object_ARRAY);
  
          // The parameters always need to be copied for AroundInvoke interceptors,
          // but otherwise only need to be copied for JACC... so only add the
          // JACC check when there are no interceptors for the method.     d367572.4
          if (!aroundInvoke)
          {
              // --------------------------------------------------------------------
              // Object[] args = null;
              // --------------------------------------------------------------------
              mg.visitInsn(ACONST_NULL);
              mg.storeLocal(args);
  
              // --------------------------------------------------------------------
              //   if ( container.doesJaccNeedsEJBArguments( this ) )
              //   {
              // --------------------------------------------------------------------
              loadContainer(mg, className, wrapperType);
              loadWrapperBase(mg, className, wrapperType); // F743-1756
              mg.visitMethodInsn(INVOKEVIRTUAL, "com/ibm/ejs/container/EJSContainer", "doesJaccNeedsEJBArguments", "(Lcom/ibm/ejs/container/EJSWrapperBase;)Z");
              if_JaccArgs_End = new Label();
              mg.visitJumpInsn(IFEQ, if_JaccArgs_End); // comparison to 0
          }
  
          // -----------------------------------------------------------------------
          //     args = new Object[# of args];
          // -----------------------------------------------------------------------
          mg.push(argTypes.length);
          mg.visitTypeInsn(ANEWARRAY, "java/lang/Object");
          mg.storeLocal(args);
  
          // -----------------------------------------------------------------------
          //     args[i] = "parameter";  ->  for each parameter
          // -----------------------------------------------------------------------
          for (int i = 0; i < argTypes.length; i++)
          {
              mg.loadLocal(args);
              mg.push(i);
  
              // Convert primitives to objects to put in 'args' array.
              switch (argTypes[i].getSort())
              {
                  case Type.BOOLEAN:
                      mg.visitTypeInsn(NEW, "java/lang/Boolean");
                      mg.visitInsn(DUP);
                      mg.loadArg(i); // for non-static, arg 0 = "this"
                      mg.visitMethodInsn(INVOKESPECIAL, "java/lang/Boolean", "<init>", "(Z)V");
                      break;
  
                  case Type.CHAR:
                      mg.visitTypeInsn(NEW, "java/lang/Character");
                      mg.visitInsn(DUP);
                      mg.loadArg(i); // for non-static, arg 0 = "this"
                      mg.visitMethodInsn(INVOKESPECIAL, "java/lang/Character", "<init>", "(C)V");
                      break;
  
                  case Type.BYTE:
                      mg.visitTypeInsn(NEW, "java/lang/Byte");
                      mg.visitInsn(DUP);
                      mg.loadArg(i); // for non-static, arg 0 = "this"
                      mg.visitMethodInsn(INVOKESPECIAL, "java/lang/Byte", "<init>", "(B)V");
                      break;
  
                  case Type.SHORT:
                      mg.visitTypeInsn(NEW, "java/lang/Short");
                      mg.visitInsn(DUP);
                      mg.loadArg(i); // for non-static, arg 0 = "this"
                      mg.visitMethodInsn(INVOKESPECIAL, "java/lang/Short", "<init>", "(S)V");
                      break;
  
                  case Type.INT:
                      mg.visitTypeInsn(NEW, "java/lang/Integer");
                      mg.visitInsn(DUP);
                      mg.loadArg(i); // for non-static, arg 0 = "this"
                      mg.visitMethodInsn(INVOKESPECIAL, "java/lang/Integer", "<init>", "(I)V");
                      break;
  
                  case Type.FLOAT:
                      mg.visitTypeInsn(NEW, "java/lang/Float");
                      mg.visitInsn(DUP);
                      mg.loadArg(i); // for non-static, arg 0 = "this"
                      mg.visitMethodInsn(INVOKESPECIAL, "java/lang/Float", "<init>", "(F)V");
                      break;
  
                  case Type.LONG:
                      mg.visitTypeInsn(NEW, "java/lang/Long");
                      mg.visitInsn(DUP);
                      mg.loadArg(i); // for non-static, arg 0 = "this"
                      mg.visitMethodInsn(INVOKESPECIAL, "java/lang/Long", "<init>", "(J)V");
                      break;
  
                  case Type.DOUBLE:
                      mg.visitTypeInsn(NEW, "java/lang/Double");
                      mg.visitInsn(DUP);
                      mg.loadArg(i); // for non-static, arg 0 = "this"
                      mg.visitMethodInsn(INVOKESPECIAL, "java/lang/Double", "<init>", "(D)V");
                      break;
  
                  // ARRAY & OBJECT - no need to copy, just load the arg
                  default:
                      mg.loadArg(i); // for non-static, arg 0 = "this"
                      break;
              }
  
              mg.visitInsn(AASTORE);
          }
  
          // As above, the check around the copy of the parameters was only added
          // when no interceptors, so only need the closing label when the if
          // check was added.                                              d367572.4
          if (!aroundInvoke)
          {
              // --------------------------------------------------------------------
              //   }                   // end of container.doesJaccNeedsEJBAruments()
              // --------------------------------------------------------------------
              mg.visitLabel(if_JaccArgs_End);
          }
  
          // -----------------------------------------------------------------------
          //   <bean impl> bean = (bean impl)container.EjbPreInvoke(this, methodId, s, args);
          //      or
          //   <bean impl> bean = (bean impl)container.EjbPreInvokeForStatelessCreate(this, methodId, s, args
          //      or
          //   <bean impl> bean = (bean impl) super.mdbMethodPreInvoke(methodId,args);
          // -----------------------------------------------------------------------
          Type implType = Type.getType("L" + implClassName + ";");
          int bean = mg.newLocal(implType);
  
          if (wrapperType != MDB_PROXY && wrapperType != MDB_NO_METHOD_INTERFACE_PROXY) {
              // non-MDB preInvoke processing
              loadContainer(mg, className, wrapperType);
              loadWrapperBase(mg, className, wrapperType); // F743-1756
              mg.push(methodId);
              mg.loadLocal(s);
              mg.loadLocal(args);
              mg.visitMethodInsn(INVOKEVIRTUAL, "com/ibm/ejs/container/EJSContainer",
                                 EjbPreInvoke,
                                 "(Lcom/ibm/ejs/container/EJSWrapperBase;ILcom/ibm/ejs/container/EJSDeployedSupport;[Ljava/lang/Object;)Ljava/lang/Object;");
          } else {
              // call MessageEndpointBase.mdbMethodPreInvoke() for a MDB
              loadWrapperBase(mg, className, wrapperType);
              mg.push(methodId);
              mg.loadLocal(args);
              mg.visitMethodInsn(wrapperType == MDB_NO_METHOD_INTERFACE_PROXY ? INVOKEVIRTUAL : INVOKESPECIAL, MESSAGE_ENDPOINT_BASE_STRING,
                                 "mdbMethodPreInvoke",
                                 "(I[Ljava/lang/Object;)Ljava/lang/Object;");
          }
          mg.checkCast(implType);
          mg.storeLocal(bean);
  
          // -----------------------------------------------------------------------
          // Now invoke the business method; one of 2 ways:
          // 1 ) Through the interceptors via EJSContainer.invoke()        d367572.4
          // 2 ) Directly, by calling the method on the bean instance.
          // -----------------------------------------------------------------------
  
          if (aroundInvoke)
          {
              // --------------------------------------------------------------------
              //   this.container.invoke(s,null);
              //      or
              //   rtnValue = (type)this.container.invoke(s,null);
              //      or
              //   rtnValue = ((object type)this.container.invoke(s,null)).<type>Value();  // unbox
              // --------------------------------------------------------------------
              loadContainer(mg, className, wrapperType);
              loadEJSDeployedSupport(mg, className, wrapperType, s);
              mg.visitInsn(ACONST_NULL); // F743-17763.1
              mg.visitMethodInsn(INVOKEVIRTUAL, "com/ibm/ejs/container/EJSContainer",
                                 "invoke",
                                 "(Lcom/ibm/ejs/container/EJSDeployedSupport;Ljavax/ejb/Timer;)Ljava/lang/Object;"); // F743-17763.1
  
              if (returnType == Type.VOID_TYPE)
              {
                  // No return value, just pop the returned value (null) off the stack
                  mg.pop();
              }
              else
              {
                  // Unbox any primitive values or add the appropriate cast
                  // for object/array values, and then store in local variable. d369262.7
                  unbox(mg, returnType);
                  mg.storeLocal(returnValue);
              }
          }
          else
          {
              // --------------------------------------------------------------------
              //   bean.<method>(<args...>);
              //      or
              //   rtnValue = bean.<method>(<args...>);
              // --------------------------------------------------------------------
              mg.loadLocal(bean);
              mg.loadArgs(0, argTypes.length); // do not pass "this"
              mg.visitMethodInsn(INVOKEVIRTUAL, implClassName, implMethodName,
                                 m.getDescriptor());
              if (returnType != Type.VOID_TYPE)
              {
                  mg.storeLocal(returnValue);
              }
          }
  
          // -----------------------------------------------------------------------
          // }                     // end of try
          // -----------------------------------------------------------------------
          Label main_try_end = new Label();
          mg.visitLabel(main_try_end); // mark the end
  
          Label main_finally_begin = new Label();
          mg.visitJumpInsn(JSR, main_finally_begin); // execute the finally
  
          Label main_tcf_exit = new Label();
          mg.visitJumpInsn(GOTO, main_tcf_exit); // Skip to very end
  
          // d660332 - If the throws clause contains Exception, then we need a
          // special catch block for RuntimeException so that it is not treated as
          // an application exception.
          boolean hasThrowsException = false;
          if (ContainerProperties.DeclaredUncheckedAreSystemExceptions)
          {
              for (Class<?> checkedException : checkedExceptions)
              {
                  hasThrowsException |= checkedException == Exception.class;
              }
          }
  
          Label main_catch_runtime_exception = null;
          if (hasThrowsException)
          {
              // -----------------------------------------------------------------------
              // catch (RuntimeException th)
              // {
              //   s.setUncheckedException(th);   or   s.setUncheckedLocalException(th);
              // }
              // -----------------------------------------------------------------------
              main_catch_runtime_exception = new Label();
              mg.visitLabel(main_catch_runtime_exception);
              int main_th = mg.newLocal(TYPE_RuntimeException);
              mg.storeLocal(main_th);
              loadEJSDeployedSupport(mg, className, wrapperType, s);
              mg.loadLocal(main_th);
              mg.visitMethodInsn(INVOKEVIRTUAL, "com/ibm/ejs/container/EJSDeployedSupport",
                                 setUncheckedException, "(Ljava/lang/Throwable;)V");
  
              mg.visitJumpInsn(JSR, main_finally_begin); // execute the finally
              mg.visitJumpInsn(GOTO, main_tcf_exit); // Skip to very end
          }
  
          // -----------------------------------------------------------------------
          // catch (<checked exception> <ex>)  ->  for each <checked exception>
          // {
          //   s.setCheckedException(<ex>);
          //   throw <ex>;
          // }
          // -----------------------------------------------------------------------
          Label[] main_catch_label = new Label[checkedExceptions.length];
          int caught_ex = mg.newLocal(TYPE_Exception);
  
          for (int i = 0; i < checkedExceptions.length; i++)
          {
              main_catch_label[i] = new Label();
              mg.visitLabel(main_catch_label[i]);
              mg.storeLocal(caught_ex);
              loadEJSDeployedSupport(mg, className, wrapperType, s);
              mg.loadLocal(caught_ex);
              mg.visitMethodInsn(INVOKEVIRTUAL,
                                 "com/ibm/ejs/container/EJSDeployedSupport",
                                 "setCheckedException", "(Ljava/lang/Exception;)V");
              mg.loadLocal(caught_ex);
              mg.visitInsn(ATHROW);
          }
  
          // -----------------------------------------------------------------------
          // catch (Throwable th)
          // {
          //   s.setUncheckedException(th);   or   s.setUncheckedLocalException(th);
          // }
          // -----------------------------------------------------------------------
          Label main_catch_throwable = new Label();
          mg.visitLabel(main_catch_throwable);
          int main_th = mg.newLocal(TYPE_Throwable);
          mg.storeLocal(main_th);
          loadEJSDeployedSupport(mg, className, wrapperType, s);
          mg.loadLocal(main_th);
          mg.visitMethodInsn(INVOKEVIRTUAL, "com/ibm/ejs/container/EJSDeployedSupport",
                             setUncheckedException, "(Ljava/lang/Throwable;)V");
  
          mg.visitJumpInsn(JSR, main_finally_begin); // execute the finally
          mg.visitJumpInsn(GOTO, main_tcf_exit); // Skip to very end
  
          // -----------------------------------------------------------------------
          // finally
          // {
          // -----------------------------------------------------------------------
          Label main_finally_ex = new Label();
          mg.visitLabel(main_finally_ex); // finally - after caught exception
          int main_finally_th = mg.newLocal(TYPE_Throwable);
          mg.storeLocal(main_finally_th);
          mg.visitJumpInsn(JSR, main_finally_begin); // finally - jump/return to actual code
          mg.loadLocal(main_finally_th);
          mg.visitInsn(ATHROW); // finally - re-throw exception
  
          mg.visitLabel(main_finally_begin); // finally - after normal code flow
  
          int main_finally_return = mg.newLocal(TYPE_Object);
          mg.storeLocal(main_finally_return);
  
          // -----------------------------------------------------------------------
          //   try
          //   {
          // -----------------------------------------------------------------------
          Label finally_try_begin = new Label();
          mg.visitLabel(finally_try_begin);
  
          // -----------------------------------------------------------------------
          //     this.container.postInvoke(this, methodId, s);
          //      or
          //     super.mdbMethodPostInvoke();
          // -----------------------------------------------------------------------
          if (wrapperType != MDB_PROXY && wrapperType != MDB_NO_METHOD_INTERFACE_PROXY) {
              // non-MDB postInvoke processing
              loadContainer(mg, className, wrapperType);
              loadWrapperBase(mg, className, wrapperType); // F743-1756
              mg.push(methodId);
              mg.loadLocal(s);
              mg.visitMethodInsn(INVOKEVIRTUAL, "com/ibm/ejs/container/EJSContainer",
                                 EjbPostInvoke,
                                 "(Lcom/ibm/ejs/container/EJSWrapperBase;ILcom/ibm/ejs/container/EJSDeployedSupport;)V");
          } else {
              // call MessageEndpointBase.mdbMethodPostInvoke() for a MDB
              loadWrapperBase(mg, className, wrapperType);
              mg.visitMethodInsn(wrapperType == MDB_NO_METHOD_INTERFACE_PROXY ? INVOKEVIRTUAL : INVOKESPECIAL, MESSAGE_ENDPOINT_BASE_STRING,
                                 "mdbMethodPostInvoke",
                                 "()V");
          }
  
          // -----------------------------------------------------------------------
          //   }                 // end nested try for postInvoke
          // -----------------------------------------------------------------------
          Label finally_try_end = new Label();
          mg.visitLabel(finally_try_end);
  
          Label finally_tcf_exit = new Label();
          mg.visitJumpInsn(GOTO, finally_tcf_exit); // Skip to very end
  
          // -----------------------------------------------------------------------
          // Non-RMI interfaces do not throw RemoteException, but the EJBContainer
          // postInvoke method does specify it is thrown, so for the non-RMI
          // methods, there must be a catch for RemoteException... that will
          // never be executed, but must be present to make Java happy.
          //
          // For MDBs postInvoke is not called on EJBContainer, but on
          // MessageEndpointBase, which doesn't throw RemoteException.  Therefore,
          // we don't need to catch a remoteException for MDBs.
          // -----------------------------------------------------------------------
          Label finally_catch_remote = new Label();
          if (!isRmiRemote && wrapperType != MDB_PROXY && wrapperType != MDB_NO_METHOD_INTERFACE_PROXY)
          {
              // -----------------------------------------------------------------------
              //   catch ( RemoteException rex )
              //   {
              //     s.setUncheckedLocalException(rex);
              //   }
              // -----------------------------------------------------------------------
              mg.visitLabel(finally_catch_remote);
              int finally_remote = mg.newLocal(TYPE_RemoteException);
              mg.storeLocal(finally_remote);
              loadEJSDeployedSupport(mg, className, wrapperType, s);
              mg.loadLocal(finally_remote);
              mg.visitMethodInsn(INVOKEVIRTUAL,
                                 "com/ibm/ejs/container/EJSDeployedSupport",
                                 "setUncheckedLocalException", "(Ljava/lang/Throwable;)V");
              mg.visitJumpInsn(GOTO, finally_tcf_exit); // Skip to very end
          }
  
          mg.visitLabel(finally_tcf_exit);
  
          // -----------------------------------------------------------------------
          // }                     // end of try / catch / finally
          // -----------------------------------------------------------------------
          // Return from finally, either to the end of the try-catch-finally
          // below for normal code flow... or to the beginning of the finally
          // above for the exception path, to rethrow the exception.
          mg.ret(main_finally_return);
  
          mg.visitLabel(main_tcf_exit);
  
          // -----------------------------------------------------------------------
          // return
          // -----------------------------------------------------------------------
          if (returnType != Type.VOID_TYPE)
          {
              mg.loadLocal(returnValue);
          }
          mg.returnValue();
  
          // -----------------------------------------------------------------------
          // All Try-Catch-Finally definitions for above
          // -----------------------------------------------------------------------
          if (main_catch_runtime_exception != null) // d660332
          {
              mg.visitTryCatchBlock(main_try_begin, main_try_end,
                                    main_catch_runtime_exception, "java/lang/RuntimeException");
          }
          for (int i = 0; i < checkedExceptions.length; i++)
          {
              mg.visitTryCatchBlock(main_try_begin, main_try_end,
                                    main_catch_label[i],
                                    convertClassName(checkedExceptions[i].getName()));
          }
          mg.visitTryCatchBlock(main_try_begin, main_try_end,
                                main_catch_throwable, "java/lang/Throwable");
          mg.visitTryCatchBlock(main_try_begin, main_finally_ex, main_finally_ex, null);
  
          if (!isRmiRemote && wrapperType != MDB_PROXY && wrapperType != MDB_NO_METHOD_INTERFACE_PROXY)
          {
              mg.visitTryCatchBlock(finally_try_begin, finally_try_end,
                                    finally_catch_remote, "java/rmi/RemoteException");
          }
  
          // -----------------------------------------------------------------------
          // End Method Code...
          // -----------------------------------------------------------------------
          mg.endMethod(); // GeneratorAdapter accounts for visitMaxs(x,y)
          mg.visitEnd();
      }