/*
 * JBoss, Home of Professional Open Source
 * Copyright 2005, JBoss Inc., and individual contributors as indicated
 * by the @authors tag. See the copyright.txt 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.metadata;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.management.Descriptor;
import javax.management.MBeanInfo;
import javax.management.MBeanOperationInfo;
import javax.management.MBeanParameterInfo;
import javax.management.NotCompliantMBeanException;
import javax.management.modelmbean.DescriptorSupport;
import javax.management.modelmbean.ModelMBeanAttributeInfo;
import javax.management.modelmbean.ModelMBeanConstructorInfo;
import javax.management.modelmbean.ModelMBeanInfo;
import javax.management.modelmbean.ModelMBeanInfoSupport;
import javax.management.modelmbean.ModelMBeanNotificationInfo;
import javax.management.modelmbean.ModelMBeanOperationInfo;

import org.dom4j.Attribute;
import org.dom4j.Element;

import org.jboss.mx.modelmbean.XMBeanConstants;
import org.jboss.mx.util.JBossNotCompliantMBeanException;


/**
 * Parser for the XMBean schema defined in the
 * <a href="http://www.amazon.com/exec/obidos/ASIN/0672322889/002-8100474-1804019">
 * JMX: Managing J2EE with Java Management Extensions</a> (xmbean.dtd)
 *
 * @author  <a href="mailto:juha@jboss.org">Juha Lindfors</a>.
 * @version $Revision: 1.3.4.6 $
 */
