/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the  "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/*
 * $Id$
 */
package org.apache.xalan.processor;

import java.util.Enumeration;
import java.util.Hashtable;

import org.apache.xalan.templates.Constants;
import org.apache.xml.utils.QName;

/**
 * This class defines the allowed structure for an element in a XSLT stylesheet,
 * is meant to reflect the structure defined in http://www.w3.org/TR/xslt#dtd, and the
 * mapping between Xalan classes and the markup elements in the XSLT instance.
 * This actually represents both text nodes and elements.
 */
public class XSLTElementDef
{

  /**
   * Construct an instance of XSLTElementDef.  This must be followed by a
   * call to build().
   */
  XSLTElementDef(){}

  /**
   * Construct an instance of XSLTElementDef.
   *
   * @param namespace  The Namespace URI, "*", or null.
   * @param name The local name (without prefix), "*", or null.
   * @param nameAlias A potential alias for the name, or null.
   * @param elements An array of allowed child element defs, or null.
   * @param attributes An array of allowed attribute defs, or null.
   * @param contentHandler The element processor for this element.
   * @param classObject The class of the object that this element def should produce.
   */
  XSLTElementDef(XSLTSchema schema, String namespace, String name, String nameAlias,
                 XSLTElementDef[] elements, XSLTAttributeDef[] attributes,
                 XSLTElementProcessor contentHandler, Class classObject)
  {
    build(namespace, name, nameAlias, elements, attributes, contentHandler,
          classObject);
    if ( (null != namespace)
    &&  (namespace.equals(Constants.S_XSLNAMESPACEURL)
        || namespace.equals(Constants.S_BUILTIN_EXTENSIONS_URL)
        || namespace.equals(Constants.S_BUILTIN_OLD_EXTENSIONS_URL)))
    {
      schema.addAvailableElement(new QName(namespace, name));
      if(null != nameAlias)
        schema.addAvailableElement(new QName(namespace, nameAlias));
    } 
  }
	
	/**
   * Construct an instance of XSLTElementDef.
   *
   * @param namespace  The Namespace URI, "*", or null.
   * @param name The local name (without prefix), "*", or null.
   * @param nameAlias A potential alias for the name, or null.
   * @param elements An array of allowed child element defs, or null.
   * @param attributes An array of allowed attribute defs, or null.
   * @param contentHandler The element processor for this element.
   * @param classObject The class of the object that this element def should produce.
   * @param has_required true if this element has required elements by the XSLT specification.
   */
  XSLTElementDef(XSLTSchema schema, String namespace, String name, String nameAlias,
                 XSLTElementDef[] elements, XSLTAttributeDef[] attributes,
                 XSLTElementProcessor contentHandler, Class classObject, boolean has_required)
  {
		this.m_has_required = has_required;
    build(namespace, name, nameAlias, elements, attributes, contentHandler,
          classObject);
    if ( (null != namespace)
    &&  (namespace.equals(Constants.S_XSLNAMESPACEURL)
        || namespace.equals(Constants.S_BUILTIN_EXTENSIONS_URL)
        || namespace.equals(Constants.S_BUILTIN_OLD_EXTENSIONS_URL)))
    {
      schema.addAvailableElement(new QName(namespace, name));
      if(null != nameAlias)
        schema.addAvailableElement(new QName(namespace, nameAlias));
    } 
		
  }
	
	/**
   * Construct an instance of XSLTElementDef.
   *
   * @param namespace  The Namespace URI, "*", or null.
   * @param name The local name (without prefix), "*", or null.
   * @param nameAlias A potential alias for the name, or null.
   * @param elements An array of allowed child element defs, or null.
   * @param attributes An array of allowed attribute defs, or null.
   * @param contentHandler The element processor for this element.
   * @param classObject The class of the object that this element def should produce.
   * @param has_required true if this element has required elements by the XSLT specification.
   * @param required true if this element is required by the XSLT specification.
   */
  XSLTElementDef(XSLTSchema schema, String namespace, String name, String nameAlias,
                 XSLTElementDef[] elements, XSLTAttributeDef[] attributes,
                 XSLTElementProcessor contentHandler, Class classObject, 
								 boolean has_required, boolean required)
  {
    this(schema, namespace, name,  nameAlias,
                 elements, attributes,
                 contentHandler, classObject, has_required);
		this.m_required = required;
  }
	
