code: /*
   * 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.xml.utils;
  
  import java.util.Stack;
  import java.util.StringTokenizer;
  
  import org.apache.xml.res.XMLErrorResources;
  import org.apache.xml.res.XMLMessages;
  
  import org.w3c.dom.Element;
  
  /**
   * Class to represent a qualified name: "The name of an internal XSLT object,
   * specifically a named template (see [7 Named Templates]), a mode (see [6.7 Modes]),
   * an attribute set (see [8.1.4 Named Attribute Sets]), a key (see [14.2 Keys]),
   * a locale (see [14.3 Number Formatting]), a variable or a parameter (see
   * [12 Variables and Parameters]) is specified as a QName. If it has a prefix,
   * then the prefix is expanded into a URI reference using the namespace declarations
   * in effect on the attribute in which the name occurs. The expanded name
   * consisting of the local part of the name and the possibly null URI reference
   * is used as the name of the object. The default namespace is not used for
   * unprefixed names."
   * @xsl.usage general
   */
  public class QName implements java.io.Serializable
  {
      static final long serialVersionUID = 467434581652829920L;
  
    /**
     * The local name.
     * @serial
     */
    protected String _localName;
  
    /**
     * The namespace URI.
     * @serial
     */
    protected String _namespaceURI;
  
    /**
     * The namespace prefix.
     * @serial
     */
    protected String _prefix;
  
    /**
     * The XML namespace.
     */
    public static final String S_XMLNAMESPACEURI =
      "http://www.w3.org/XML/1998/namespace";
  
    /**
     * The cached hashcode, which is calculated at construction time.
     * @serial
     */
    private int m_hashCode;
  
    /**
     * Constructs an empty QName.
     * 20001019: Try making this public, to support Serializable? -- JKESS
     */
    public QName(){}
  
    /**
     * Constructs a new QName with the specified namespace URI and
     * local name.
     *
     * @param namespaceURI The namespace URI if known, or null
     * @param localName The local name
     */
    public QName(String namespaceURI, String localName)
    {
      this(namespaceURI, localName, false); 
    }
  
    /**
     * Constructs a new QName with the specified namespace URI and
     * local name.
     *
     * @param namespaceURI The namespace URI if known, or null
     * @param localName The local name
     * @param validate If true the new QName will be validated and an IllegalArgumentException will
     *                 be thrown if it is invalid.
     */
    public QName(String namespaceURI, String localName, boolean validate) 
    {
  
      // This check was already here.  So, for now, I will not add it to the validation
      // that is done when the validate parameter is true.
      if (localName == null)
        throw new IllegalArgumentException(XMLMessages.createXMLMessage(
              XMLErrorResources.ER_ARG_LOCALNAME_NULL, null)); //"Argument 'localName' is null");
  
      if (validate) 
      {
          if (!XML11Char.isXML11ValidNCName(localName))
          {
              throw new IllegalArgumentException(XMLMessages.createXMLMessage(
              XMLErrorResources.ER_ARG_LOCALNAME_INVALID,null )); //"Argument 'localName' not a valid NCName");
          }
      }
      
      _namespaceURI = namespaceURI;
      _localName = localName;
      m_hashCode = toString().hashCode();
    }
    
    /**
     * Constructs a new QName with the specified namespace URI, prefix
     * and local name.
     *
     * @param namespaceURI The namespace URI if known, or null
     * @param prefix The namespace prefix is known, or null
     * @param localName The local name
     * 
     */
    public QName(String namespaceURI, String prefix, String localName)
    {
       this(namespaceURI, prefix, localName, false);
    }
    
   /**
     * Constructs a new QName with the specified namespace URI, prefix
     * and local name.
     *
     * @param namespaceURI The namespace URI if known, or null
     * @param prefix The namespace prefix is known, or null
     * @param localName The local name
     * @param validate If true the new QName will be validated and an IllegalArgumentException will
     *                 be thrown if it is invalid.
     */
    public QName(String namespaceURI, String prefix, String localName, boolean validate)
    {
  
      // This check was already here.  So, for now, I will not add it to the validation
      // that is done when the validate parameter is true.
      if (localName == null)
        throw new IllegalArgumentException(XMLMessages.createXMLMessage(
              XMLErrorResources.ER_ARG_LOCALNAME_NULL, null)); //"Argument 'localName' is null");
  
      if (validate)
      {    
          if (!XML11Char.isXML11ValidNCName(localName))
          {
              throw new IllegalArgumentException(XMLMessages.createXMLMessage(
              XMLErrorResources.ER_ARG_LOCALNAME_INVALID,null )); //"Argument 'localName' not a valid NCName");
          }
  
          if ((null != prefix) && (!XML11Char.isXML11ValidNCName(prefix)))
          {
              throw new IllegalArgumentException(XMLMessages.createXMLMessage(
              XMLErrorResources.ER_ARG_PREFIX_INVALID,null )); //"Argument 'prefix' not a valid NCName");
          }
  
      }
      _namespaceURI = namespaceURI;
      _prefix = prefix;
      _localName = localName;
      m_hashCode = toString().hashCode();
    }  
  
    /**
     * Construct a QName from a string, without namespace resolution.  Good
     * for a few odd cases.
     *
     * @param localName Local part of qualified name
     * 
     */
    public QName(String localName)
    {
      this(localName, false);
    }
    
    /**
     * Construct a QName from a string, without namespace resolution.  Good
     * for a few odd cases.
     *
     * @param localName Local part of qualified name
     * @param validate If true the new QName will be validated and an IllegalArgumentException will
     *                 be thrown if it is invalid.
     */
    public QName(String localName, boolean validate)
    {
  
      // This check was already here.  So, for now, I will not add it to the validation
      // that is done when the validate parameter is true.
      if (localName == null)
        throw new IllegalArgumentException(XMLMessages.createXMLMessage(
              XMLErrorResources.ER_ARG_LOCALNAME_NULL, null)); //"Argument 'localName' is null");
  
      if (validate)
      {    
          if (!XML11Char.isXML11ValidNCName(localName))
          {
              throw new IllegalArgumentException(XMLMessages.createXMLMessage(
              XMLErrorResources.ER_ARG_LOCALNAME_INVALID,null )); //"Argument 'localName' not a valid NCName");
          }
      }
      _namespaceURI = null;
      _localName = localName;
      m_hashCode = toString().hashCode();
    }  
  
    /**
     * Construct a QName from a string, resolving the prefix
     * using the given namespace stack. The default namespace is
     * not resolved.
     *
     * @param qname Qualified name to resolve
     * @param namespaces Namespace stack to use to resolve namespace
     */
    public QName(String qname, Stack namespaces)
    {
      this(qname, namespaces, false);
    }
  
    /**
     * Construct a QName from a string, resolving the prefix
     * using the given namespace stack. The default namespace is
     * not resolved.
     *
     * @param qname Qualified name to resolve
     * @param namespaces Namespace stack to use to resolve namespace
     * @param validate If true the new QName will be validated and an IllegalArgumentException will
     *                 be thrown if it is invalid.
     */
    public QName(String qname, Stack namespaces, boolean validate)
    {
  
      String namespace = null;
      String prefix = null;
      int indexOfNSSep = qname.indexOf(':');
  
      if (indexOfNSSep > 0)
      {
        prefix = qname.substring(0, indexOfNSSep);
  
        if (prefix.equals("xml"))
        {
          namespace = S_XMLNAMESPACEURI;
        }
        // Do we want this?
        else if (prefix.equals("xmlns"))
        {
          return;
        }
        else
        {
          int depth = namespaces.size();
  
          for (int i = depth - 1; i >= 0; i--)
          {
            NameSpace ns = (NameSpace) namespaces.elementAt(i);
  
            while (null != ns)
            {
              if ((null != ns.m_prefix) && prefix.equals(ns.m_prefix))
              {
                namespace = ns.m_uri;
                i = -1;
  
                break;
              }
  
              ns = ns.m_next;
            }
          }
        }
  
        if (null == namespace)
        {
          throw new RuntimeException(
            XMLMessages.createXMLMessage(
              XMLErrorResources.ER_PREFIX_MUST_RESOLVE,
              new Object[]{ prefix }));  //"Prefix must resolve to a namespace: "+prefix);
        }
      }
  
      _localName = (indexOfNSSep < 0)
                   ? qname : qname.substring(indexOfNSSep + 1);
                   
      if (validate)
      {
          if ((_localName == null) || (!XML11Char.isXML11ValidNCName(_localName))) 
          {
             throw new IllegalArgumentException(XMLMessages.createXMLMessage(
              XMLErrorResources.ER_ARG_LOCALNAME_INVALID,null )); //"Argument 'localName' not a valid NCName");
          }
      }                 
      _namespaceURI = namespace;
      _prefix = prefix;
      m_hashCode = toString().hashCode();
    }
  
    /**
     * Construct a QName from a string, resolving the prefix
     * using the given namespace context and prefix resolver. 
     * The default namespace is not resolved.
     * 
     * @param qname Qualified name to resolve
     * @param namespaceContext Namespace Context to use
     * @param resolver Prefix resolver for this context
     */
    public QName(String qname, Element namespaceContext,
                 PrefixResolver resolver)
    {
        this(qname, namespaceContext, resolver, false);
    }
  
    /**
     * Construct a QName from a string, resolving the prefix
     * using the given namespace context and prefix resolver. 
     * The default namespace is not resolved.
     * 
     * @param qname Qualified name to resolve
     * @param namespaceContext Namespace Context to use
     * @param resolver Prefix resolver for this context
     * @param validate If true the new QName will be validated and an IllegalArgumentException will
     *                 be thrown if it is invalid.
     */
    public QName(String qname, Element namespaceContext,
                 PrefixResolver resolver, boolean validate)
    {
  
      _namespaceURI = null;
  
      int indexOfNSSep = qname.indexOf(':');
  
      if (indexOfNSSep > 0)
      {
        if (null != namespaceContext)
        {
          String prefix = qname.substring(0, indexOfNSSep);
  
          _prefix = prefix;
  
          if (prefix.equals("xml"))
          {
            _namespaceURI = S_XMLNAMESPACEURI;
          }
          
          // Do we want this?
          else if (prefix.equals("xmlns"))
          {
            return;
          }
          else
          {
            _namespaceURI = resolver.getNamespaceForPrefix(prefix,
                    namespaceContext);
          }
  
          if (null == _namespaceURI)
          {
            throw new RuntimeException(
              XMLMessages.createXMLMessage(
                XMLErrorResources.ER_PREFIX_MUST_RESOLVE,
                new Object[]{ prefix }));  //"Prefix must resolve to a namespace: "+prefix);
          }
        }
        else
        {
  
          // TODO: error or warning...
        }
      }
  
      _localName = (indexOfNSSep < 0)
                   ? qname : qname.substring(indexOfNSSep + 1);
  
      if (validate)
      {
          if ((_localName == null) || (!XML11Char.isXML11ValidNCName(_localName))) 
          {
             throw new IllegalArgumentException(XMLMessages.createXMLMessage(
              XMLErrorResources.ER_ARG_LOCALNAME_INVALID,null )); //"Argument 'localName' not a valid NCName");
          }
      }                 
                   
      m_hashCode = toString().hashCode();
    }
  
  
    /**
     * Construct a QName from a string, resolving the prefix
     * using the given namespace stack. The default namespace is
     * not resolved.
     *
     * @param qname Qualified name to resolve
     * @param resolver Prefix resolver for this context
     */
    public QName(String qname, PrefixResolver resolver)
    {
      this(qname, resolver, false);
    }
  
    /**
     * Construct a QName from a string, resolving the prefix
     * using the given namespace stack. The default namespace is
     * not resolved.
     *
     * @param qname Qualified name to resolve
     * @param resolver Prefix resolver for this context
     * @param validate If true the new QName will be validated and an IllegalArgumentException will
     *                 be thrown if it is invalid.
     */
    public QName(String qname, PrefixResolver resolver, boolean validate)
    {
  
  	String prefix = null;
      _namespaceURI = null;
  
      int indexOfNSSep = qname.indexOf(':');
  
      if (indexOfNSSep > 0)
      {
        prefix = qname.substring(0, indexOfNSSep);
  
        if (prefix.equals("xml"))
        {
          _namespaceURI = S_XMLNAMESPACEURI;
        }
        else
        {
          _namespaceURI = resolver.getNamespaceForPrefix(prefix);
        }
  
        if (null == _namespaceURI)
        {
          throw new RuntimeException(
            XMLMessages.createXMLMessage(
              XMLErrorResources.ER_PREFIX_MUST_RESOLVE,
              new Object[]{ prefix }));  //"Prefix must resolve to a namespace: "+prefix);
        }
        _localName = qname.substring(indexOfNSSep + 1);
      }
      else if (indexOfNSSep == 0) 
      {
        throw new RuntimeException(
           XMLMessages.createXMLMessage(
             XMLErrorResources.ER_NAME_CANT_START_WITH_COLON,
             null));
      }
      else
      {
        _localName = qname;
      }   
                   
      if (validate)
      {
          if ((_localName == null) || (!XML11Char.isXML11ValidNCName(_localName))) 
          {
             throw new IllegalArgumentException(XMLMessages.createXMLMessage(
              XMLErrorResources.ER_ARG_LOCALNAME_INVALID,null )); //"Argument 'localName' not a valid NCName");
          }
      }                 
  
                
      m_hashCode = toString().hashCode();
      _prefix = prefix;
    }
  
    /**
     * Returns the namespace URI. Returns null if the namespace URI
     * is not known.
     *
     * @return The namespace URI, or null
     */
    public String getNamespaceURI()
    {
      return _namespaceURI;
    }
  
    /**
     * Returns the namespace prefix. Returns null if the namespace
     * prefix is not known.
     *
     * @return The namespace prefix, or null
     */
    public String getPrefix()
    {
      return _prefix;
    }
  
    /**
     * Returns the local part of the qualified name.
     *
     * @return The local part of the qualified name
     */
    public String getLocalName()
    {
      return _localName;
    }
  
    /**
     * Return the string representation of the qualified name, using the 
     * prefix if available, or the '{ns}foo' notation if not. Performs
     * string concatenation, so beware of performance issues.
     *
     * @return the string representation of the namespace
     */
    public String toString()
    {
  
      return _prefix != null
             ? (_prefix + ":" + _localName)
             : (_namespaceURI != null
                ? ("{"+_namespaceURI + "}" + _localName) : _localName);
    }
    
    /**
     * Return the string representation of the qualified name using the 
     * the '{ns}foo' notation. Performs
     * string concatenation, so beware of performance issues.
     *
     * @return the string representation of the namespace
     */
    public String toNamespacedString()
    {
  
      return (_namespaceURI != null
                ? ("{"+_namespaceURI + "}" + _localName) : _localName);
    }
  
  
    /**
     * Get the namespace of the qualified name.
     *
     * @return the namespace URI of the qualified name
     */
    public String getNamespace()
    {
      return getNamespaceURI();
    }
  
    /**
     * Get the local part of the qualified name.
     *
     * @return the local part of the qualified name
     */
    public String getLocalPart()
    {
      return getLocalName();
    }
  
    /**
     * Return the cached hashcode of the qualified name.
     *
     * @return the cached hashcode of the qualified name
     */
    public int hashCode()
    {
      return m_hashCode;
    }
  
    /**
     * Override equals and agree that we're equal if
     * the passed object is a string and it matches
     * the name of the arg.
     *
     * @param ns Namespace URI to compare to
     * @param localPart Local part of qualified name to compare to 
     *
     * @return True if the local name and uri match 
     */
    public boolean equals(String ns, String localPart)
    {
  
      String thisnamespace = getNamespaceURI();
  
      return getLocalName().equals(localPart)
             && (((null != thisnamespace) && (null != ns))
                 ? thisnamespace.equals(ns)
                 : ((null == thisnamespace) && (null == ns)));
    }
  
    /**
     * Override equals and agree that we're equal if
     * the passed object is a QName and it matches
     * the name of the arg.
     *
     * @return True if the qualified names are equal
     */
    public boolean equals(Object object)
    {
  
      if (object == this)
        return true;
  
      if (object instanceof QName) {
        QName qname = (QName) object;
        String thisnamespace = getNamespaceURI();
        String thatnamespace = qname.getNamespaceURI();
  
        return getLocalName().equals(qname.getLocalName())
               && (((null != thisnamespace) && (null != thatnamespace))
                   ? thisnamespace.equals(thatnamespace)
                   : ((null == thisnamespace) && (null == thatnamespace)));
      }
      else
        return false;
    }
  
    /**
     * Given a string, create and return a QName object  
     *
     *
     * @param name String to use to create QName
     *
     * @return a QName object
     */
    public static QName getQNameFromString(String name)
    {
  
      StringTokenizer tokenizer = new StringTokenizer(name, "{}", false);
      QName qname;
      String s1 = tokenizer.nextToken();
      String s2 = tokenizer.hasMoreTokens() ? tokenizer.nextToken() : null;
  
      if (null == s2)
        qname = new QName(null, s1);
      else
        qname = new QName(s1, s2);
  
      return qname;
    }
  
    /**
     * This function tells if a raw attribute name is a
     * xmlns attribute.
     *
     * @param attRawName Raw name of attribute
     *
     * @return True if the attribute starts with or is equal to xmlns 
     */
    public static boolean isXMLNSDecl(String attRawName)
    {
  
      return (attRawName.startsWith("xmlns")
              && (attRawName.equals("xmlns")
                  || attRawName.startsWith("xmlns:")));
    }
  
    /**
     * This function tells if a raw attribute name is a
     * xmlns attribute.
     *
     * @param attRawName Raw name of attribute
     *
     * @return Prefix of attribute
     */
    public static String getPrefixFromXMLNSDecl(String attRawName)
    {
  
      int index = attRawName.indexOf(':');
  
      return (index >= 0) ? attRawName.substring(index + 1) : "";
    }
  
    /**
     * Returns the local name of the given node.
     *
     * @param qname Input name
     *
     * @return Local part of the name if prefixed, or the given name if not
     */
    public static String getLocalPart(String qname)
    {
  
      int index = qname.indexOf(':');
  
      return (index < 0) ? qname : qname.substring(index + 1);
    }
  
    /**
     * Returns the local name of the given node.
     *
     * @param qname Input name 
     *
     * @return Prefix of name or empty string if none there   
     */
    public static String getPrefixPart(String qname)
    {
  
      int index = qname.indexOf(':');
  
      return (index >= 0) ? qname.substring(0, index) : "";
    }
  }
  