/* 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. Explicit definition of an operation. Reflection is used to locate method
   * called.
   *
   * @param opInfo
   */
  public synchronized void defineOperation(ModelMBeanOperationInfo opInfo) {
    _dirty = true;
    Class oClass = _object.getClass();

    try {
      MBeanParameterInfo[] pInfo = opInfo.getSignature();

      Class[] types = new Class[pInfo.length];
      String method = opInfo.getName() + "(";
      for (int i = 0; i < pInfo.length; i++) {
        Class type = TypeUtil.fromName(pInfo[i].getType());
        if (type == null)
          type = Thread.currentThread().getContextClassLoader().loadClass(pInfo[i].getType());
        types[i] = type;
        method += (i > 0 ? "," : "") + pInfo[i].getType();
      }
      method += ")";

      _method.put(method, oClass.getMethod(opInfo.getName(), types));
      _operations.add(opInfo);
    } catch (Exception e) {
      log.warn(LogSupport.EXCEPTION, e);
      throw new IllegalArgumentException(e.toString());
    }
  }
  /**
   * Define an attribute. Explicit definition of an attribute. Reflection is used to locate the
   * actual getter and setter methods.
   *
   * @param attrInfo ModelMBeanAttributeInfo.
   */
  public synchronized void defineAttribute(ModelMBeanAttributeInfo attrInfo) {
    if (_object == null) throw new IllegalStateException("No Object");

    _dirty = true;

    String name = attrInfo.getName();
    String uName = name.substring(0, 1).toUpperCase() + name.substring(1);
    Class oClass = _object.getClass();

    try {
      Class type = TypeUtil.fromName(attrInfo.getType());
      if (type == null)
        type = Thread.currentThread().getContextClassLoader().loadClass(attrInfo.getType());

      Method getter = null;
      Method setter = null;

      if (attrInfo.isReadable())
        getter =
            oClass.getMethod((attrInfo.isIs() ? "is" : "get") + uName, (java.lang.Class[]) null);

      if (attrInfo.isWritable()) setter = oClass.getMethod("set" + uName, new Class[] {type});

      _getter.put(name, getter);
      _setter.put(name, setter);
      _attributes.add(attrInfo);
    } catch (Exception e) {
      log.warn(LogSupport.EXCEPTION, e);
      throw new IllegalArgumentException(e.toString());
    }
  }
  /**
   * Define an attribute on the managed object. The meta data is defined by looking for standard
   * getter and setter methods. Descriptions are obtained with a call to findDescription with the
   * attribute name.
   *
   * @param name The name of the attribute. Normal java bean capitlization is enforced on this name.
   * @param writable If false, do not look for a setter.
   * @param onMBean .
   */
  public synchronized void defineAttribute(String name, boolean writable, boolean onMBean) {
    _dirty = true;

    String uName = name.substring(0, 1).toUpperCase() + name.substring(1);
    name = java.beans.Introspector.decapitalize(name);
    Class oClass = onMBean ? this.getClass() : _object.getClass();

    Class type = null;
    Method getter = null;
    Method setter = null;
    Method[] methods = oClass.getMethods();
    for (int m = 0; m < methods.length; m++) {
      if ((methods[m].getModifiers() & Modifier.PUBLIC) == 0) continue;

      // Look for a getter
      if (methods[m].getName().equals("get" + uName)
          && methods[m].getParameterTypes().length == 0) {
        if (getter != null) throw new IllegalArgumentException("Multiple getters for attr " + name);
        getter = methods[m];
        if (type != null && !type.equals(methods[m].getReturnType()))
          throw new IllegalArgumentException("Type conflict for attr " + name);
        type = methods[m].getReturnType();
      }

      // Look for an is getter
      if (methods[m].getName().equals("is" + uName) && methods[m].getParameterTypes().length == 0) {
        if (getter != null) throw new IllegalArgumentException("Multiple getters for attr " + name);
        getter = methods[m];
        if (type != null && !type.equals(methods[m].getReturnType()))
          throw new IllegalArgumentException("Type conflict for attr " + name);
        type = methods[m].getReturnType();
      }

      // look for a setter
      if (writable
          && methods[m].getName().equals("set" + uName)
          && methods[m].getParameterTypes().length == 1) {
        if (setter != null) throw new IllegalArgumentException("Multiple setters for attr " + name);
        setter = methods[m];
        if (type != null && !type.equals(methods[m].getParameterTypes()[0]))
          throw new IllegalArgumentException("Type conflict for attr " + name);
        type = methods[m].getParameterTypes()[0];
      }
    }

    if (getter == null && setter == null)
      throw new IllegalArgumentException("No getter or setters found for " + name);

    try {
      // Remember the methods
      _getter.put(name, getter);
      _setter.put(name, setter);
      // create and add the info
      _attributes.add(new ModelMBeanAttributeInfo(name, findDescription(name), getter, setter));
    } catch (Exception e) {
      log.warn(LogSupport.EXCEPTION, e);
      throw new IllegalArgumentException(e.toString());
    }
  }
 /**
  * Proxy MBean Constructor.
  *
  * @param proxyObject The actual object on which attributes and operations are to be defined and
  *     called.
  */
 public ModelMBeanImpl(Object proxyObject) {
   try {
     setManagedResource(proxyObject, "objectReference");
   } catch (Exception e) {
     log.warn(LogSupport.EXCEPTION, e);
     throw new IllegalArgumentException(e.toString());
   }
 }
  /**
   * 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());
    }
  }