	/**
   * Construct an instance of XSLTElementDef.
   *
   * @param namespace  The Namespace URI, "*", or null.
   * @param name The local name (without prefix), "*", or null.
   * @param nameAlias A potential alias for the name, or null.
   * @param elements An array of allowed child element defs, or null.
   * @param attributes An array of allowed attribute defs, or null.
   * @param contentHandler The element processor for this element.
   * @param classObject The class of the object that this element def should produce.
   * @param has_required true if this element has required elements by the XSLT specification.
   * @param required true if this element is required by the XSLT specification.
   * @param order the order this element should appear according to the XSLT specification.   
   * @param multiAllowed whether this element is allowed more than once
   */
  XSLTElementDef(XSLTSchema schema, String namespace, String name, String nameAlias,
                 XSLTElementDef[] elements, XSLTAttributeDef[] attributes,
                 XSLTElementProcessor contentHandler, Class classObject, 
								 boolean has_required, boolean required, int order, 
								 boolean multiAllowed)
  {
		this(schema, namespace, name,  nameAlias,
                 elements, attributes,
                 contentHandler, classObject, has_required, required);    
		this.m_order = order;
		this.m_multiAllowed = multiAllowed;
  }
	
	/**
   * Construct an instance of XSLTElementDef.
   *
   * @param namespace  The Namespace URI, "*", or null.
   * @param name The local name (without prefix), "*", or null.
   * @param nameAlias A potential alias for the name, or null.
   * @param elements An array of allowed child element defs, or null.
   * @param attributes An array of allowed attribute defs, or null.
   * @param contentHandler The element processor for this element.
   * @param classObject The class of the object that this element def should produce.
   * @param has_required true if this element has required elements by the XSLT specification.
   * @param required true if this element is required by the XSLT specification.
   * @param has_order whether this element has ordered child elements
   * @param order the order this element should appear according to the XSLT specification.   
   * @param multiAllowed whether this element is allowed more than once
   */
  XSLTElementDef(XSLTSchema schema, String namespace, String name, String nameAlias,
                 XSLTElementDef[] elements, XSLTAttributeDef[] attributes,
                 XSLTElementProcessor contentHandler, Class classObject, 
								 boolean has_required, boolean required, boolean has_order, int order, 
								 boolean multiAllowed)
  {
		this(schema, namespace, name,  nameAlias,
                 elements, attributes,
                 contentHandler, classObject, has_required, required);    
		this.m_order = order;
		this.m_multiAllowed = multiAllowed;
    this.m_isOrdered = has_order;		
  }
	
	/**
   * Construct an instance of XSLTElementDef.
   *
   * @param namespace  The Namespace URI, "*", or null.
   * @param name The local name (without prefix), "*", or null.
   * @param nameAlias A potential alias for the name, or null.
   * @param elements An array of allowed child element defs, or null.
   * @param attributes An array of allowed attribute defs, or null.
   * @param contentHandler The element processor for this element.
   * @param classObject The class of the object that this element def should produce.
   * @param has_order whether this element has ordered child elements
   * @param order the order this element should appear according to the XSLT specification.   
   * @param multiAllowed whether this element is allowed more than once
   */
  XSLTElementDef(XSLTSchema schema, String namespace, String name, String nameAlias,
                 XSLTElementDef[] elements, XSLTAttributeDef[] attributes,
                 XSLTElementProcessor contentHandler, Class classObject, 
								 boolean has_order, int order, boolean multiAllowed)
  {
    this(schema, namespace, name,  nameAlias,
                 elements, attributes,
                 contentHandler, classObject, 
								 order, multiAllowed);
		this.m_isOrdered = has_order;		
  }
	
