/**
   * Create MBean for Object. Attempts to create an MBean for the object by searching the package
   * and class name space. For example an object of the type
   *
   * <PRE>
   *   class com.acme.MyClass extends com.acme.util.BaseClass
   * </PRE>
   *
   * Then this method would look for the following classes:
   *
   * <UL>
   *   <LI>com.acme.MyClassMBean
   *   <LI>com.acme.jmx.MyClassMBean
   *   <LI>com.acme.util.BaseClassMBean
   *   <LI>com.acme.util.jmx.BaseClassMBean
   * </UL>
   *
   * @param o The object
   * @return A new instance of an MBean for the object or null.
   */
  public static ModelMBean mbeanFor(Object o) {
    try {
      Class oClass = o.getClass();
      ClassLoader loader = oClass.getClassLoader();

      ModelMBean mbean = null;
      boolean jmx = false;
      Class[] interfaces = null;
      int i = 0;

      while (mbean == null && oClass != null) {
        Class focus = interfaces == null ? oClass : interfaces[i];
        String pName = focus.getPackage().getName();
        String cName = focus.getName().substring(pName.length() + 1);
        String mName = pName + (jmx ? ".jmx." : ".") + cName + "MBean";

        try {
          Class mClass = loader.loadClass(mName);
          if (log.isTraceEnabled()) log.trace("mbeanFor " + o + " mClass=" + mClass);
          mbean = (ModelMBean) mClass.newInstance();
          mbean.setManagedResource(o, "objectReference");
          if (log.isDebugEnabled()) log.debug("mbeanFor " + o + " is " + mbean);
          return mbean;
        } catch (ClassNotFoundException e) {
          if (e.toString().endsWith("MBean")) {
            if (log.isTraceEnabled()) log.trace(e.toString());
          } else log.warn(LogSupport.EXCEPTION, e);
        } catch (Error e) {
          log.warn(LogSupport.EXCEPTION, e);
          mbean = null;
        } catch (Exception e) {
          log.warn(LogSupport.EXCEPTION, e);
          mbean = null;
        }

        if (jmx) {
          if (interfaces != null) {
            i++;
            if (i >= interfaces.length) {
              interfaces = null;
              oClass = oClass.getSuperclass();
            }
          } else {
            interfaces = oClass.getInterfaces();
            i = 0;
            if (interfaces == null || interfaces.length == 0) {
              interfaces = null;
              oClass = oClass.getSuperclass();
            }
          }
        }
        jmx = !jmx;
      }
    } catch (Exception e) {
      LogSupport.ignore(log, e);
    }
    return null;
  }
  /* Find MBean descriptions.
   * MBean descriptions are searched for in ResourceBundles. Bundles
   * are looked for in a mbean.property files within each package of
   * the MBean class inheritance hierachy.
   * Once a bundle is found, the key is added to object names in the
   * following order: fully qualied managed resource class name, tail
   * managed resource class name, tail mbean class name. The string
   * "MBean" is stripped from the tail of any name.
   * <P>For example, if the class a.b.C is managed by a MBean
   * p.q.RMBean which is derived from p.SMBean, then the seach order
   * for a key x is as follows:<PRE>
   *   bundle: p.q.mbean    name: a.b.C.x
   *   bundle: p.q.mbean    name: C.x
   *   bundle: p.q.mbean    name: R.x
   *   bundle: p.mbean      name: a.b.C.x
   *   bundle: p.mbean      name: C.x
   *   bundle: p.mbean      name: S.x
   * </PRE>
   * <P>The convention used for keys passed to this method are:<PRE>
   *   null or empty         - Object description
   *   xxx                   - Attribute xxx description
   *   xxx()                 - Simple operation xxx description
   *   xxx(type,..)          - Operation xxx with signature desciption
   *   xxx(type,..)[n]       - Param n of operation xxx description
   * </PRE>
   * @param key
   * @return Description string.
   */
  private String findDescription(String key) {
    Class lookIn = this.getClass();

    // Array of possible objectNames
    String[] objectNames = new String[3];
    objectNames[0] = _object.getClass().getName();
    if (objectNames[0].indexOf(".") >= 0)
      objectNames[1] = objectNames[0].substring(objectNames[0].lastIndexOf(".") + 1);

    while (lookIn != null) {
      String pkg = lookIn.getName();
      int lastDot = pkg.lastIndexOf(".");
      if (lastDot > 0) {
        objectNames[2] = pkg.substring(lastDot + 1);
        pkg = pkg.substring(0, lastDot);
      } else {
        objectNames[2] = pkg;
        pkg = null;
      }

      String resource = (pkg == null ? "mbean" : (pkg.replace('.', '/') + "/mbean"));
      if (log.isTraceEnabled()) log.trace("Look for: " + resource);

      try {
        ResourceBundle bundle =
            ResourceBundle.getBundle(
                resource, Locale.getDefault(), _object.getClass().getClassLoader());

        if (log.isTraceEnabled()) log.trace("Bundle " + resource);

        for (int i = 0; i < objectNames.length; i++) {
          String name = objectNames[i];

          if (name == null) continue;
          if (name.endsWith("MBean")) name = name.substring(0, name.length() - 5);
          if (key != null && key.length() > 0) name += "." + key;

          try {
            String description = bundle.getString(name);
            if (description != null && description.length() > 0) return description;
          } catch (Exception e) {
            if (log.isTraceEnabled()) log.trace(e.toString());
          }
        }
      } catch (Exception e) {
        if (log.isTraceEnabled()) log.trace(e.toString());
      }

      lookIn = lookIn.getSuperclass();
    }

    if (key == null || key.length() == 0) return objectNames[0];

    return key;
  }
  /**
   * Define an operation on the managed object. Defines an operation with parameters. Refection is
   * used to determine find the method and it's return type. The description of the method is found
   * with a call to findDescription on "name(signature)". The name and description of each parameter
   * is found with a call to findDescription with "name(partialSignature", the returned description
   * is for the last parameter of the partial signature and is assumed to start with the parameter
   * name, followed by a colon.
   *
   * @param name The name of the method call.
   * @param signature The types of the operation parameters.
   * @param impact Impact as defined in MBeanOperationInfo
   * @param onMBean true if the operation is defined on the mbean
   */
  public synchronized void defineOperation(
      String name, String[] signature, int impact, boolean onMBean) {
    _dirty = true;
    Class oClass = onMBean ? this.getClass() : _object.getClass();
    if (signature == null) signature = new String[0];

    try {
      Class[] types = new Class[signature.length];
      MBeanParameterInfo[] pInfo = new MBeanParameterInfo[signature.length];

      // Check types and build methodKey
      String methodKey = name + "(";
      for (int i = 0; i < signature.length; i++) {
        Class type = TypeUtil.fromName(signature[i]);
        if (type == null)
          type = Thread.currentThread().getContextClassLoader().loadClass(signature[i]);
        types[i] = type;
        signature[i] = type.isPrimitive() ? TypeUtil.toName(type) : signature[i];
        methodKey += (i > 0 ? "," : "") + signature[i];
      }
      methodKey += ")";

      // Build param infos
      for (int i = 0; i < signature.length; i++) {
        String description = findDescription(methodKey + "[" + i + "]");
        int colon = description.indexOf(":");
        if (colon < 0) {
          description = "param" + i + ":" + description;
          colon = description.indexOf(":");
        }
        pInfo[i] =
            new MBeanParameterInfo(
                description.substring(0, colon).trim(),
                signature[i],
                description.substring(colon + 1).trim());
      }

      // build the operation info
      Method method = oClass.getMethod(name, types);
      Class returnClass = method.getReturnType();
      _method.put(methodKey, method);
      _operations.add(
          new ModelMBeanOperationInfo(
              name,
              findDescription(methodKey),
              pInfo,
              returnClass.isPrimitive() ? TypeUtil.toName(returnClass) : (returnClass.getName()),
              impact));
    } catch (Exception e) {
      log.warn("operation " + name, e);
      throw new IllegalArgumentException(e.toString());
    }
  }