public class XMBeanMetaData
   extends AbstractBuilder
   implements XMBeanConstants
{

   // Constants -----------------------------------------------------

   //
   // Below the XML element names used in the document instances.
   //
   private final static String GET_METHOD = "getMethod";
   private final static String SET_METHOD = "setMethod";
   private final static String PERSIST_POLICY = "persistPolicy";
   private final static String PERSIST_PERIOD = "persistPeriod";
   private final static String PERSIST_NAME = "persistName";
   private final static String PERSIST_LOCATION = "persistLocation";
   private final static String CURRENCY_TIME_LIMIT = "currencyTimeLimit";
   private final static String ON_UPDATE = "OnUpdate";
   private final static String NO_MORE_OFTEN_THAN = "NoMoreOftenThan";
   private final static String NEVER = "Never";
   private final static String ON_TIMER = "OnTimer";

   // Attributes ----------------------------------------------------


   /**
    * Name of the class this Model MBean represents.
    */
   private final String resourceClassName;

   /**
    * Name of the Model MBean implementation class.
    */
   private final String mmbClassName;

   private final Element element;

   // Constructors --------------------------------------------------

   public XMBeanMetaData(String mmbClassName, String resourceClassName, Element element, Map properties)
   {
      super();
      this.mmbClassName = mmbClassName;
      this.resourceClassName = resourceClassName;
      this.element = element;
      setProperties(properties);
   }



   // MetaDataBuilder interface implementation ----------------------

   public MBeanInfo build() throws NotCompliantMBeanException
   {
      try
      {
         List constructors = element.elements("constructor");
         List operations = element.elements("operation");
         List attributes = element.elements("attribute");
         List notifications = element.elements("notifications");
         String description = element.elementText("description");

         Attribute persistPolicy = element.attribute(PERSIST_POLICY);
         Attribute persistPeriod = element.attribute(PERSIST_PERIOD);
         Attribute persistLocation = element.attribute(PERSIST_LOCATION);
         Attribute persistName = element.attribute(PERSIST_NAME);
         Attribute currTimeLimit = element.attribute(CURRENCY_TIME_LIMIT);

         Descriptor descr = new DescriptorSupport();
         descr.setField("name", mmbClassName);
         descr.setField("descriptorType", "mbean");

         if (persistPolicy != null)
            descr.setField(PERSIST_POLICY, persistPolicy.getValue());
         if (persistPeriod != null)
            descr.setField(PERSIST_PERIOD, persistPeriod.getValue());
         if (persistLocation != null)
            descr.setField(PERSIST_LOCATION, persistLocation.getValue());
         if (persistName != null)
            descr.setField(PERSIST_NAME, persistName.getValue());
         if (currTimeLimit != null)
            descr.setField(CURRENCY_TIME_LIMIT, currTimeLimit.getValue());

         ModelMBeanInfo info = buildMBeanMetaData(
            description, constructors, operations,
            attributes, notifications, descr
         );

         return (MBeanInfo) info;
      }
      catch (Throwable t)
      {
         throw new JBossNotCompliantMBeanException("Error parsing the XML file: ", t);
      }
   }


   // Protected -----------------------------------------------------

   /**
    * Parses the elements under the root and turns them into a Model MBean info
    * instance. Subclasses may override if they need to handle additional
    * elements under the root <tt>&lt;mbean&gt;</tt>.
    *
    * @param   description    the MBean description extracted from the document
    *                         instance.
    * @param   constructors   list of <tt>&lt;constructor&gt;</tt> elements extracted
    *                         from the document instance.
    * @param   operations     list of <tt>&lt;operation&gt;</tt> elements extracted
    *                         from the document instance.
    * @param   attributes     list of <tt>&lt;attributes&gt;</tt> elements extracted
    *                         from the document instance.
    * @param   notifications  list of <tt>&lt;notification&gt;</tt> elements
    *                         extracted from the document instance.
    * @param   descr          MBean descriptor
    *
    * @return  initialized Model MBean info
    */
   protected ModelMBeanInfo buildMBeanMetaData(String description, List constructors,
             List operations, List attributes, List notifications, Descriptor descr)
   {

      ModelMBeanOperationInfo[] operInfo     = buildOperationInfo(operations);
      ModelMBeanAttributeInfo[] attrInfo     = buildAttributeInfo(attributes);
      ModelMBeanConstructorInfo[] constrInfo = buildConstructorInfo(constructors);
      ModelMBeanNotificationInfo[] notifInfo = buildNotificationInfo(notifications);

      ModelMBeanInfo info = new ModelMBeanInfoSupport(
         mmbClassName, description, attrInfo, constrInfo, operInfo, notifInfo, descr
      );

      return info;
   }

   /**
    * Parses the contents of an <tt>&lt;constructor&gt;</tt> element. Subclasses
    * may override if they need to handle additional nested elements or arguments.
    *
    * @param   constructors   a list of <tt>&lt;constructor&gt;</tt> elements
    *                         extracted from the document instance
    *
    * @return  initialized constructor info
    */
   protected ModelMBeanConstructorInfo[] buildConstructorInfo(List constructors)
   {

      Iterator it = constructors.iterator();
      List infos = new ArrayList();

      while (it.hasNext())
      {
         Element constr = (Element) it.next();
         String name = constr.elementTextTrim("name");
         String descr = constr.elementTextTrim("description");
         List params = constr.elements("parameter");

         MBeanParameterInfo[] paramInfo = buildParameterInfo(params);
         ModelMBeanConstructorInfo info = new ModelMBeanConstructorInfo(name, descr, paramInfo);

         infos.add(info);
      }

      return (ModelMBeanConstructorInfo[]) infos.toArray(new ModelMBeanConstructorInfo[0]);
   }

   /**
    * Parses the contents of an <tt>&lt;operation&gt;</tt> element. Subclasses
    * may override if they need to handle additional nested elements or arguments.
    *
    * @param   operations  a list of <tt>&lt;operation&gt;</tt> elements
    *                      extracted from the document instance
    *
    * @return  initialized operation info
    */
   protected ModelMBeanOperationInfo[] buildOperationInfo(List operations)
   {

      Iterator it = operations.iterator();
      List infos = new ArrayList();

      while (it.hasNext())
      {
         Element oper = (Element) it.next();
         String name = oper.elementTextTrim("name");
         String descr = oper.elementTextTrim("description");
         String type = oper.elementTextTrim("return-type");
         String impact = oper.elementTextTrim("impact");
         List params = oper.elements("parameter");

         MBeanParameterInfo[] paramInfo = buildParameterInfo(params);

         // defaults to ACTION_INFO
         int operImpact = MBeanOperationInfo.ACTION_INFO;

         if (impact != null)
         {
            if (impact.equals("INFO"))
               operImpact = MBeanOperationInfo.INFO;
            else if (impact.equals("ACTION"))
               operImpact = MBeanOperationInfo.ACTION;
            else if (impact.equals("ACTION_INFO"))
               operImpact = MBeanOperationInfo.ACTION_INFO;
         }

         // default return-type is void
         if (type == null)
            type = "void";

         ModelMBeanOperationInfo info = new ModelMBeanOperationInfo(
            name, descr, paramInfo, type, operImpact
         );

         infos.add(info);
      }

      return (ModelMBeanOperationInfo[]) infos.toArray(new ModelMBeanOperationInfo[0]);
   }


   /**
    * Parses the contents of an <tt>&lt;notification&gt;</tt> element. Subclasses
    * may override if they need to handle additional nested elements or arguments.
    *
    * @param   notifications  a list of <tt>&lt;notification&gt;</tt> elements
    *                         extraced from the document instance
    *
    * @return  initialized notification info
    */
   protected ModelMBeanNotificationInfo[] buildNotificationInfo(List notifications)
   {

      Iterator it = notifications.iterator();
      List infos = new ArrayList();

      while (it.hasNext())
      {
         Element notif = (Element) it.next();
         String name = notif.elementTextTrim("name");
         String descr = notif.elementTextTrim("description");
         List notifTypes = notif.elements("notification-type");

         Iterator iterator = notifTypes.iterator();
         List types = new ArrayList();

         while (iterator.hasNext())
         {
            Element type = (Element) iterator.next();
            types.add(type.getTextTrim());
         }

         ModelMBeanNotificationInfo info = new ModelMBeanNotificationInfo(
            (String[]) types.toArray(), name, descr
         );

         infos.add(info);
      }

      return (ModelMBeanNotificationInfo[]) infos.toArray(
         new ModelMBeanNotificationInfo[0]
      );
   }

   /**
    * Parses the contents of an <tt>&lt;attribute&gt;</tt> element. Subclasses
    * may override if they need to handle additional nested elements or arguments.
    *
    * @param   attributes  a list of <tt>&lt;attribute&gt;</tt> elements
    *                      extracted from the document instance
    *
    * @return  initialized attribute info
    */
   protected ModelMBeanAttributeInfo[] buildAttributeInfo(List attributes)
   {

      Iterator it = attributes.iterator();
      List infos = new ArrayList();

      while (it.hasNext())
      {
         Element attr = (Element) it.next();
         String name = attr.elementTextTrim("name");
         String description = attr.elementTextTrim("description");
         String type = attr.elementTextTrim("type");
         String access = attr.elementTextTrim("access");

         Attribute persistPolicy = attr.attribute(PERSIST_POLICY);
         Attribute persistPeriod = attr.attribute(PERSIST_PERIOD);
         Attribute setMethod = attr.attribute(SET_METHOD);
         Attribute getMethod = attr.attribute(GET_METHOD);
         Attribute currTimeLimit = attr.attribute(CURRENCY_TIME_LIMIT);

         Descriptor descr = new DescriptorSupport();
         descr.setField("name", name);
         descr.setField("descriptorType", "attribute");

         if (persistPolicy != null)
            descr.setField(PERSIST_POLICY, persistPolicy.getValue());
         if (persistPeriod != null)
            descr.setField(PERSIST_PERIOD, persistPeriod.getValue());
         if (setMethod != null)
            descr.setField(SET_METHOD, setMethod.getValue());
         if (getMethod != null)
            descr.setField(GET_METHOD, getMethod.getValue());
         if (currTimeLimit != null)
            descr.setField(CURRENCY_TIME_LIMIT, currTimeLimit.getValue());

         // if no method mapping, enable caching automatically
         if (setMethod == null && getMethod == null && currTimeLimit == null)
            descr.setField(CURRENCY_TIME_LIMIT, "-1");

         // defaults read-write
         boolean isReadable = true;
         boolean isWritable = true;

         if (access.equalsIgnoreCase("read-only"))
            isWritable = false;

         else if (access.equalsIgnoreCase("write-only"))
            isReadable = false;


         ModelMBeanAttributeInfo info = new ModelMBeanAttributeInfo(
            name, type, description, isReadable, isWritable, false, descr
         );

         infos.add(info);
      }

      return (ModelMBeanAttributeInfo[]) infos.toArray(new ModelMBeanAttributeInfo[0]);
   }

   /**
    * Parses the contents of an <tt>&lt;parameter&gt;</tt> element. Subclasses
    * may override if they need to handle additional nested elements or arguments.
    *
    * @param   operations  a list of <tt>&lt;parameter&gt;</tt> elements
    *                      extracted from the document instance
    *
    * @return  initialized parameter info
    */
   protected MBeanParameterInfo[] buildParameterInfo(List parameters)
   {

      Iterator it = parameters.iterator();
      List infos = new ArrayList();

      while (it.hasNext())
      {
         Element param = (Element) it.next();
         String name = param.elementTextTrim("name");
         String type = param.elementTextTrim("type");
         String descr = param.elementTextTrim("description");

         MBeanParameterInfo info = new MBeanParameterInfo(name, type, descr);

         infos.add(info);
      }

      return (MBeanParameterInfo[]) infos.toArray(new MBeanParameterInfo[0]);
   }

}