	/**
   * Construct an instance of XSLTElementDef.
   *
   * @param namespace  The Namespace URI, "*", or null.
   * @param name The local name (without prefix), "*", or null.
   * @param nameAlias A potential alias for the name, or null.
   * @param elements An array of allowed child element defs, or null.
   * @param attributes An array of allowed attribute defs, or null.
   * @param contentHandler The element processor for this element.
   * @param classObject The class of the object that this element def should produce.
   * @param order the order this element should appear according to the XSLT specification.   
   * @param multiAllowed whether this element is allowed more than once
   */
  XSLTElementDef(XSLTSchema schema, String namespace, String name, String nameAlias,
                 XSLTElementDef[] elements, XSLTAttributeDef[] attributes,
                 XSLTElementProcessor contentHandler, Class classObject, 
								 int order, boolean multiAllowed)
  {
    this(schema, namespace, name, nameAlias, elements, attributes, contentHandler,
          classObject);
    this.m_order = order;
		this.m_multiAllowed = multiAllowed;
  }

  /**
   * Construct an instance of XSLTElementDef that represents text.
   *
   * @param classObject The class of the object that this element def should produce.
   * @param contentHandler The element processor for this element.
   * @param type Content type, one of T_ELEMENT, T_PCDATA, or T_ANY.
   */
  XSLTElementDef(Class classObject, XSLTElementProcessor contentHandler,
                 int type)
  {

    this.m_classObject = classObject;
    this.m_type = type;

    setElementProcessor(contentHandler);
  }

  /**
   * Construct an instance of XSLTElementDef.
   *
   * @param namespace  The Namespace URI, "*", or null.
   * @param name The local name (without prefix), "*", or null.
   * @param nameAlias A potential alias for the name, or null.
   * @param elements An array of allowed child element defs, or null.
   * @param attributes An array of allowed attribute defs, or null.
   * @param contentHandler The element processor for this element.
   * @param classObject The class of the object that this element def should produce.
   */
  void build(String namespace, String name, String nameAlias,
             XSLTElementDef[] elements, XSLTAttributeDef[] attributes,
             XSLTElementProcessor contentHandler, Class classObject)
  {

    this.m_namespace = namespace;
    this.m_name = name;
    this.m_nameAlias = nameAlias;
    this.m_elements = elements;
    this.m_attributes = attributes;

    setElementProcessor(contentHandler);

    this.m_classObject = classObject;
		
		if (hasRequired() && m_elements != null)
		{
			int n = m_elements.length;
			for (int i = 0; i < n; i++)
			{
				XSLTElementDef def = m_elements[i];
				
				if (def != null && def.getRequired())
				{
					if (m_requiredFound == null)			
						m_requiredFound = new Hashtable();
					m_requiredFound.put(def.getName(), "xsl:" +def.getName()); 
				}
			}
		}
  }

  /**
   * Tell if two objects are equal, when either one may be null.
   * If both are null, they are considered equal.
   *
   * @param obj1 A reference to the first object, or null.
   * @param obj2 A reference to the second object, or null.
   *
   * @return true if the to objects are equal by both being null or 
   * because obj2.equals(obj1) returns true.
   */
  private static boolean equalsMayBeNull(Object obj1, Object obj2)
  {
    return (obj2 == obj1)
           || ((null != obj1) && (null != obj2) && obj2.equals(obj1));
  }

