/* * JBoss, Home of Professional Open Source. * Copyright 2008, Red Hat Middleware LLC, and individual contributors * as indicated by the @author tags. See the copyright.txt file in the * distribution for a full listing of individual contributors. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.jboss.mx.capability; import org.apache.bcel.Constants; import org.apache.bcel.classfile.JavaClass; import org.apache.bcel.generic.*; import org.jboss.mx.metadata.AttributeOperationResolver; import org.jboss.mx.server.ServerConstants; import javax.management.MBeanException; import javax.management.MBeanInfo; import javax.management.MBeanOperationInfo; import javax.management.MBeanParameterInfo; import javax.management.ReflectionException; import java.io.BufferedOutputStream; import java.io.ByteArrayOutputStream; import java.lang.reflect.Constructor; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; /** * Byte code optimized dispatcher for Standard MBeans. This dispatcher generates * an invoke implementation that handles the operation dispatching without * Java reflection.<p> * * The use of this dispatcher may be controlled by setting a * {@link org.jboss.mx.server.ServerConstants#OPTIMIZE_REFLECTED_DISPATCHER OPTIMIZE_REFLECTED_DISPATCHER} * property to either <tt>"true"</tt> or <tt>"false"</tt> string value. * * @see org.jboss.mx.capability.ReflectedMBeanDispatcher * @see org.jboss.mx.capability.DispatchClassLoader * * @author <a href="mailto:juha@jboss.org">Juha Lindfors</a>. * @version $Revision: 81026 $ * */ public class OptimizedMBeanDispatcher implements ServerConstants { // Constants ----------------------------------------------------- final static Class SUPER_CLASS = ReflectedMBeanDispatcher.class; // Static -------------------------------------------------------- public static ReflectedMBeanDispatcher create(MBeanInfo info, Object resource) { try { // construct class template String className = resource.getClass().getName().replace('.', '_') + "_Dispatcher"; String superClass = SUPER_CLASS.getName(); String fileName = className + ".class"; int modifiers = Constants.ACC_PUBLIC; String[] interfaces = new String[0]; ClassGen clazz = new ClassGen(className, superClass, fileName, modifiers, interfaces); ConstantPoolGen cp = clazz.getConstantPool(); clazz.addMethod(createConstructor(cp, className).getMethod()); clazz.addMethod(createInvoke(cp, info, className, resource.getClass().getName()).getMethod()); clazz.update(); JavaClass c = clazz.getJavaClass(); ByteArrayOutputStream baos = new ByteArrayOutputStream(2000); BufferedOutputStream bos = new BufferedOutputStream(baos); c.dump(bos); // FIXME: what about ctx cl? // FIXME: also I dont know if the parent is right here, have to check later ClassLoader ocl = new DispatchClassLoader(resource.getClass().getClassLoader(), className, baos.toByteArray()); Class dispatcherClass = ocl.loadClass(className); Constructor constr = dispatcherClass.getConstructor( new Class[] { MBeanInfo.class, AttributeOperationResolver.class, Object.class } ); Object o = constr.newInstance(new Object[] { info, new AttributeOperationResolver(info), resource }); return (ReflectedMBeanDispatcher)o; } catch (Exception e) { e.printStackTrace(); throw new Error(); } } /** * Returns the signature of a MBean operation using the grammar required by * the class file format, excluding the method name. <p> * * <h4>The Java Virtual Machine Specification: 4.3.3 Method Descriptors</h4> * * A method descriptor represents the parameters that the method takes and the value that it returns: <br><i><pre> * * MethodDescriptor: * ( ParameterDescriptor* ) ReturnDescriptor * </pre></i> * * A parameter descriptor represents a parameter passed to a method: <br><i><pre> * * ParameterDescriptor: * FieldType * </pre></i> * * A return descriptor represents the type of the value returned from a method. It is a series of characters generated by the grammar: <br><i><pre> * * ReturnDescriptor: * FieldType * V * </pre></i> * * The character V indicates that the method returns no value (its return type is void). <p> * * For example, the method descriptor for the method <br> * * <pre> Object mymethod(int i, double d, Thread t) </pre> * * is <br> * * <b><pre> (IDLjava/lang/Thread;)Ljava/lang/Object; </pre></b> * * Note that internal forms of the fully qualified names of Thread and Object are used in the method descriptor. */ public static String getMethodDescriptor(MBeanParameterInfo[] signature, String returnType) { StringBuffer sign = new StringBuffer(256); sign.append("("); for (int i = 0; i < signature.length; ++i) sign.append(getDescriptorForType(signature[i].getName())); sign.append(")" + getDescriptorForType(returnType)); return sign.toString(); } /** * Returns a descriptor for a given Java type. See {@link java.lang.Class#getName() Class.getName()} * for details on the grammar for arrays and primitive types. Note that the internal form of the fully * qualified name for class Object is used, so for example, the returned descriptor for * <tt>java.lang.Object</tt> is * * <b><pre>Ljava/lang/Object;</pre></b> * * See JVM spec 4.2 and 4.3 for detailed description of the internal class name format and grammar notation. * * @param name fully qualified name of the Java type * @return descriptor string using the JVM grammar */ public static String getDescriptorForType(String name) { if (name.equals(Byte.TYPE.getName())) return "B"; else if (name.equals(Character.TYPE.getName())) return "C"; else if (name.equals(Double.TYPE.getName())) return "D"; else if (name.equals(Float.TYPE.getName())) return "F"; else if (name.equals(Integer.TYPE.getName())) return "I"; else if (name.equals(Long.TYPE.getName())) return "J"; else if (name.equals(Short.TYPE.getName())) return "S"; else if (name.equals(Boolean.TYPE.getName())) return "Z"; else if (name.equals(Void.TYPE.getName())) return "V"; else if (name.startsWith("[")) return name.replace('.', '/'); else return "L" + name.replace('.', '/') + ";"; } /** * Checks if a given name matches the <tt>TYPE</tt> name of a primitive wrapper class. * * @see java.lang.Integer#TYPE * * @param name TYPE.getName() * @return true if is a primitive type name; false otherwise */ public static boolean isPrimitive(String name) { if (name.equals(Byte.TYPE.getName()) || name.equals(Character.TYPE.getName()) || name.equals(Double.TYPE.getName()) || name.equals(Float.TYPE.getName()) || name.equals(Integer.TYPE.getName()) || name.equals(Long.TYPE.getName()) || name.equals(Short.TYPE.getName()) || name.equals(Boolean.TYPE.getName())) return true; return false; } // Protected ----------------------------------------------------- /** * creates constructor <tt><init>(MBeanInfo info, AttributeOperationResolver resolver, Object resource)</tt> * that calls <tt>super(info, resolver, resource)</tt> in its implementation * * @param cp constant pool * @param className name of the class being generated */ protected static MethodGen createConstructor(ConstantPoolGen cp, String className) { InstructionList constrInstructions = new InstructionList(); int constrRefIndex = cp.addMethodref( SUPER_CLASS.getName(), "<init>", "(" + getDescriptorForType(MBeanInfo.class.getName()) + getDescriptorForType(AttributeOperationResolver.class.getName()) + getDescriptorForType(Object.class.getName()) + ")V" ); constrInstructions.append(new ALOAD(0)); // Stack: => ..., this constrInstructions.append(new ALOAD(1)); // Stack: => ..., this, arg1 [MBeanInfo] constrInstructions.append(new ALOAD(2)); // Stack: => ..., this, arg1 [MBeanInfo], arg2 [AttributeOperationResolver] constrInstructions.append(new ALOAD(3)); // Stack: => ..., this, arg1 [MBeanInfo], arg2 [AttributeOperationResolver], arg3 [Object] constrInstructions.append(new INVOKESPECIAL(constrRefIndex)); // Stack: => ... constrInstructions.append(new RETURN()); // Stack: => <empty> MethodGen constrMethod = new MethodGen( Constants.ACC_PUBLIC, Type.VOID, new Type[] { new ObjectType(MBeanInfo.class.getName()), new ObjectType(AttributeOperationResolver.class.getName()), new ObjectType(Object.class.getName()) }, new String[] { "info", "resolver", "resource" }, "<init>", className, constrInstructions, cp ); constrMethod.setMaxStack(4); return constrMethod; } /** * Creates the implementation of the <tt>invoke(String actionName, Object[] args, String[] signature)</tt> * method. This implementation currently handles all non overloaded MBean operation invocations. * Overloaded operations still fall back to the default reflected invocations. <p> * * The Java equivalent of the implementation looks roughly as follows:<br><pre> * * public void invoke(String actionName, Object[] args, String[] signature) * { * if (actionName != null) * { * try * { * if (actionName.equals(<operName1>)) * return ((<resource type>)super.getResourceObject()).<operName1>((<arg1 type>)arg1, (<arg2 type>)arg2, ...); * else if (actionName.equals(<operName2>)) * return ((<resource type>)super.getResourceObject()).<operName2>((<arg1 type>)arg1, (<arg2 type>)arg2, ...); * * ... * * else * super.invoke(actionName, args, signature); * } * catch (Throwable t) * { * super.invoke(actionName, args, signature); * } * } * } * </pre> * * @param cp constant pool of the class being generated * @param info metadata of the MBean * @param className name of the class being generated * @param resourceClassName name of the resource class being invoked */ protected static MethodGen createInvoke(ConstantPoolGen cp, MBeanInfo info, String className, String resourceClassName) { InstructionList invokeInstructions = new InstructionList(); MethodEntry[] operations = getOperations(info); // load operation name strings and methods to constant pool for (int i = 0; i < operations.length; ++i) { operations[i].nameIndexInCP = cp.addString(operations[i].getName()); operations[i].methodIndexInCP = cp.addMethodref( resourceClassName, operations[i].getName(), operations[i].methodDescriptor ); } int invokeIndex = cp.addMethodref( SUPER_CLASS.getName(), "invoke", "(" + getDescriptorForType(String.class.getName()) + getDescriptorForType(Object[].class.getName()) + getDescriptorForType(String[].class.getName()) + ")" + getDescriptorForType(Object.class.getName()) ); int getResourceObjectIndex = cp.addMethodref( SUPER_CLASS.getName(), "getResourceObject", "()Ljava/lang/Object;" ); int strEqualsIndex = cp.addMethodref( String.class.getName(), "equals", "(Ljava/lang/Object;)Z" ); InstructionHandle beginTryBlock = null; InstructionHandle endTryBlock = null; IFNULL ifOperationEqualsNull = new IFNULL(null); IFEQ operationElseIfBranch = null; if (operations.length > 0) { // // if (actionName != null) // invokeInstructions.append(new ALOAD(1)); // Stack: => ..., arg1 [String] beginTryBlock = invokeInstructions.append(ifOperationEqualsNull); // Stack: => ... for (int i = 0; i < operations.length; ++i) { // // if (actionName.equals(operations[i].getName()); // InstructionHandle jumpToNextElse = invokeInstructions.append(new ALOAD(1)); // Stack: => ..., arg1 [String] invokeInstructions.append(new LDC(operations[i].nameIndexInCP)); // Stack: => ..., opName [String] invokeInstructions.append(new INVOKEVIRTUAL(strEqualsIndex)); // Stack: => ..., 0 | 1 [boolean] // set the jump target for previous else if branch if (operationElseIfBranch != null) operationElseIfBranch.setTarget(jumpToNextElse); operationElseIfBranch = new IFEQ(null); invokeInstructions.append(operationElseIfBranch); // Stack: => ... invokeInstructions.append(new ALOAD(0)); // Stack: => ..., this invokeInstructions.append(new INVOKEVIRTUAL(getResourceObjectIndex)); // Stack: => ..., resource [Object] int x = cp.addClass(resourceClassName); invokeInstructions.append(new CHECKCAST(x)); // Stack: => ..., resource [<resource object type>] // if invocation has args, we need to push them into stack if (operations[i].getSignature().length > 0) { for (int arrayIndex = 0; arrayIndex < operations[i].getSignature().length; ++arrayIndex) { invokeInstructions.append(new ALOAD(2)); // Stack: => ..., resource [<type>], arg2 [Object[]] invokeInstructions.append(new PUSH(cp, arrayIndex)); // Stack: => ..., resource [<type>], arg2 [Object[]], array index [int] invokeInstructions.append(new AALOAD()); // Stack: => ..., resource [<type>], array[index] [Object] // Args come in as objects. If signature has a primitive type // we need to convert the arg before we can invoke the operation String type = operations[i].getSignature() [arrayIndex].getName(); if (isPrimitive(type)) invokeInstructions.append(convertObjectToPrimitive(cp, type)); // Stack: => ..., resource[<type>], value [<primitive>] else { x = cp.addClass(type); invokeInstructions.append(new CHECKCAST(x)); // Stack: => ..., resource[<type>], value [<reference>] } } } // // resource.<operation>(<arg 1, ... arg n>) // x = operations[i].methodIndexInCP; invokeInstructions.append(new INVOKEVIRTUAL(x)); // Stack: => ..., returnvalue // Wrap primitive return values into their corresponding wrapper objects String type = operations[i].getReturnType(); if (isPrimitive(type)) { invokeInstructions.append(convertPrimitiveToObject(cp, type)); // Stack: => ..., objectref [wrapper] invokeInstructions.append(new ARETURN()); // Stack: => <empty> } else if (type.equals(Void.TYPE.getName())) { invokeInstructions.append(new ACONST_NULL()); // Stack: => ..., null invokeInstructions.append(new ARETURN()); // Stack: => <empty> } else { invokeInstructions.append(new ARETURN()); // Stack: => <empty> } } } // // super.invoke(actionName, args, signature) if no match was found // InstructionHandle jumpToSuperInvoke = invokeInstructions.append(new ALOAD(0)); // Stack: => ..., this invokeInstructions.append(new ALOAD(1)); // Stack: => ..., this, arg1 [String] invokeInstructions.append(new ALOAD(2)); // Stack: => ..., this, arg1 [String], arg2 [Object[]] invokeInstructions.append(new ALOAD(3)); // Stack: => ..., this, arg1 [String], arg2 [Object[]], arg3 [String[]] invokeInstructions.append(new INVOKESPECIAL(invokeIndex)); // Stack: => ..., reference [Object] invokeInstructions.append(new ARETURN()); // Stack: => <empty> // set the jump targets ifOperationEqualsNull.setTarget(jumpToSuperInvoke); if (operations.length > 0) { // set the last else branch to call super.invoke if (operationElseIfBranch != null) operationElseIfBranch.setTarget(jumpToSuperInvoke); // set the try catch block limits beginTryBlock = beginTryBlock.getNext(); endTryBlock = jumpToSuperInvoke.getPrev(); } // exception handler (it's a cheap shot -- if there is any exception, re-invoke // on super class and let it handle all exceptions) InstructionHandle exceptionHandlerCode = invokeInstructions.append(new ALOAD(0)); invokeInstructions.append(new ALOAD(1)); invokeInstructions.append(new ALOAD(2)); invokeInstructions.append(new ALOAD(3)); invokeInstructions.append(new INVOKESPECIAL(invokeIndex)); invokeInstructions.append(new ARETURN()); MethodGen invokeMethod = new MethodGen( Constants.ACC_PUBLIC, Type.OBJECT, new Type[] { Type.STRING, new ArrayType(Object.class.getName(), 1), new ArrayType(String.class.getName(), 1) }, new String[] { "operationName", "args", "signature" }, "invoke", className, invokeInstructions, cp ); invokeMethod.setMaxLocals(7); invokeMethod.setMaxStack(calculateMaxStackSize(info)); invokeMethod.addException(ReflectionException.class.getName()); invokeMethod.addException(MBeanException.class.getName()); if (operations.length > 0) { invokeMethod.addExceptionHandler(beginTryBlock, endTryBlock, exceptionHandlerCode, new ObjectType("java.lang.Throwable")); } return invokeMethod; } private static int calculateMaxStackSize(MBeanInfo info) { MBeanOperationInfo[] operations = info.getOperations(); int maxSize = 7; for (int i = 0; i < operations.length; ++i) { if (operations[i].getSignature().length > maxSize + 2) maxSize = operations[i].getSignature().length + 2; } return maxSize; } /** * Converts a reference of a primitve wrapper object into a primite value type. * This method assumes that the wrapper object reference is already loaded at the * top of the operand stack. The stack is modified so that the object reference * to a primitive wrapper is replaced by the corresponding value in the stack. * * @param cp constant pool * @param type class name of the primitive wrapper object to convert * @return an instruction list that replaces an object reference of a primitive * wrapper object to its corresponding value in the operand stack */ protected static InstructionList convertObjectToPrimitive(ConstantPoolGen cp, String type) { InstructionList il = new InstructionList(); int intValueIndex = cp.addMethodref(Integer.class.getName(), "intValue", "()I"); int byteValueIndex = cp.addMethodref(Byte.class.getName(), "byteValue", "()B"); int charValueIndex = cp.addMethodref(Character.class.getName(), "charValue", "()C"); int doubleValueIndex = cp.addMethodref(Double.class.getName(), "doubleValue", "()D"); int floatValueIndex = cp.addMethodref(Float.class.getName(), "floatValue", "()F"); int longValueIndex = cp.addMethodref(Long.class.getName(), "longValue", "()J"); int shortValueIndex = cp.addMethodref(Short.class.getName(), "shortValue", "()S"); int booleanValueIndex = cp.addMethodref(Boolean.class.getName(), "booleanValue", "()Z"); // // Assumes the wrapper object reference is on top of the stack // if (type.equals(Integer.TYPE.getName())) { int x = cp.addClass("java.lang.Integer"); il.append(new CHECKCAST(x)); // Stack: => ..., type [Integer] il.append(new INVOKEVIRTUAL(intValueIndex)); // Stack: => ..., value [int] } else if (type.equals(Byte.TYPE.getName())) { int x = cp.addClass("java.lang.Byte"); il.append(new CHECKCAST(x)); // Stack: => ..., type [Boolean] il.append(new INVOKEVIRTUAL(byteValueIndex)); // Stack: => ..., 0 | 1 [boolean] } else if (type.equals(Character.TYPE.getName())) { int x = cp.addClass("java.lang.Character"); il.append(new CHECKCAST(x)); // Stack: => ..., type [Character] il.append(new INVOKEVIRTUAL(charValueIndex)); // Stack: => ..., value [char] } else if (type.equals(Double.TYPE.getName())) { int x = cp.addClass("java.lang.Double"); il.append(new CHECKCAST(x)); // Stack: => ..., type [Double] il.append(new INVOKEVIRTUAL(doubleValueIndex)); // Stack: => ..., value [double] } else if (type.equals(Float.TYPE.getName())) { int x = cp.addClass("java.lang.Float"); il.append(new CHECKCAST(x)); // Stack: => ..., type [Float] il.append(new INVOKEVIRTUAL(floatValueIndex)); // Stack: => ..., value [float] } else if (type.equals(Long.TYPE.getName())) { int x = cp.addClass("java.lang.Long"); il.append(new CHECKCAST(x)); // Stack: => ..., type [Long] il.append(new INVOKEVIRTUAL(longValueIndex)); // Stack: => ..., value [long] } else if (type.equals(Short.TYPE.getName())) { int x = cp.addClass("java.lang.Short"); il.append(new CHECKCAST(x)); // Stack: => ..., type [Short] il.append(new INVOKEVIRTUAL(shortValueIndex)); // Stack: => ..., value [short] } else if (type.equals(Boolean.TYPE.getName())) { int x = cp.addClass("java.lang.Boolean"); il.append(new CHECKCAST(x)); // Stack: => ..., type [Boolean] il.append(new INVOKEVIRTUAL(booleanValueIndex)); // Stack: => ..., value [boolean] } return il; } /** * Converts a primitive into its corresponding object wrapper reference. * This method assumes the primitve is already pushed to the top of the operand * stack. The stack is modified so that the primitive value is replaced * by a reference to its corresponding wrapper object that has been * initialized to contain the same value. * * @param cp constant pool * @param type type string of the primitive, for example {@link java.lang.Integer#TYPE Integer.TYPE.getName()} * @return an instruction list that replaces the primitive type at the top of * the operand stack with its corresponding, initialized, wrapper object */ protected static InstructionList convertPrimitiveToObject(ConstantPoolGen cp, String type) { InstructionList il = new InstructionList(); if (type.equals(Boolean.TYPE.getName())) { int x = cp.addClass("java.lang.Boolean"); int constrIndex = cp.addMethodref("java.lang.Boolean", "<init>", "(B)V"); il.append(new ISTORE(4)); il.append(new NEW(x)); il.append(new ASTORE(5)); il.append(new ALOAD(5)); il.append(new ILOAD(4)); il.append(new INVOKESPECIAL(constrIndex)); il.append(new ALOAD(5)); } else if (type.equals(Short.TYPE.getName())) { int x = cp.addClass("java.lang.Short"); int constrIndex = cp.addMethodref("java.lang.Short", "<init>", "(S)V"); il.append(new ISTORE(4)); il.append(new NEW(x)); il.append(new ASTORE(5)); il.append(new ALOAD(5)); il.append(new ILOAD(4)); il.append(new INVOKESPECIAL(constrIndex)); il.append(new ALOAD(5)); } else if (type.equals(Long.TYPE.getName())) { int x = cp.addClass("java.lang.Long"); int constrIndex = cp.addMethodref("java.lang.Long", "<init>", "(J)V"); il.append(new LSTORE(4)); il.append(new NEW(x)); il.append(new ASTORE(6)); il.append(new ALOAD(6)); il.append(new LLOAD(4)); il.append(new INVOKESPECIAL(constrIndex)); il.append(new ALOAD(6)); } else if (type.equals(Integer.TYPE.getName())) { int x = cp.addClass("java.lang.Integer"); int constrIndex = cp.addMethodref("java.lang.Integer", "<init>", "(I)V"); il.append(new ISTORE(4)); il.append(new NEW(x)); il.append(new ASTORE(5)); il.append(new ALOAD(5)); il.append(new ILOAD(4)); il.append(new INVOKESPECIAL(constrIndex)); il.append(new ALOAD(5)); } else if (type.equals(Float.TYPE.getName())) { int x = cp.addClass("java.lang.Float"); int constrIndex = cp.addMethodref("java.lang.Float", "<init>", "(F)V"); il.append(new FSTORE(4)); il.append(new NEW(x)); il.append(new ASTORE(5)); il.append(new ALOAD(5)); il.append(new FLOAD(4)); il.append(new INVOKESPECIAL(constrIndex)); il.append(new ALOAD(5)); } else if (type.equals(Double.TYPE.getName())) { int x = cp.addClass("java.lang.Double"); int constrIndex = cp.addMethodref("java.lang.Double", "<init>", "(D)V"); il.append(new DSTORE(4)); il.append(new NEW(x)); il.append(new ASTORE(6)); il.append(new ALOAD(6)); il.append(new DLOAD(4)); il.append(new INVOKESPECIAL(constrIndex)); il.append(new ALOAD(6)); } else if (type.equals(Character.TYPE.getName())) { int x = cp.addClass("java.lang.Character"); int constrIndex = cp.addMethodref("java.lang.Character", "<init>", "(C)V"); il.append(new ISTORE(4)); il.append(new NEW(x)); il.append(new ASTORE(5)); il.append(new ALOAD(5)); il.append(new ILOAD(4)); il.append(new INVOKESPECIAL(constrIndex)); il.append(new ALOAD(5)); } else if (type.equals(Byte.TYPE.getName())) { int x = cp.addClass("java.lang.Byte"); int constrIndex = cp.addMethodref("java.lang.Byte", "<init>", "(B)V"); il.append(new ISTORE(4)); il.append(new NEW(x)); il.append(new ASTORE(5)); il.append(new ALOAD(5)); il.append(new ILOAD(4)); il.append(new INVOKESPECIAL(constrIndex)); il.append(new ALOAD(5)); } return il; } /** * Returns a subset of MBean's operations. Overloaded operations are not supported yet, * so they're left out of the list and their invocations are delegated to the reflection * based super class. <p> * * Overloaded operations that differ in their arg list length may be able to gain in * performance if implemented directly with byte code. Overloaded operations with * equal arg list length may not show much difference compared to ternary search tree * based resolver. */ protected static MethodEntry[] getOperations(MBeanInfo info) { HashMap operationMap = new HashMap(); ArrayList overloadList = new ArrayList(); MBeanOperationInfo[] operations = info.getOperations(); for (int i = 0; i < operations.length; ++i) { String methodName = operations[i].getName(); if (operationMap.containsKey(methodName)) overloadList.add(methodName); else operationMap.put(methodName, new MethodEntry(operations[i])); } // method overloading not supported yet Iterator it = overloadList.iterator(); while (it.hasNext()) operationMap.remove(it.next()); return (MethodEntry[])operationMap.values().toArray(new MethodEntry[0]); } // Inner classes ------------------------------------------------- private static class MethodEntry extends MBeanOperationInfo { private static final long serialVersionUID = 1792631947840418314L; String methodDescriptor = null; int nameIndexInCP = -1; int methodIndexInCP = -1; public MethodEntry(MBeanOperationInfo info) { super(info.getName(), info.getDescription(), info.getSignature(), info.getReturnType(), info.getImpact()); this.methodDescriptor = getMethodDescriptor(info.getSignature(), info.getReturnType()); } } }