  /**
   * Tell if the two string refs are equal,
   * equality being defined as:
   * 1) Both strings are null.
   * 2) One string is null and the other is empty.
   * 3) Both strings are non-null, and equal.
   *
   * @param s1 A reference to the first string, or null.
   * @param s2 A reference to the second string, or null.
   *
   * @return true if Both strings are null, or if 
   * one string is null and the other is empty, or if 
   * both strings are non-null, and equal because 
   * s1.equals(s2) returns true.
   */
  private static boolean equalsMayBeNullOrZeroLen(String s1, String s2)
  {

    int len1 = (s1 == null) ? 0 : s1.length();
    int len2 = (s2 == null) ? 0 : s2.length();

    return (len1 != len2) ? false 
						 : (len1 == 0) ? true 
								 : s1.equals(s2);
  }

  /** Content type enumerations    */
  static final int T_ELEMENT = 1, T_PCDATA = 2, T_ANY = 3;

  /**
   * The type of this element.
   */
  private int m_type = T_ELEMENT;

  /**
   * Get the type of this element.
   *
   * @return Content type, one of T_ELEMENT, T_PCDATA, or T_ANY.
   */
  int getType()
  {
    return m_type;
  }

  /**
   * Set the type of this element.
   *
   * @param t Content type, one of T_ELEMENT, T_PCDATA, or T_ANY.
   */
  void setType(int t)
  {
    m_type = t;
  }

  /**
   * The allowed namespace for this element.
   */
  private String m_namespace;

  /**
   * Get the allowed namespace for this element.
   *
   * @return The Namespace URI, "*", or null.
   */
  String getNamespace()
  {
    return m_namespace;
  }

  /**
   * The name of this element.
   */
  private String m_name;

  /**
   * Get the local name of this element.
   *
   * @return The local name of this element, "*", or null.
   */
  String getName()
  {
    return m_name;
  }

  /**
   * The name of this element.
   */
  private String m_nameAlias;

  /**
   * Get the name of this element.
   *
   * @return A potential alias for the name, or null.
   */
  String getNameAlias()
  {
    return m_nameAlias;
  }

  /**
   * The allowed elements for this type.
   */
  private XSLTElementDef[] m_elements;

  /**
   * Get the allowed elements for this type.
   *
   * @return An array of allowed child element defs, or null.
   * @xsl.usage internal
   */
  public XSLTElementDef[] getElements()
  {
    return m_elements;
  }

  /**
   * Set the allowed elements for this type.
   *
   * @param defs An array of allowed child element defs, or null.
   */
  void setElements(XSLTElementDef[] defs)
  {
    m_elements = defs;
  }

  /**
   * Tell if the namespace URI and local name match this
   * element.
   * @param uri The namespace uri, which may be null.
   * @param localName The local name of an element, which may be null.
   *
   * @return true if the uri and local name arguments are considered 
   * to match the uri and local name of this element def.
   */
  private boolean QNameEquals(String uri, String localName)
  {

    return (equalsMayBeNullOrZeroLen(m_namespace, uri)
            && (equalsMayBeNullOrZeroLen(m_name, localName)
                || equalsMayBeNullOrZeroLen(m_nameAlias, localName)));
  }

  /**
   * Given a namespace URI, and a local name, get the processor
   * for the element, or return null if not allowed.
   *
   * @param uri The Namespace URI, or an empty string.
   * @param localName The local name (without prefix), or empty string if not namespace processing.
   *
   * @return The element processor that matches the arguments, or null.
   */
  XSLTElementProcessor getProcessorFor(String uri, String localName) 
	{

    XSLTElementProcessor elemDef = null;  // return value

    if (null == m_elements)
      return null;

    int n = m_elements.length;
    int order = -1;
		boolean multiAllowed = true;
    for (int i = 0; i < n; i++)
    {
      XSLTElementDef def = m_elements[i];

      // A "*" signals that the element allows literal result
      // elements, so just assign the def, and continue to  
      // see if anything else matches.
      if (def.m_name.equals("*"))
      {
				
        // Don't allow xsl elements
        if (!equalsMayBeNullOrZeroLen(uri, Constants.S_XSLNAMESPACEURL))
				{
          elemDef = def.m_elementProcessor;
				  order = def.getOrder();
					multiAllowed = def.getMultiAllowed();
				}
      }
			else if (def.QNameEquals(uri, localName))
			{	
				if (def.getRequired())
					this.setRequiredFound(def.getName(), true);
				order = def.getOrder();
				multiAllowed = def.getMultiAllowed();
				elemDef = def.m_elementProcessor;
				break;
			}
		}		
		
		if (elemDef != null && this.isOrdered())
		{			
			int lastOrder = getLastOrder();
			if (order > lastOrder)
				setLastOrder(order);
			else if (order == lastOrder && !multiAllowed)
			{
				return null;
			}
			else if (order < lastOrder && order > 0)
			{
				return null;
			}
		}

    return elemDef;
  }

  /**
   * Given an unknown element, get the processor
   * for the element.
   *
   * @param uri The Namespace URI, or an empty string.
   * @param localName The local name (without prefix), or empty string if not namespace processing.
   *
   * @return normally a {@link ProcessorUnknown} reference.
   * @see ProcessorUnknown
   */
  XSLTElementProcessor getProcessorForUnknown(String uri, String localName)
  {

    // XSLTElementProcessor lreDef = null; // return value
    if (null == m_elements)
      return null;

    int n = m_elements.length;

    for (int i = 0; i < n; i++)
    {
      XSLTElementDef def = m_elements[i];

      if (def.m_name.equals("unknown") && uri.length() > 0)
      {
        return def.m_elementProcessor;
      }
    }

    return null;
  }

  /**
   * The allowed attributes for this type.
   */
  private XSLTAttributeDef[] m_attributes;

  /**
   * Get the allowed attributes for this type.
   *
   * @return An array of allowed attribute defs, or null.
   */
  XSLTAttributeDef[] getAttributes()
  {
    return m_attributes;
  }

  /**
   * Given a namespace URI, and a local name, return the element's
   * attribute definition, if it has one.
   *
   * @param uri The Namespace URI, or an empty string.
   * @param localName The local name (without prefix), or empty string if not namespace processing.
   *
   * @return The attribute def that matches the arguments, or null.
   */
  XSLTAttributeDef getAttributeDef(String uri, String localName)
  {

    XSLTAttributeDef defaultDef = null;
    XSLTAttributeDef[] attrDefs = getAttributes();
    int nAttrDefs = attrDefs.length;

    for (int k = 0; k < nAttrDefs; k++)
    {
      XSLTAttributeDef attrDef = attrDefs[k];
      String uriDef = attrDef.getNamespace();
      String nameDef = attrDef.getName();
      
      if (nameDef.equals("*") && (equalsMayBeNullOrZeroLen(uri, uriDef) || 
          (uriDef != null && uriDef.equals("*") && uri!=null && uri.length() > 0 )))
      {
        return attrDef;
      }
      else if (nameDef.equals("*") && (uriDef == null))
      {

        // In this case, all attributes are legal, so return 
        // this as the last resort.
        defaultDef = attrDef;
      }
      else if (equalsMayBeNullOrZeroLen(uri, uriDef)
               && localName.equals(nameDef))
      {
        return attrDef;
      }
    }

    if (null == defaultDef)
    {
      if (uri.length() > 0 && !equalsMayBeNullOrZeroLen(uri, Constants.S_XSLNAMESPACEURL))
      {
        return XSLTAttributeDef.m_foreignAttr;
      }
    }

    return defaultDef;
  }

  /**
   * If non-null, the ContentHandler/TransformerFactory for this element.
   */
  private XSLTElementProcessor m_elementProcessor;

  /**
   * Return the XSLTElementProcessor for this element.
   *
   * @return The element processor for this element.
   * @xsl.usage internal
   */
  public XSLTElementProcessor getElementProcessor()
  {
    return m_elementProcessor;
  }

  /**
   * Set the XSLTElementProcessor for this element.
   *
   * @param handler The element processor for this element.
   * @xsl.usage internal
   */
  public void setElementProcessor(XSLTElementProcessor handler)
  {

    if (handler != null)
    {
      m_elementProcessor = handler;

      m_elementProcessor.setElemDef(this);
    }
  }

  /**
   * If non-null, the class object that should in instantiated for
   * a Xalan instance of this element.
   */
  private Class m_classObject;

  /**
   * Return the class object that should in instantiated for
   * a Xalan instance of this element.
   *
   * @return The class of the object that this element def should produce, or null.
   */
  Class getClassObject()
  {
    return m_classObject;
  }
	
	/**
   * If true, this has a required element.
   */
  private boolean m_has_required = false;

  /**
   * Get whether or not this has a required element.
   *
   * @return true if this this has a required element.
   */
  boolean hasRequired()
  {
    return m_has_required;
  }
	
	/**
   * If true, this is a required element.
   */
  private boolean m_required = false;

  /**
   * Get whether or not this is a required element.
   *
   * @return true if this is a required element.
   */
  boolean getRequired()
  {
    return m_required;
  }
	
	Hashtable m_requiredFound;
	
	/**
   * Set this required element found.
   *
   */
  void setRequiredFound(String elem, boolean found)
  {
   if (m_requiredFound.get(elem) != null) 
		 m_requiredFound.remove(elem);
  }
	
	/**
   * Get whether all required elements were found.
   *
   * @return true if all required elements were found.
   */
  boolean getRequiredFound()
  {
		if (m_requiredFound == null)
			return true;
    return m_requiredFound.isEmpty();
  }
	
	/**
   * Get required elements that were not found.
   *
   * @return required elements that were not found.
   */
  String getRequiredElem()
  {
		if (m_requiredFound == null)
			return null;
		Enumeration elems = m_requiredFound.elements();
		String s = "";
		boolean first = true;
		while (elems.hasMoreElements())
		{
			if (first)
				first = false;
			else
			 s = s + ", ";
			s = s + (String)elems.nextElement();
		}
    return s;
  }
	
	boolean m_isOrdered = false;	
	
	/**
   * Get whether this element requires ordered children.
   *
   * @return true if this element requires ordered children.
   */
  boolean isOrdered()
  {
		/*if (!m_CheckedOrdered)
		{
			m_CheckedOrdered = true;
			m_isOrdered = false;
			if (null == m_elements)
				return false;

			int n = m_elements.length;

			for (int i = 0; i < n; i++)
			{
				if (m_elements[i].getOrder() > 0)
				{
					m_isOrdered = true;
					return true;
				}
			}
			return false;
		}
		else*/
			return m_isOrdered;
  }
	
	/**
   * the order that this element should appear, or -1 if not ordered
   */
  private int m_order = -1;
	
	/**
   * Get the order that this element should appear .
   *
   * @return the order that this element should appear.
   */
  int getOrder()
  {
    return m_order;
  }
	
	/**
   * the highest order of child elements have appeared so far, 
   * or -1 if not ordered
   */
  private int m_lastOrder = -1;
	
	/**
   * Get the highest order of child elements have appeared so far .
   *
   * @return the highest order of child elements have appeared so far.
   */
  int getLastOrder()
  {
    return m_lastOrder;
  }
	
	/**
   * Set the highest order of child elements have appeared so far .
   *
   * @param order the highest order of child elements have appeared so far.
   */
  void setLastOrder(int order)
  {
    m_lastOrder = order ;
  }
	
	/**
   * True if this element can appear multiple times
   */
  private boolean m_multiAllowed = true;
	
	/**
   * Get whether this element can appear multiple times
   *
   * @return true if this element can appear multiple times
   */
  boolean getMultiAllowed()
  {
    return m_multiAllowed;
  }
}
