/***********************************************************
 Copyright (C) 2004 VeriSign, Inc.

 This library 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 library 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 library; if not, write to the Free Software
 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA

 http://www.verisign.com/nds/naming/namestore/techdocs.html
 ***********************************************************/
package com.verisign.epp.codec.gen;

import java.io.ByteArrayOutputStream;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.text.ParsePosition;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.TimeZone;
import java.util.Vector;

import jakarta.xml.bind.DatatypeConverter;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.apache.commons.codec.binary.Base64;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.Text;

import com.verisign.epp.interfaces.EPPCommandException;
import com.verisign.epp.util.EPPEnv;
import com.verisign.epp.util.EPPSendReceiveLogger;

/**
 * Provides a set of utility static methods for use by the EPP Codec classes.
 */
public class EPPUtil {
  /** Default format used for the XML Schema {@code dateTime} data type */
  public static final String DEFAULT_TIME_INSTANT_FORMAT = "yyyy-MM-dd'T'HH':'mm':'ss'.'SSS'Z'";

  /**
   * Format used for the XML Schema {@code dateTime} data type in the
   * {@link #encodeTimeInstant(Date) and #encodeTimeInstant(Document, Element,
   * Date, String, String)} methods supported by the {@link SimpleDateFormat}.
   */
  private static String timeInstantFormat = DEFAULT_TIME_INSTANT_FORMAT;

  /** Format used for the XML Schema date data type. */
  public static final String DATE_FORMAT = "yyyy-MM-dd";

  /** Category for logging */
      private static Logger cat = LoggerFactory.getLogger(EPPUtil.class);

  /**
   * Used to format decimal values according to the US locale to remove locale
   * specific impacts when encoding decimal values.
   */
  private static DecimalFormatSymbols decimalFormatSymbols = new DecimalFormatSymbols(Locale.US);

  /**
   * Logger for the sent and received packets. Used for the
   * {@link EPPSendReceiveLogger#maskMessage(EPPMessage)} method in
   * {@link #toString(EPPCodecComponent)}.
   */
  private static EPPSendReceiveLogger sendReceiveLogger = EPPEnv.getSendReceiveLogger();

  /**
   * Gets the XML Schema {@code timeDate} format used in
   * {@link #encodeTimeInstant(Date) and #encodeTimeInstant(Document, Element,
   * Date, String, String)}. The defauLt format is defined by the constant
   * {@link #DEFAULT_TIME_INSTANT_FORMAT} and it can be overridden using the
   * {@link #setTimeInstantFormat(String)} method.
   * 
   * @return XML Schema {@code timeDate} format used in
   *         {@link #encodeTimeInstant(Date) and #encodeTimeInstant(Document,
   *         Element, Date, String, String)}.
   */
  public static String getTimeInstantFormat() {
    return timeInstantFormat;
  }

  /**
   * Sets the XML Schema {@code timeDate} format used in
   * {@link #encodeTimeInstant(Date) and #encodeTimeInstant(Document, Element,
   * Date, String, String)}. The format must follow the format supported by
   * {@link SimpleDateFormat}.
   * 
   * @param aTimeInstantFormat
   *           XML Schema {@code timeDate} format supported by
   *           {@link SimpleDateFormat}.
   */
  public static void setTimeInstantFormat(String aTimeInstantFormat) {
    timeInstantFormat = aTimeInstantFormat;
  }

  /**
   * Appends the children Nodes of {@code aSource} as children nodes of
   * {@code aDest}.
   * 
   * @param aSource
   *           Source Element tree to get children from
   * @param aDest
   *           Destination Element to append {@code aSource} children.
   */
  public static void appendChildren(Element aSource, Element aDest) {
    NodeList children = aSource.getChildNodes();

    for (int i = 0; i < children.getLength(); i++) {
      aDest.appendChild(children.item(i).cloneNode(true));
    }
  }

  /**
   * Decode {@code BigDecimal}, by XML namespace and tag name, from an XML
   * Element. The children elements of {@code aElement} will be searched for
   * the specified {@code aNS} namespace URI and the specified
   * {@code aTagName}. The first XML element found will be decoded and
   * returned. If no element is found, {@code null} is returned.
   * 
   * @param aElement
   *           XML Element to scan. For example, the element could be
   *           &lt;domain:create&gt;
   * @param aNS
   *           XML namespace of the elements. For example, for domain element
   *           this is "urn:iana:xmlns:domain".
   * @param aTagName
   *           Tag name of the element including an optional namespace prefix.
   *           For example, the tag name for the domain name is "domain:name".
   * @return {@code BigDecimal} value if found; {@code null} otherwise.
   * @exception EPPDecodeException
   *               Error decoding {@code aElement}.
   */
  public static BigDecimal decodeBigDecimal(Element aElement, String aNS, String aTagName) throws EPPDecodeException {

    Element theElm = EPPUtil.getElementByTagNameNS(aElement, aNS, aTagName);

    return EPPUtil.decodeBigDecimal(theElm);
  }

  /**
   * Decode {@code BigDecimal}, from an XML Element. The {@code aElement}
   * element will be decoded and returned. If element is {@code null},
   * {@code null} is returned.
   * 
   * @param aElement
   *           XML Element to decode.
   * @return {@code BigDecimal} value if {@code aElement} is not {@code null};
   *         {@code null} otherwise.
   * @exception EPPDecodeException
   *               Error decoding {@code aElement}.
   */
  public static BigDecimal decodeBigDecimal(Element aElement) throws EPPDecodeException {

    BigDecimal retBigDecimal = null;

    if (aElement != null) {
      Node textNode = aElement.getFirstChild();

      // Element does have a text node?
      if (textNode != null) {

        String doubleValStr = textNode.getNodeValue();
        try {

          Double tempDouble = Double.valueOf(doubleValStr);
          retBigDecimal = new BigDecimal(tempDouble.doubleValue());
          retBigDecimal = retBigDecimal.setScale(2, RoundingMode.HALF_UP);
        }
        catch (NumberFormatException e) {
          throw new EPPDecodeException("Can't convert value to Double: " + doubleValStr + e);
        }
      }
      else {
        throw new EPPDecodeException("Can't decode numeric value from non-existant text node");
      }
    }
    return retBigDecimal;
  }

  /**
   * Decode {@code Boolean}, by XML namespace and tag name, from an XML
   * Element. The children elements of {@code aElement} will be searched for
   * the specified {@code aNS} namespace URI and the specified
   * {@code aTagName}. The first XML element found will be decoded and
   * returned. If no element is found, {@code null} is returned.
   * 
   * @param aElement
   *           XML Element to scan. For example, the element could be
   *           &lt;domain:create&gt;
   * @param aNS
   *           XML namespace of the elements. For example, for domain element
   *           this is "urn:iana:xmlns:domain".
   * @param aTagName
   *           Tag name of the element including an optional namespace prefix.
   *           For example, the tag name for the domain name is "domain:name".
   * @return {@code Boolean} value if found; {@code null} otherwise.
   * @exception EPPDecodeException
   *               Error decoding {@code aElement}.
   */
  public static Boolean decodeBoolean(Element aElement, String aNS, String aTagName) throws EPPDecodeException {
    Boolean retVal = null;
    String theVal = null;

    Element theElm = EPPUtil.getElementByTagNameNS(aElement, aNS, aTagName);

    if (theElm != null) {
      Node textNode = theElm.getFirstChild();

      // Element does have a text node?
      if (textNode != null) {
        theVal = textNode.getNodeValue();

        if (theVal.equalsIgnoreCase("true") || theVal.equals("1")) {
          retVal = Boolean.valueOf(true);
        }
        else {
          retVal = Boolean.valueOf(false);
        }
      }
      else {
        retVal = null;
      }
    }

    return retVal;
  }

  /**
   * Decodes a boolean attribute value given the {@code Element} and attribute
   * name.
   * 
   * @param aElement
   *           Element with the attribute to look for
   * @param aAttr
   *           Attribute name
   * @return Decoded boolean value
   * @exception EPPDecodeException
   *               Cound not find attribute or the attribute value is not a
   *               valid boolean value
   */
  public static boolean decodeBooleanAttr(Element aElement, String aAttr) throws EPPDecodeException {
    boolean theRet = false;

    String theAttrVal = aElement.getAttribute(aAttr);

    if (theAttrVal == null || theAttrVal.isEmpty()) {
      throw new EPPDecodeException("EPPUtil.decodeBooleanAttr: Could not find attr \"" + aAttr + "\"");
    }

    if (theAttrVal.equals("1") || theAttrVal.equals("true")) {
      theRet = true;
    }
    else if (theAttrVal.equals("0") || theAttrVal.equals("false")) {
      theRet = false;
    }
    else {
      throw new EPPDecodeException(
            "EPPUtil.decodeBooleanAttr: Invalid boolean attr \"" + aAttr + "\" value of <" + theAttrVal + ">");
    }

    return theRet;
  }

  /**
   * Decodes a string attribute by name. If the attribute is not found,
   * {@code null} is returned.
   * 
   * @param aElement
   *           Element to search for the attribute
   * @param aAttr
   *           Attribute name to search for
   * @return Attribute {@code String} value if exists; {@code null} otherwise.
   */
  public static String decodeStringAttr(Element aElement, String aAttr) {
    String theRet = null;

    String theAttrVal = aElement.getAttribute(aAttr);

    if (theAttrVal != null && !theAttrVal.isEmpty()) {
      theRet = theAttrVal;
    }

    return theRet;

  }

  /**
   * Decode a {@code EPPCodecComponent}, by XML namespace and tag name, from an
   * XML Element. The children elements of {@code aElement} will be searched
   * for the specified {@code aNS} namespace URI and the specified
   * {@code aTagName}. There first XML element found will be used to initial
   * and instance of {@code aClass}.
   * 
   * @param aElement
   *           XML Element to scan. For example, the element could be
   *           &lt;domain:update&gt;
   * @param aNS
   *           XML namespace of the elements. For example, for domain element
   *           this is "urn:iana:xmlns:domain".
   * @param aTagName
   *           Tag name of the element including an optional namespace prefix.
   *           For example, the tag name for the domain name servers is
   *           "domain:server".
   * @param aClass
   *           Class to instantiate for a found XML element. This must be a
   *           class that implements {@code EPPCodecComponent}
   * @return Instance of {@code aClass} that represents the found XML elements
   *         if found; {@code null} otherwise.
   * @exception EPPDecodeException
   *               Error decoding {@code aElement}.
   */
  public static EPPCodecComponent decodeComp(Element aElement, String aNS, String aTagName, Class aClass)
        throws EPPDecodeException {
    EPPCodecComponent retVal = null;

    try {
      Element theElm = EPPUtil.getElementByTagNameNS(aElement, aNS, aTagName);

      if (theElm != null) {
        retVal = (EPPCodecComponent) aClass.getDeclaredConstructor().newInstance();

        retVal.decode(theElm);
      }
    }
    catch (Exception e) {
      throw new EPPDecodeException("EPPUtil.decodeComp(), " + e.getClass().getName() + ": " + e);
    }

    return retVal;
  }

  /**
   * Decode a {@code List} of {@code EPPCodecComponent}'s, by XML namespace and
   * tag name, from an XML Element. The children elements of {@code aElement}
   * will be searched for the specified {@code aNS} namespace URI and the
   * specified {@code aTagName}. Each XML element found will result in the
   * instantiation of {@code aClass}, which will decode the associated XML
   * element and added to the returned {@code List}.
   * 
   * @param aElement
   *           XML Element to scan. For example, the element could be
   *           &lt;domain:add&gt;
   * @param aNS
   *           XML namespace of the elements. For example, for domain element
   *           this is "urn:iana:xmlns:domain".
   * @param aTagName
   *           Tag name of the element including an optional namespace prefix.
   *           For example, the tag name for the contact elements of
   *           &gt;domain:add&gt; is "domain:contact".
   * @param aClass
   *           Class to instantiate for each found XML element. This must be a
   *           class that implements {@code EPPCodecComponent}
   * @return {@code List} of {@code EPPCodecComponent} elements representing
   *         the found XML elements.
   * @exception EPPDecodeException
   *               Error decoding {@code aElement}.
   */
  public static List decodeCompList(Element aElement, String aNS, String aTagName, Class aClass)
        throws EPPDecodeException {
    List retVal = new ArrayList();

    try {
      Vector theChildren = EPPUtil.getElementsByTagNameNS(aElement, aNS, aTagName);

      // For each element
      for (int i = 0; i < theChildren.size(); i++) {
        EPPCodecComponent currComp = (EPPCodecComponent) aClass.getDeclaredConstructor().newInstance();

        currComp.decode((Element) theChildren.elementAt(i));

        retVal.add(currComp);
      }
    }
    catch (Exception e) {
      throw new EPPDecodeException("EPPUtil.decodeCompList(), " + e.getClass().getName() + ": " + e);

    }

    return retVal;
  }

  /**
   * Decode a {@code Vector} of {@code EPPCodecComponent}'s, by XML namespace
   * and tag name, from an XML Element. The children elements of
   * {@code aElement} will be searched for the specified {@code aNS} namespace
   * URI and the specified {@code aTagName}. Each XML element found will result
   * in the instantiation of {@code aClass}, which will decode the associated
   * XML element and added to the returned {@code Vector}.
   * 
   * @param aElement
   *           XML Element to scan. For example, the element could be
   *           &lt;domain:add&gt;
   * @param aNS
   *           XML namespace of the elements. For example, for domain element
   *           this is "urn:iana:xmlns:domain".
   * @param aTagName
   *           Tag name of the element including an optional namespace prefix.
   *           For example, the tag name for the contact elements of
   *           &lt;domain:add&gt; is "domain:contact".
   * @param aClass
   *           Class to instantiate for each found XML element. This must be a
   *           class that implements {@code EPPCodecComponent}
   * @return {@code Vector} of {@code EPPCodecComponent} elements representing
   *         the found XML elements.
   * @exception EPPDecodeException
   *               Error decoding {@code aElement}.
   */
  public static Vector decodeCompVector(Element aElement, String aNS, String aTagName, Class aClass)
        throws EPPDecodeException {
    Vector retVal = new Vector();

    try {
      Vector theChildren = EPPUtil.getElementsByTagNameNS(aElement, aNS, aTagName);

      // For each element
      for (int i = 0; i < theChildren.size(); i++) {
        EPPCodecComponent currComp = (EPPCodecComponent) aClass.getDeclaredConstructor().newInstance();

        currComp.decode((Element) theChildren.elementAt(i));

        retVal.addElement(currComp);
      }

    }
    catch (Exception e) {
      throw new EPPDecodeException("EPPUtil.decodeCompVector(), Exception: " + e);
    }

    return retVal;
  }

  /**
   * Decode {@code Date}, by XML namespace and tag name, from an XML Element.
   * The children elements of {@code aElement} will be searched for the
   * specified {@code aNS} namespace URI and the specified {@code aTagName}.
   * The first XML element found will be decoded and returned. If no element is
   * found, {@code null} is returned. The format used for decoding the date is
   * defined by the constant {@code EPPUtil.DATE_FORMAT}.
   * 
   * @param aElement
   *           XML Element to scan. For example, the element could be
   *           &lt;trans-id&gt;
   * @param aNS
   *           XML namespace of the elements. For example, for domain element
   *           this is "urn:iana:xmlns:epp".
   * @param aTagName
   *           Tag name of the element including an optional namespace prefix.
   *           For example, the tag name for the transaction Id date is "date".
   * @return {@code Date} value if found; {@code null} otherwise.
   * @exception EPPDecodeException
   *               Error decoding {@code aElement}.
   */
  public static Date decodeDate(Element aElement, String aNS, String aTagName) throws EPPDecodeException {
    Date retVal = null;

    Element theElm = EPPUtil.getElementByTagNameNS(aElement, aNS, aTagName);

    if (theElm != null) {
      retVal = EPPUtil.decodeDate(theElm.getFirstChild().getNodeValue());
    }

    return retVal;
  }

  /**
   * Decode an XML Schema date data type (YYYY-MM-DD) to a Java Date object.
   * 
   * @param aDateValue
   *           XML Schema date data type string (YYYY-MM-DD).
   * @return Java Date object.
   */
  public static Date decodeDate(String aDateValue) {

    SimpleDateFormat formatter = new SimpleDateFormat(DATE_FORMAT);

    // Set to UTC with no time element
    Calendar theCal = Calendar.getInstance(TimeZone.getTimeZone("UTC"));
    theCal.set(Calendar.HOUR_OF_DAY, 0);
    theCal.set(Calendar.MINUTE, 0);
    theCal.set(Calendar.SECOND, 0);
    theCal.set(Calendar.MILLISECOND, 0);
    formatter.setCalendar(theCal);

    Date theDate = formatter.parse(aDateValue, new ParsePosition(0));

    return theDate;
  }

  /**
   * Decode {@code Integer} from {@code aElement} parameter.  
   * 
   * @param aElement
   *           XML Element to scan for the {@code Integer} value.
   * @return {@code Integer} value if found; {@code null} otherwise.
   * @exception EPPDecodeException
   *               Error decoding {@code aElement}.
   */
  public static Integer decodeInteger(Element aElement) throws EPPDecodeException {
    Integer retInteger = null;
    if (aElement != null) {
      Node textNode = aElement.getFirstChild();

      // Element does have a text node?
      if (textNode != null) {

        String intValStr = textNode.getNodeValue();
        try {
          retInteger = Integer.valueOf(intValStr);
        }
        catch (NumberFormatException e) {

          throw new EPPDecodeException("Can't convert value to Integer: " + intValStr + e);
        }
      }
      else {
        throw new EPPDecodeException("Can't decode numeric value from non-existant text node");
      }
    }
    return retInteger;
  }

  /**
   * Decode {@code Integer}, by XML namespace and tag name, from an XML
   * Element. The children elements of {@code aElement} will be searched for
   * the specified {@code aNS} namespace URI and the specified
   * {@code aTagName}. The first XML element found will be decoded and
   * returned. If no element is found, {@code null} is returned.
   * 
   * @param aElement
   *           XML Element to scan. For example, the element could be
   *           &lt;domain:create&gt;
   * @param aNS
   *           XML namespace of the elements. For example, for domain element
   *           this is "urn:iana:xmlns:domain".
   * @param aTagName
   *           Tag name of the element including an optional namespace prefix.
   *           For example, the tag name for the domain name is "domain:name".
   * @return {@code Integer} value if found; {@code null} otherwise.
   * @exception EPPDecodeException
   *               Error decoding {@code aElement}.
   */
  public static Integer decodeInteger(Element aElement, String aNS, String aTagName) throws EPPDecodeException {

    Element theElm = EPPUtil.getElementByTagNameNS(aElement, aNS, aTagName);
    
    return decodeInteger(theElm);
  }

  /**
   * Decode a {@code List} of {@code Enum}'s by XML namespace and tag name,
   * from an XML Element. The children elements of {@code aElement} will be
   * searched for the specified {@code aNS} namespace URI and the specified
   * {@code aTagName}. Each XML element found will be decoded and added to the
   * returned {@code List} of {@code Enum} values using the
   * {@link Enum#valueOf(Class, String)} method.
   * 
   * @param aElement
   *           XML Element to scan. For example, the element could be
   *           &lt;domain:update&gt;
   * @param aNS
   *           XML namespace of the elements. For example, for domain element
   *           this is "urn:iana:xmlns:domain".
   * @param aTagName
   *           Tag name of the element including an optional namespace prefix.
   *           For example, the tag name for the domain name servers is
   *           "domain:server".
   * @param aEnumType
   *           Enum type associated with tag.
   * @return {@code List} of {@code Enum} elements that match the
   *         {@code aEnumType} representing matching the XML element values.
   * @exception EPPDecodeException
   *               Error decoding {@code aElement}.
   */
  public static List decodeEnumList(Element aElement, String aNS, String aTagName, Class aEnumType)
        throws EPPDecodeException {
    List retVal = new ArrayList();

    Vector theChildren = EPPUtil.getElementsByTagNameNS(aElement, aNS, aTagName);

    for (int i = 0; i < theChildren.size(); i++) {
      Element currChild = (Element) theChildren.elementAt(i);

      Text currVal = (Text) currChild.getFirstChild();

      // Element has text?
      if (currVal != null) {
        try {
          retVal.add(Enum.valueOf(aEnumType, currVal.getNodeValue()));
        }
        catch (Exception ex) {
          throw new EPPDecodeException("Error converting element value " + currVal.getNodeValue() + " to the Enum "
                + aEnumType.getName());
        }
      }
      else { // No text in element.
        throw new EPPDecodeException("Empty element with tag name " + aTagName + " cannot be converted to the Enum "
              + aEnumType.getName());
      }
    }

    return retVal;
  }

  /**
   * Decode a {@code List} of {@code String}'s by XML namespace and tag name,
   * from an XML Element. The children elements of {@code aElement} will be
   * searched for the specified {@code aNS} namespace URI and the specified
   * {@code aTagName}. Each XML element found will be decoded and added to the
   * returned {@code List}. Empty child elements, will result in empty string
   * ("") return {@code List} elements.
   * 
   * @param aElement
   *           XML Element to scan. For example, the element could be
   *           &lt;domain:update&gt;
   * @param aNS
   *           XML namespace of the elements. For example, for domain element
   *           this is "urn:iana:xmlns:domain".
   * @param aTagName
   *           Tag name of the element including an optional namespace prefix.
   *           For example, the tag name for the domain name servers is
   *           "domain:server".
   * @return {@code List} of {@code String} elements representing the text
   *         nodes of the found XML elements.
   * @exception EPPDecodeException
   *               Error decoding {@code aElement}.
   */
  public static List decodeList(Element aElement, String aNS, String aTagName) throws EPPDecodeException {
    List retVal = new ArrayList();

    Vector theChildren = EPPUtil.getElementsByTagNameNS(aElement, aNS, aTagName);

    for (int i = 0; i < theChildren.size(); i++) {
      Element currChild = (Element) theChildren.elementAt(i);

      Text currVal = (Text) currChild.getFirstChild();

      // Element has text?
      if (currVal != null) {
        retVal.add(currVal.getNodeValue());
      }
      else { // No text in element.
        retVal.add("");
      }
    }

    return retVal;
  }

  /**
   * Decode {@code String}, by XML namespace and tag name, from an XML Element.
   * The children elements of {@code aElement} will be searched for the
   * specified {@code aNS} namespace URI and the specified {@code aTagName}.
   * The first XML element found will be decoded and returned. If no element is
   * found, {@code null} is returned.
   * 
   * @param aElement
   *           XML Element to scan. For example, the element could be
   *           &lt;domain:create&gt;
   * @param aNS
   *           XML namespace of the elements. For example, for domain element
   *           this is "urn:iana:xmlns:domain".
   * @param aTagName
   *           Tag name of the element including an optional namespace prefix.
   *           For example, the tag name for the domain name is "domain:name".
   * @return {@code String} value if found; {@code null} otherwise.
   * @exception EPPDecodeException
   *               Error decoding {@code aElement}.
   */
  public static String decodeString(Element aElement, String aNS, String aTagName) throws EPPDecodeException {
    String retVal = null;

    Element theElm = EPPUtil.getElementByTagNameNS(aElement, aNS, aTagName);

    if (theElm != null) {
      Node textNode = theElm.getFirstChild();

      // Element does have a text node?
      if (textNode != null) {
        retVal = textNode.getNodeValue();
      }
      else {
        retVal = "";
      }
    }

    return retVal;
  }

  /**
   * Decode {@code String} value from an XML Element.
   * 
   * @param aElement
   *           XML Element to get the {@code String} value.
   * @return {@code String} value if set; &quot;&quot; other.
   */
  public static String decodeStringValue(Element aElement) {
    String retVal = null;

    Node textNode = aElement.getFirstChild();

    // Element does have a text node?
    if (textNode != null) {
      retVal = textNode.getNodeValue();
    }
    else {
      retVal = "";
    }

    return retVal;
  }

  /**
   * Decode {@code Date}, as date and time, by XML namespace and tag name, from
   * an XML Element. The children elements of {@code aElement} will be searched
   * for the specified {@code aNS} namespace URI and the specified
   * {@code aTagName}. The first XML element found will be decoded and
   * returned. If no element is found, {@code null} is returned. The format
   * used for decoding the date is defined by the constant
   * {@code EPPUtil.TIME_INSTANT_FORMAT}.
   * 
   * @param aElement
   *           XML Element to scan. For example, the element could be
   *           &lt;trans-id&gt;
   * @param aNS
   *           XML namespace of the elements. For example, for domain element
   *           this is "urn:iana:xmlns:epp".
   * @param aTagName
   *           Tag name of the element including an optional namespace prefix.
   *           For example, the tag name for the transaction Id date is "date".
   * @return {@code Date} value as date and time if found; {@code null}
   *         otherwise.
   * @exception EPPDecodeException
   *               Error decoding {@code aElement}.
   */
  public static Date decodeTimeInstant(Element aElement, String aNS, String aTagName) throws EPPDecodeException {
    Date retVal = null;

    Element theElm = EPPUtil.getElementByTagNameNS(aElement, aNS, aTagName);

    if (theElm != null) {
      retVal = EPPUtil.decodeTimeInstant(theElm.getFirstChild().getNodeValue());
    }

    return retVal;
  }

  /**
   * Decode an XML Schema timeInstant data type to a Java Date object.
   * 
   * @param aTimeInstant
   *           XML Schema timeInstant data type string.
   * @return Java Date object if {@code aTimeInstant} format is valid;
   *         {@code null} otherwise
   */
  public static Date decodeTimeInstant(String aTimeInstant) {
    Date theDate = null;

    try {
      theDate = DatatypeConverter.parseDateTime(aTimeInstant).getTime();
    }
    catch (IllegalArgumentException ex) {
      cat.error("Exception decoding dateTime \"" + aTimeInstant + "\": " + ex);
      theDate = null;
    }

    return theDate;
  }

  /**
   * Decode a {@code Vector} of {@code String}'s by XML namespace and tag name,
   * from an XML Element. The children elements of {@code aElement} will be
   * searched for the specified {@code aNS} namespace URI and the specified
   * {@code aTagName}. Each XML element found will be decoded and added to the
   * returned {@code Vector}. Empty child elements, will result in empty string
   * ("") return {@code Vector} elements.
   * 
   * @param aElement
   *           XML Element to scan. For example, the element could be
   *           &lt;domain:update&gt;
   * @param aNS
   *           XML namespace of the elements. For example, for domain element
   *           this is "urn:iana:xmlns:domain".
   * @param aTagName
   *           Tag name of the element including an optional namespace prefix.
   *           For example, the tag name for the domain name servers is
   *           "domain:server".
   * @return {@code Vector} of {@code String} elements representing the text
   *         nodes of the found XML elements.
   * @exception EPPDecodeException
   *               Error decoding {@code aElement}.
   */
  public static Vector decodeVector(Element aElement, String aNS, String aTagName) throws EPPDecodeException {
    Vector retVal = new Vector();

    Vector theChildren = EPPUtil.getElementsByTagNameNS(aElement, aNS, aTagName);

    for (int i = 0; i < theChildren.size(); i++) {
      Element currChild = (Element) theChildren.elementAt(i);

      Text currVal = (Text) currChild.getFirstChild();

      // Element has text?
      if (currVal != null) {
        retVal.addElement(currVal.getNodeValue());
      }
      else { // No text in element.
        retVal.addElement("");
      }
    }

    return retVal;
  }

  /**
   * Encode a {@code Double} in XML with a given XML namespace and tag name.
   * Takes an optional argument for the format of the Double, if not provided
   * then it uses #0.00 which formats the double to 2 decimal places.
   * 
   * @param aDocument
   *           DOM Document of {@code aRoot}. This parameter also acts as a
   *           factory for XML elements.
   * @param aRoot
   *           XML Element to add children nodes to. For example, the root node
   *           could be &lt;domain:update&gt;
   * @param aBigDecimal
   *           {@code Double} to add.
   * @param aNS
   *           XML namespace of the element. For example, for domain element
   *           this is "urn:iana:xmlns:domain".
   * @param aTagName
   *           Tag name of the element including an optional namespace prefix.
   *           For example, the tag name for the domain name is "domain:name".
   * @param aFormat
   *           Format to use following the {@link DecimalFormat} format. If
   *           {@code null} is passed, the default format of &quot;#0.00&quot;
   *           is used.
   * @exception EPPEncodeException
   *               Error encoding the {@code Double}.
   */
  public static void encodeBigDecimal(Document aDocument, Element aRoot, BigDecimal aBigDecimal, String aNS,
        String aTagName, String aFormat) throws EPPEncodeException {

    if (aBigDecimal != null) {

      String format = aFormat != null ? aFormat : "#0.00";
      DecimalFormat decFormat = new DecimalFormat(format, decimalFormatSymbols);

      String numberStr = decFormat.format(aBigDecimal);
      EPPUtil.encodeString(aDocument, aRoot, numberStr, aNS, aTagName);
    }
  }

  /**
   * Encode a {@code Double} in XML with a given XML namespace and tag name.
   * Takes an optional argument for the format of the Double, if not provided
   * then it uses #0.00 which formats the double to 2 decimal places.
   * 
   * @param aDocument
   *           DOM Document of {@code aRoot}. This parameter also acts as a
   *           factory for XML elements.
   * @param aElement
   *           XML Element to add the text node encoded with the
   *           {@code BigDecimal} value.
   * @param aBigDecimal
   *           {@code Double} to encode into the text node of the element.
   * @param aFormat
   *           Format to use following the {@link DecimalFormat} format. If
   *           {@code null} is passed, the default format of &quot;#0.00&quot;
   *           is used.
   * @exception EPPEncodeException
   *               Error encoding the {@code Double}.
   */
  public static void encodeBigDecimal(Document aDocument, Element aElement, BigDecimal aBigDecimal, String aFormat)
        throws EPPEncodeException {

    if (aBigDecimal != null) {

      String format = aFormat != null ? aFormat : "#0.00";
      DecimalFormat decFormat = new DecimalFormat(format, decimalFormatSymbols);

      String numberStr = decFormat.format(aBigDecimal);

      // Add text node
      Text currVal = aDocument.createTextNode(numberStr);
      aElement.appendChild(currVal);
    }
  }

  /**
   * Encode a {@code Boolean} in XML with a given XML namespace and tag name.
   * If aBoolean is {@code null} an element is not added.
   * 
   * @param aDocument
   *           DOM Document of {@code aRoot}. This parameter also acts as a
   *           factory for XML elements.
   * @param aRoot
   *           XML Element to add children nodes to. For example, the root node
   *           could be &lt;domain:update&gt;
   * @param aBoolean
   *           {@code Boolean} to add.
   * @param aNS
   *           XML namespace of the element. For example, for domain element
   *           this is "urn:iana:xmlns:domain".
   * @param aTagName
   *           Tag name of the element including an optional namespace prefix.
   *           For example, the tag name for the domain name is "domain:name".
   * @exception EPPEncodeException
   *               Error encoding the {@code Boolean}.
   */
  public static void encodeBoolean(Document aDocument, Element aRoot, Boolean aBoolean, String aNS, String aTagName)
        throws EPPEncodeException {
    if (aBoolean != null) {
      Element currElm = aDocument.createElementNS(aNS, aTagName);

      String xmlValue;

      if (aBoolean.booleanValue()) {
        xmlValue = "1";
      }
      else {
        xmlValue = "0";
      }

      Text currVal = aDocument.createTextNode(xmlValue);

      currElm.appendChild(currVal);
      aRoot.appendChild(currElm);
    }
  }

  /**
   * Encodes a boolean {@code Element} attribute value. The attribute is set to
   * &quot;1&quot; with a value of {@code true} and is set to &quot;0&quot;
   * with a value of {@code false}.
   * 
   * @param aElement
   *           Element to add attribute to
   * @param aAttr
   *           Attribute name
   * @param aVal
   *           Attribute boolean value
   */
  public static void encodeBooleanAttr(Element aElement, String aAttr, boolean aVal) {
    if (aVal) {
      aElement.setAttribute(aAttr, "1");
    }
    else {
      aElement.setAttribute(aAttr, "0");
    }
  }

  /**
   * Encode a {@code EPPCodecComponent} instance in XML. The component is first
   * checked for not being {@code null}, than it will be appended as a child of
   * {@code aRoot}.
   * 
   * @param aDocument
   *           DOM Document of {@code aRoot}. This parameter also acts as a
   *           factory for XML elements.
   * @param aRoot
   *           XML Element to add to. For example, the root node could be
   *           &lt;domain:update&gt;
   * @param aComp
   *           {@code EPPCodecComponent's} to add.
   * @exception EPPEncodeException
   *               Error encoding {@code EPPCodecComponent}.
   */
  public static void encodeComp(Document aDocument, Element aRoot, EPPCodecComponent aComp) throws EPPEncodeException {
    if (aComp != null) {
      aRoot.appendChild(aComp.encode(aDocument));
    }
  }

  /**
   * Encode a {@code List} of {@code EPPCodecComponent}'s in XML. Each
   * {@code aList} element will be encoded and added to {@code aRoot}.
   * 
   * @param aDocument
   *           DOM Document of {@code aRoot}. This parameter also acts as a
   *           factory for XML elements.
   * @param aRoot
   *           XML Element to add children nodes to. For example, the root node
   *           could be &lt;domain:update&gt;
   * @param aList
   *           {@code List} of {@code EPPCodecComponent's} to add.
   * @exception EPPEncodeException
   *               Error encoding the {@code List} of
   *               {@code EPPCodecComponent}'s.
   */
  public static void encodeCompList(Document aDocument, Element aRoot, List aList) throws EPPEncodeException {
    if (aList != null) {
      Iterator elms = aList.iterator();

      while (elms.hasNext()) {
        EPPCodecComponent currComp = (EPPCodecComponent) elms.next();

        aRoot.appendChild(currComp.encode(aDocument));
      }

    }
  }

  /**
   * Encode a {@code Vector} of {@code EPPCodecComponent}'s in XML. Each
   * {@code aVector} element will be encoded and added to {@code aRoot}.
   * 
   * @param aDocument
   *           DOM Document of {@code aRoot}. This parameter also acts as a
   *           factory for XML elements.
   * @param aRoot
   *           XML Element to add children nodes to. For example, the root node
   *           could be &lt;domain:update&gt;
   * @param aVector
   *           {@code Vector} of {@code EPPCodecComponent's} to add.
   * @exception EPPEncodeException
   *               Error encoding the {@code Vector} of
   *               {@code EPPCodecComponent}'s.
   */
  public static void encodeCompVector(Document aDocument, Element aRoot, Vector aVector) throws EPPEncodeException {
    if (aVector != null) {
      Enumeration elms = aVector.elements();

      while (elms.hasMoreElements()) {
        EPPCodecComponent currComp = (EPPCodecComponent) elms.nextElement();

        aRoot.appendChild(currComp.encode(aDocument));
      }

    }
  }

  /**
   * Encode a Java Date into an XML Schema date data type string.
   * 
   * @param aDate
   *           Java Date to encode into a XML Schema date data type string.
   * @return Encoded XML Schema date data type string.
   */
  public static String encodeDate(Date aDate) {
    TimeZone utcTimeZone = TimeZone.getTimeZone("UTC");
    SimpleDateFormat formatter = new SimpleDateFormat(DATE_FORMAT);
    formatter.setTimeZone(utcTimeZone);

    return formatter.format(aDate);
  }

  /**
   * Encode a {@code Date} in XML with a given XML namespace and tag name. The
   * format used for encoding the date is defined by the constant
   * {@code EPPUtil.DATE_FORMAT}.
   * 
   * @param aDocument
   *           DOM Document of {@code aRoot}. This parameter also acts as a
   *           factory for XML elements.
   * @param aRoot
   *           XML Element to add children nodes to. For example, the root node
   *           could be &lt;domain:update&gt;
   * @param aDate
   *           {@code Date} to add.
   * @param aNS
   *           XML namespace of the element. For example, for domain element
   *           this is "urn:iana:xmlns:domain".
   * @param aTagName
   *           Tag name of the element including an optional namespace prefix.
   *           For example, the tag name for the domain name is "domain:name".
   * @exception EPPEncodeException
   *               Error encoding {@code Date}.
   */
  public static void encodeDate(Document aDocument, Element aRoot, Date aDate, String aNS, String aTagName)
        throws EPPEncodeException {
    if (aDate != null) {
      encodeString(aDocument, aRoot, EPPUtil.encodeDate(aDate), aNS, aTagName);
    }
  }

  /**
   * Encode a {@code List} of {@code String}'s in XML with a given XML
   * namespace and tag name.
   * 
   * @param aDocument
   *           DOM Document of {@code aRoot}. This parameter also acts as a
   *           factory for XML elements.
   * @param aRoot
   *           XML Element to add children nodes to. For example, the root node
   *           could be &lt;domain:update&gt;
   * @param aList
   *           {@code List} of {@code String's} to add.
   * @param aNS
   *           XML namespace of the elements. For example, for domain element
   *           this is "urn:iana:xmlns:domain".
   * @param aTagName
   *           Tag name of the element including an optional namespace prefix.
   *           For example, the tag name for the domain name servers is
   *           "domain:server".
   * @exception EPPEncodeException
   *               Error encoding the {@code List} of {@code String} 's.
   */
  public static void encodeList(Document aDocument, Element aRoot, List aList, String aNS, String aTagName)
        throws EPPEncodeException {
    Element currElm;
    Text currVal;

    if (aList != null) {
      Iterator elms = aList.iterator();

      while (elms.hasNext()) {
        currElm = aDocument.createElementNS(aNS, aTagName);
        currVal = aDocument.createTextNode(elms.next().toString());

        currElm.appendChild(currVal);
        aRoot.appendChild(currElm);
      }

    }

  }

  /**
   * DOCUMENT ME!
   * 
   * @param aDocument
   *           DOCUMENT ME!
   * @param aRoot
   *           DOCUMENT ME!
   * @param aNS
   *           DOCUMENT ME!
   * @param aTagName
   *           DOCUMENT ME!
   * @throws EPPEncodeException
   *            DOCUMENT ME!
   */
  public static void encodeNill(Document aDocument, Element aRoot, String aNS, String aTagName)
        throws EPPEncodeException {
    Element localElement = aDocument.createElementNS(aNS, aTagName);

    localElement.setAttribute("xsi:nil", "true");
    aRoot.appendChild(localElement);
  }

  /**
   * Encode a {@code String} in XML with a given XML namespace and tag name.
   * 
   * @param aDocument
   *           DOM Document of {@code aRoot}. This parameter also acts as a
   *           factory for XML elements.
   * @param aRoot
   *           XML Element to add children nodes to. For example, the root node
   *           could be &lt;domain:update&gt;
   * @param aString
   *           {@code String} to add.
   * @param aNS
   *           XML namespace of the element. For example, for domain element
   *           this is "urn:iana:xmlns:domain".
   * @param aTagName
   *           Tag name of the element including an optional namespace prefix.
   *           For example, the tag name for the domain name is "domain:name".
   * @exception EPPEncodeException
   *               Error encoding the {@code String}.
   */
  public static void encodeString(Document aDocument, Element aRoot, String aString, String aNS, String aTagName)
        throws EPPEncodeException {
    if (aString != null) {
      Element currElm = aDocument.createElementNS(aNS, aTagName);
      Text currVal = aDocument.createTextNode(aString);

      currElm.appendChild(currVal);
      aRoot.appendChild(currElm);
    }
  }

  /**
   * Encode a {@code Integer} in XML with a given XML namespace and tag name.
   * If the {@code Integer} is {@code null} nothing will be encoded.
   * 
   * @param aDocument
   *           DOM Document of {@code aRoot}. This parameter also acts as a
   *           factory for XML elements.
   * @param aRoot
   *           XML Element to add children nodes to. For example, the root node
   *           could be &lt;domain:update&gt;
   * @param aInteger
   *           {@code Integer} to add.
   * @param aNS
   *           XML namespace of the element. For example, for domain element
   *           this is "urn:iana:xmlns:domain".
   * @param aTagName
   *           Tag name of the element including an optional namespace prefix.
   *           For example, the tag name for the domain name is "domain:name".
   * @exception EPPEncodeException
   *               Error encoding the {@code Integer}.
   */
  public static void encodeInteger(Document aDocument, Element aRoot, Integer aInteger, String aNS, String aTagName)
        throws EPPEncodeException {
    if (aInteger != null) {
      EPPUtil.encodeString(aDocument, aRoot, aInteger.toString(), aNS, aTagName);

    }
  }

  public static void encodeStringList(Document aDocument, Element aRoot, List aList, String aNS, String aTagName) {
    String aString;
    Element aElement;
    Text aValue;

    if (aList != null) {
      Iterator elms = aList.iterator();

      while (elms.hasNext()) {
        aString = (String) elms.next();
        aElement = aDocument.createElementNS(aNS, aTagName);
        aValue = aDocument.createTextNode(aString);
        aElement.appendChild(aValue);
        aRoot.appendChild(aElement);
      }
    }
  }

  /**
   * Encode a Java Date into an XML Schema timeInstant data type string. The
   * XML Schema timeInstant data type string has the following format:
   * CCYY-MM-DDThh:mm:ss.ssss can be followed by a Z to indicate Coordinated
   * Universal Time
   * 
   * @param aDate
   *           Java Date to encode into a XML Schema timeInstant data type
   *           string.
   * @return Encoded XML Schema timeInstant data type string.
   */
  public static String encodeTimeInstant(Date aDate) {
    TimeZone utcTimeZone = TimeZone.getTimeZone("UTC");
    SimpleDateFormat formatter = new SimpleDateFormat(timeInstantFormat);
    formatter.setTimeZone(utcTimeZone);

    return formatter.format(aDate);
  }

  /**
   * Encode a {@code Date}, as date and time, in XML with a given XML namespace
   * and tag name. The format used for encoding the date is defined by the
   * constant {@code EPPUtil.TIME_INSTANT_FORMAT}.
   * 
   * @param aDocument
   *           DOM Document of {@code aRoot}. This parameter also acts as a
   *           factory for XML elements.
   * @param aRoot
   *           XML Element to add children nodes to. For example, the root node
   *           could be &lt;domain:expire-data&gt;
   * @param aDate
   *           {@code Date} as date and time to add.
   * @param aNS
   *           XML namespace of the element. For example, for domain element
   *           this is "urn:iana:xmlns:domain".
   * @param aTagName
   *           Tag name of the element including an optional namespace prefix.
   *           For example, the tag name for the domain expiration date and
   *           time is "domain:expiration-date".
   * @exception EPPEncodeException
   *               Error encoding {@code Date}.
   */
  public static void encodeTimeInstant(Document aDocument, Element aRoot, Date aDate, String aNS, String aTagName)
        throws EPPEncodeException {
    if (aDate != null) {
      encodeString(aDocument, aRoot, EPPUtil.encodeTimeInstant(aDate), aNS, aTagName);
    }
  }

  /**
   * Encode a {@code Vector} of {@code String}'s in XML with a given XML
   * namespace and tag name.
   * 
   * @param aDocument
   *           DOM Document of {@code aRoot}. This parameter also acts as a
   *           factory for XML elements.
   * @param aRoot
   *           XML Element to add children nodes to. For example, the root node
   *           could be &lt;domain:update&gt;
   * @param aVector
   *           {@code Vector} of {@code String's} to add.
   * @param aNS
   *           XML namespace of the elements. For example, for domain element
   *           this is "urn:iana:xmlns:domain".
   * @param aTagName
   *           Tag name of the element including an optional namespace prefix.
   *           For example, the tag name for the domain name servers is
   *           "domain:server".
   * @exception EPPEncodeException
   *               Error encoding the {@code Vector} of {@code String}'s.
   */
  public static void encodeVector(Document aDocument, Element aRoot, Vector aVector, String aNS, String aTagName)
        throws EPPEncodeException {
    Element currElm;
    Text currVal;

    if (aVector != null) {
      Enumeration elms = aVector.elements();

      while (elms.hasMoreElements()) {
        currElm = aDocument.createElementNS(aNS, aTagName);
        currVal = aDocument.createTextNode(elms.nextElement().toString());

        currElm.appendChild(currVal);
        aRoot.appendChild(currElm);
      }

    }

  }

  /**
   * compares two {@code List} instances. The Java 1.1 {@code List} class does
   * not implement the {@code equals} methods, so {@code equalLists} was
   * written to implement {@code equals} of two {@code Lists} (aV1 and aV2).
   * This method is not need for Java 2.
   * 
   * @param aV1
   *           First List instance to compare.
   * @param aV2
   *           Second List instance to compare.
   * @return {@code true} if List's are equal; {@code false} otherwise.
   */
  public static boolean equalLists(List aV1, List aV2) {
    if ((aV1 == null) && (aV2 == null)) {
      return true;
    }

    else if ((aV1 != null) && (aV2 != null)) {
      if (aV1.size() != aV2.size()) {
        return false;
      }

      Iterator v1Iter = aV1.iterator();
      Iterator v2Iter = aV2.iterator();

      for (int i = 0; i < aV1.size(); i++) {
        Object elm1 = v1Iter.next();
        Object elm2 = v2Iter.next();

        if (!((elm1 == null) ? (elm2 == null) : elm1.equals(elm2))) {
          return false;
        }
      }

      return true;
    }
    else {
      return false;
    }
  }

  /**
   * compares two {@code Vector} instances. The Java 1.1 {@code Vector} class
   * does not implement the {@code equals} methods, so {@code equalVectors} was
   * written to implement {@code equals} of two {@code Vectors} (aV1 and aV2).
   * This method is not need for Java 2.
   * 
   * @param aV1
   *           First Vector instance to compare.
   * @param aV2
   *           Second Vector instance to compare.
   * @return {@code true} if Vector's are equal; {@code false} otherwise.
   */
  public static boolean equalVectors(Vector aV1, Vector aV2) {
    if ((aV1 == null) && (aV2 == null)) {
      return true;
    }

    else if ((aV1 != null) && (aV2 != null)) {
      if (aV1.size() != aV2.size()) {
        return false;
      }

      for (int i = 0; i < aV1.size(); i++) {
        Object elm1 = aV1.elementAt(i);
        Object elm2 = aV2.elementAt(i);

        if (!((elm1 == null) ? (elm2 == null) : elm1.equals(elm2))) {
          return false;
        }
      }

      return true;
    }
    else {
      return false;
    }
  }

  /**
   * Gets the first direct child element with a given tag name and XML
   * namespace.
   * 
   * @param aElement
   *           Root element to start from to search for the element
   * @param aNS
   *           XML namespace of the elements. For example, for domain element
   *           this is "urn:iana:xmlns:domain".
   * @param aTagName
   *           Tag name to scan for.
   * @return Matching {@code Element} if found; {@code null} otherwise.
   */
  public static Element getElementByTagNameNS(Element aElement, String aNS, String aTagName) {

    Element retElm = null;

    aTagName = EPPUtil.getLocalName(aTagName);

    NodeList theNodes = aElement.getChildNodes();

    // Found matching nodes?
    if (theNodes != null) {

      for (int i = 0; (i < theNodes.getLength()) && retElm == null; i++) {
        if (theNodes.item(i).getNodeType() == Node.ELEMENT_NODE) {
          Element currElm = (Element) theNodes.item(i);

          if (currElm.getNamespaceURI().equals(aNS) && currElm.getLocalName().equals(aTagName)) {
            retElm = currElm;
          }
        }
      }
    }

    return retElm;
  }

  /**
   * Gets all of the direct child element with a given tag name and XML
   * namespace.
   * 
   * @param aElement
   *           Root element to start from to search for the element
   * @param aNS
   *           XML namespace of the elements. For example, for domain element
   *           this is "urn:iana:xmlns:domain".
   * @param aTagName
   *           Tag name to scan for.
   * @return {@code Vector} of {@code Node} instances that are elements and
   *         have the specified tag name and XML namespace.
   */
  public static Vector getElementsByTagNameNS(Element aElement, String aNS, String aTagName) {
    Vector retVal = new Vector();

    aTagName = EPPUtil.getLocalName(aTagName);

    NodeList theNodes = aElement.getChildNodes();

    if (theNodes != null) {
      for (int i = 0; i < theNodes.getLength(); i++) {
        if (theNodes.item(i).getNodeType() == Node.ELEMENT_NODE) {
          Element currElm = (Element) theNodes.item(i);

          if (currElm.getNamespaceURI().equals(aNS) && currElm.getLocalName().equals(aTagName)) {
            retVal.add(theNodes.item(i));
          }
        }
      }
    }

    return retVal;
  }

  /**
   * Gets the local name given the qualified element name. If the local name is
   * passed, it will be simply returned back.
   * 
   * @param aQualifiedName
   *           Qualified name of the element like {@code domain:name}. A
   *           localhost like {@code name} can be passed without error.
   * @return Localname of the qualified name
   */
  public static String getLocalName(String aQualifiedName) {
    if (aQualifiedName.indexOf(':') != -1) {
      aQualifiedName = aQualifiedName.substring(aQualifiedName.indexOf(':') + 1);
    }

    return aQualifiedName;
  }

  /**
   * Gets the namespace prefix given a qualified name. If no prefix is found,
   * empty string is returned.
   * 
   * @param aQualifiedName
   *           Qualified name of the element like {@code domain:name}.
   * @return Namespace prefix of the qualified name if found; empty string ("")
   *         if not found.
   */
  public static String getPrefix(String aQualifiedName) {
    String prefix = "";

    if (aQualifiedName.indexOf(':') != -1) {
      prefix = aQualifiedName.substring(0, aQualifiedName.indexOf(':'));
    }

    return prefix;
  }

  /**
   * Gets the first DOM Element Node of the {@code aElement} DOM Element.
   * 
   * @param aElement
   *           Element to scan for the first element.
   * @return Found DOM Element Node if found; {@code null} otherwise.
   */
  public static Element getFirstElementChild(Element aElement) {
    NodeList children = aElement.getChildNodes();

    for (int i = 0; i < children.getLength(); i++) {
      if (children.item(i).getNodeType() == Node.ELEMENT_NODE) {
        return (Element) children.item(i);
      }
    }

    return null;
  }

  /**
   * Gets the next sibling Element of a provided Element.
   * 
   * @param aElement
   *           Element to start scan for the next Element.
   * @return Found DOM Element Node if found; {@code null} otherwise.
   */
  public static Element getNextElementSibling(Element aElement) {
    Element theSibling = null;
    Node theCurrNode = aElement;

    while ((theCurrNode != null) && (theSibling == null)) {
      theCurrNode = theCurrNode.getNextSibling();

      if ((theCurrNode != null) && (theCurrNode.getNodeType() == Node.ELEMENT_NODE)) {
        theSibling = (Element) theCurrNode;
      }
    }

    return theSibling;
  }

  /**
   * A pass-thru to the getTextContent() method using a default value of false
   * for the required flag.
   * 
   * @param node
   *           Node to get the text node from.
   * @throws EPPDecodeException
   *            Error getting text node
   * @return The text node {@code String} value if found; throwing
   *         {@code EPPDecodeException} otherwise.
   */
  static public String getTextContent(Node node) throws EPPDecodeException {
    return getTextContent(node, false);
  }

  /**
   * Return the text data within an XML tag.<br>
   * &lt;id&gt;12345&lt;/id&gt; yields the String "12345"<br>
   * If the node==null, child==null, value==null, or value=="" =&gt;
   * Exception<br>
   * UNLESS allowEmpty, in which case return ""
   * <p>
   * For reference:<br>
   * &lt;tag&gt;&lt;/tag&gt; =&gt; child is null<br>
   * &lt;tag/&gt; =&gt; child is null<br>
   * &lt;tag&gt; &lt;/tag&gt; =&gt; value is empty
   * 
   * @param node
   *           Node to get the text node from.
   * @param allowEmpty
   *           Allow empty text node return value. If {@code true} then if the
   *           text node is not found, the empty string will be returned
   *           instead of throwing the {@code EPPDecodeException}.
   * @throws EPPDecodeException
   *            Error getting text node
   * @return The text node {@code String} value if found; empty string if
   *         {@code allowEmpty} is {@code true} and {@code EPPDecodeException}
   *         if {@code allowEmpty} is {@code false} otherwise.
   * 
   */
  static public String getTextContent(Node node, boolean allowEmpty) throws EPPDecodeException {
    if (node != null) {
      Node child = node.getFirstChild();
      if (child != null) {
        String value = child.getNodeValue();
        if (value != null) {
          value = value.trim();
          if (value.length() > 0) {
            return value;
          }
        }
      }
    }
    if (allowEmpty)
      return "";
    throw new EPPDecodeException("Empty tag encountered during decode");
  }

  /**
   * Determines if one {@code List} is a subset of another {@code List}. If
   * every element in {@code aV1} is in {@code aV2} return {@code true};
   * otherwise return {@code false}.
   * 
   * @param aV1
   *           Subset {@code List} to compare against {@code aV2}
   * @param aV2
   *           Superset {@code List} to compare against {@code aV1}
   * @return {@code true} if {@code aV1} is a subset of {@code aV2};
   *         {@code false} otherwise.
   */
  public static boolean listSubset(List aV1, List aV2) {
    Iterator v1Iter = aV1.iterator();

    while (v1Iter.hasNext()) {
      if (!aV2.contains(v1Iter.next())) {
        return false;
      }
    }

    return true;
  }

  /**
   * Converts an {@code EPPCodecComponent} to a {@code String} for printing.
   * Each {@code EPPCodecComponent} can use this utility method to implement
   * {@code Object.toString()}.
   * 
   * @param aComponent
   *           a concrete {@code EPPCodecComponent} instance
   * @return {@code String} representation of {@code EPPCodecComponent} if
   *         successful; "ERROR" {@code String} otherwise.
   */
  public static String toString(EPPCodecComponent aComponent) {
    String ret = "ERROR";

    // Mask component if required
    EPPCodecComponent theComponent = sendReceiveLogger.maskMessage(aComponent);

    try {
      DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
      DocumentBuilder builder = dbf.newDocumentBuilder();
      Document document = builder.newDocument();
      Element elm = theComponent.encode(document);

      ret = toString(elm);
    }
    catch (Exception ex) {
      ex.printStackTrace();
    }

    return ret;
  }

  /**
   * Converts an {@code aElement} to a {@code String} for printing.
   * 
   * @param aElement
   *           {@code Element} to print
   * @return {@code String} representation of {@code Element} if successful;
   *         "ERROR" {@code String} otherwise.
   */
  public static String toString(Element aElement) {
    String ret = "ERROR";
    ByteArrayOutputStream theBuffer = new ByteArrayOutputStream();

    // Serialize DOM Document to stream
    try {
      TransformerFactory transFac = TransformerFactory.newInstance();
      Transformer trans = transFac.newTransformer();
      trans.setOutputProperty(OutputKeys.INDENT, "yes");
      trans.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "2");
      trans.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
      trans.transform(new DOMSource(aElement), new StreamResult(theBuffer));
      theBuffer.close();
      ret = new String(theBuffer.toByteArray());
    }
    catch (Exception ex) {
      ex.printStackTrace();
    }

    return ret;
  }

  /**
   * Converts an {@code aElement} to a {@code String} for printing without
   * pretty printing.
   * 
   * @param aElement
   *           {@code Element} to print
   * @return {@code String} representation of {@code Element} if successful;
   *         "ERROR" {@code String} otherwise.
   */
  public static String toStringNoIndent(Element aElement) {
    String ret = "ERROR";
    ByteArrayOutputStream theBuffer = new ByteArrayOutputStream();

    // Serialize DOM Document to stream
    try {
      TransformerFactory transFac = TransformerFactory.newInstance();
      Transformer trans = transFac.newTransformer();
      trans.setOutputProperty(OutputKeys.INDENT, "no");
      trans.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
      trans.transform(new DOMSource(aElement), new StreamResult(theBuffer));
      theBuffer.close();
      ret = new String(theBuffer.toByteArray());
    }
    catch (Exception ex) {
      ex.printStackTrace();
    }

    return ret;
  }

  /**
   * Determines if one {@code Vector} is a subset of another {@code Vector}. If
   * every element in {@code aV1} is in {@code aV2} return {@code true};
   * otherwise return {@code false}.
   * 
   * @param aV1
   *           Subset {@code Vector} to compare against {@code aV2}
   * @param aV2
   *           Superset {@code Vector} to compare against {@code aV1}
   * @return {@code true} if {@code aV1} is a subset of {@code aV2};
   *         {@code false} otherwise.
   */
  public static boolean vectorSubset(Vector aV1, Vector aV2) {
    Enumeration v1Enum = aV1.elements();

    while (v1Enum.hasMoreElements()) {
      if (!aV2.contains(v1Enum.nextElement())) {
        return false;
      }
    }

    return true;
  }

  /**
   * Decode a {@code List} of {@code Integer}'s by XML namespace and tag name,
   * from an XML Element. The children elements of {@code aElement} will be
   * searched for the specified {@code aNS} namespace URI and the specified
   * {@code aTagName}. Each XML element found will be decoded and added to the
   * returned {@code List}. Empty child elements, will result in an
   * {@code EPPDecodeException}.
   * 
   * @param aElement
   *           XML Element to scan. For example, the element could be
   *           &lt;domain:update&gt;
   * @param aNS
   *           XML namespace of the elements. For example, for domain element
   *           this is "urn:iana:xmlns:domain".
   * @param aTagName
   *           Tag name of the element including an optional namespace prefix.
   *           For example, the tag name for the domain name servers is
   *           "domain:server".
   * 
   * @return {@code List} of {@code Integer} elements representing the text
   *         nodes of the found XML elements.
   * 
   * @exception EPPDecodeException
   *               Error decoding {@code aElement}.
   */
  public static List decodeIntegerList(Element aElement, String aNS, String aTagName) throws EPPDecodeException {

    List retVal = new ArrayList();

    Vector theChildren = EPPUtil.getElementsByTagNameNS(aElement, aNS, aTagName);

    for (int i = 0; i < theChildren.size(); i++) {
      Element currChild = (Element) theChildren.elementAt(i);

      Integer retInteger = null;

      Node textNode = currChild.getFirstChild();

      // Element does have a text node?
      if (textNode != null) {

        String intValStr = textNode.getNodeValue();
        try {
          retInteger = Integer.valueOf(intValStr);
        }
        catch (NumberFormatException e) {

          throw new EPPDecodeException(
                "EPPUtil.decodeIntegerList Can't convert value to Integer: " + intValStr + e);
        }
      }
      else {
        throw new EPPDecodeException(
              "EPPUtil.decodeIntegerList Can't decode numeric value from non-existant text node");
      }

      retVal.add(retInteger);
    }

    return retVal;
  }

  /**
   * Encode a {@code List} of {@code Integer}'s in XML with a given XML
   * namespace and tag name.
   * 
   * @param aDocument
   *           DOM Document of {@code aRoot}. This parameter also acts as a
   *           factory for XML elements.
   * @param aRoot
   *           XML Element to add children nodes to. For example, the root node
   *           could be &lt;domain:update&gt;
   * @param aList
   *           {@code List} of {@code Integer}'s to add.
   * @param aNS
   *           XML namespace of the elements. For example, for domain element
   *           this is "urn:iana:xmlns:domain".
   * @param aTagName
   *           Tag name of the element including an optional namespace prefix.
   *           For example, the tag name for the domain name servers is
   *           "domain:server".
   * 
   * @exception EPPEncodeException
   *               Error encoding the {@code List} of {@code Integer} 's.
   */
  public static void encodeIntegerList(Document aDocument, Element aRoot, List aList, String aNS, String aTagName)
        throws EPPEncodeException {
    Element currElm;
    Text currVal;

    if (aList != null) {
      Iterator elms = aList.iterator();

      while (elms.hasNext()) {
        currElm = aDocument.createElementNS(aNS, aTagName);
        currVal = aDocument.createTextNode(elms.next().toString());

        currElm.appendChild(currVal);
        aRoot.appendChild(currElm);
      }

    }

  }

  /**
   * Collapse Whitespace, which means that all leading and trailing whitespace
   * will be stripped, and all internal whitespace collapsed to single space
   * characters. Intended to emulate XML Schema whitespace (collapse)
   * constraining facet: http://www.w3.org/TR/xmlschema-2/#rf-whiteSpace
   * 
   * @param inString
   *           {@code String} to be collapsed
   * @return {@code String} with whitespace collapsed, or null when result is
   *         empty
   */
  public static String collapseWhitespace(String inString) {

    if (inString == null) {
      return null;
    }

    String s = inString.trim(); // remove leading and trailing whitespace
    StringBuffer sb = new StringBuffer();

    int len = s.length();
    boolean prevWhitespace = true;
    for (int i = 0; i < len; i++) {
      char c = s.charAt(i);
      if (Character.isWhitespace(c) == false) {
        sb.append(c); // maintain all non-whitespace characters
        prevWhitespace = false;
      }
      else {
        if (prevWhitespace == false) {
          sb.append(' '); // replace each sequence of whitespace with
          // a single space
          prevWhitespace = true;
        }
      }
    }

    if (sb.length() == 0) {
      return null; // return null string when result is empty
    }
    else {
      return sb.toString(); // return collapsed string
    }

  }

  /**
   * Remove Whitespace, which means that all whitespace will be stripped.
   * 
   * @param inString
   *           {@code String} from which to remove whitespace
   * @return {@code String} with whitespace removed, or null when result is
   *         empty
   */
  public static String removeWhitespace(String inString) {

    if (inString == null) {
      return null;
    }

    StringBuffer sb = new StringBuffer();

    int len = inString.length();
    for (int i = 0; i < len; i++) {
      char c = inString.charAt(i);
      if (Character.isWhitespace(c) == false) {
        sb.append(c); // maintain all non-whitespace characters
      }
    }

    if (sb.length() == 0) {
      return null; // return null string when result is empty
    }
    else {
      return sb.toString(); // return result string
    }

  }

  /**
   * Base64 encode the XML {@code Element} tree.
   * 
   * @param aElement
   *           Root element to encode in Base64
   * @return Base64 encoded value of the XML {@code Element} tree.
   * @throws Exception
   *            Error encoding the {@code Element}
   */
  public static String encodeBase64(Element aElement) throws Exception {
    ByteArrayOutputStream ostream = new ByteArrayOutputStream();

    TransformerFactory transFac = TransformerFactory.newInstance();
    Transformer trans = transFac.newTransformer();
    trans.transform(new DOMSource(aElement), new StreamResult(ostream));

    return (new String(Base64.encodeBase64(ostream.toByteArray(), true)));
  }

  /**
   * Find the set of duplicate EPP extensions based on the list of EPP
   * extensions.
   * 
   * @param aExtensions
   *           List of EPP extensions to find duplicates
   * 
   * @return {@link Set} of duplicate EPP extension XML namespaces;
   *         {@code null} otherwise.
   * 
   */
  public static Set<String> findDuplicateExtNamespaces(List<EPPCodecComponent> aExtensions) {
    if (aExtensions == null) {
      return null;
    }

    Set<String> theExtNamespaces = new HashSet<String>();
    Set<String> theDuplicateExtNamespaces = null;

    // Iterate through the list of extensions for duplicates
    for (EPPCodecComponent theExt : aExtensions) {

      // Duplicate extension?
      if (theExtNamespaces.contains(theExt.getNamespace())) {
        // Add duplicate extension to the set of duplicates
        if (theDuplicateExtNamespaces == null) {
          theDuplicateExtNamespaces = new HashSet<String>();
        }
        theDuplicateExtNamespaces.add(theExt.getNamespace());
      }
      else {
        // Add extension to set of extensions
        theExtNamespaces.add(theExt.getNamespace());
      }
    }

    return theDuplicateExtNamespaces;
  }

  /**
   * Find the set of unsupported EPP extensions based on passing the list of
   * supported extension XML namespaces and the list of EPP extensions.
   * 
   * @param aSupportedExtNamespaces
   *           List of supported EPP extension XML namespaces. Pass
   *           {@code null} for no supported extensions.
   * @param aExtensions
   *           List of EPP extensions to scan for unsupported XML namespaces
   * 
   * @return {@link Set} of unsupported EPP extension XML namespaces;
   *         {@code null} otherwise.
   * 
   */
  public static Set<String> findUnsupportedExtNamespaces(List<String> aSupportedExtNamespaces,
        List<EPPCodecComponent> aExtensions) {
    if (aExtensions == null) {
      return null;
    }

    Set<String> theUnsupportedExtNamespaces = null;

    // Iterate through the list of extensions for unsupported extension XML
    // namespaces
    for (EPPCodecComponent theExt : aExtensions) {

      // Unsupported extension?
      if ((aSupportedExtNamespaces == null) || (!aSupportedExtNamespaces.contains(theExt.getNamespace()))) {
        // Add unsupported extension to the set of unsupported namespaces
        if (theUnsupportedExtNamespaces == null) {
          theUnsupportedExtNamespaces = new HashSet<String>();
        }
        theUnsupportedExtNamespaces.add(theExt.getNamespace());
      }
    }

    return theUnsupportedExtNamespaces;
  }

  /**
   * Find the set of unsupported EPP extensions based on passing the list of
   * supported extension XML namespace suffixes and the list of EPP extensions.
   * See {@link #namespaceToNamespaceSuffix(String)} for conversion of an XML
   * namespace to an XML namespace suffix.
   * 
   * @param aSupportedExtNamespaceSuffixes
   *           List of supported EPP extension XML namespace suffixes. Pass
   *           {@code null} for no supported extensions.
   * @param aExtensions
   *           List of EPP extensions to scan for unsupported XML namespace
   *           suffixes
   * 
   * @return {@link Set} of unsupported EPP extension XML namespace suffixes;
   *         {@code null} otherwise.
   * 
   */
  public static Set<String> findUnsupportedExtNamespaceSuffixes(List<String> aSupportedExtNamespaceSuffixes,
        List<EPPCodecComponent> aExtensions) {
    if (aExtensions == null) {
      return null;
    }

    Set<String> theUnsupportedExtNamespaceSuffixes = null;

    // Iterate through the list of extensions for unsupported extension XML
    // namespace suffixes
    for (EPPCodecComponent theExt : aExtensions) {

      String theNamespaceSuffix = namespaceToNamespaceSuffix(theExt.getNamespace());

      // Unsupported extension?
      if ((aSupportedExtNamespaceSuffixes == null)
            || (!aSupportedExtNamespaceSuffixes.contains(theNamespaceSuffix))) {
        // Add unsupported extension to the set of unsupported namespaces
        if (theUnsupportedExtNamespaceSuffixes == null) {
          theUnsupportedExtNamespaceSuffixes = new HashSet<String>();
        }
        theUnsupportedExtNamespaceSuffixes.add(theNamespaceSuffix);
      }
    }

    return theUnsupportedExtNamespaceSuffixes;
  }

  /**
   * Find the set of unique EPP extension XML namespaces based on the list of
   * EPP extensions and the optional use of a list of XML namespaces to filter.
   * 
   * @param aExtensions
   *           List of EPP extension to find unique EPP extension XML
   *           namespaces.
   * @param aFilterExtNamespaces
   *           Filter extension XML namespaces from the returned set. Set to
   *           {@code null} for no filtering.
   * 
   * @return {@link Set} of unique EPP extension XML namespaces.
   * 
   */
  public static Set<String> findExtNamespaces(List<EPPCodecComponent> aExtensions, List<String> aFilterExtNamespaces) {
    Set<String> theExtNamespaces = new HashSet<String>();

    if (aExtensions == null) {
      return theExtNamespaces;
    }

    // Iterate through the list of extensions for unique extension namespaces
    for (EPPCodecComponent theExt : aExtensions) {

      // Unique extension?
      if (!theExtNamespaces.contains(theExt.getNamespace())) {

        if ((aFilterExtNamespaces == null) || (!aFilterExtNamespaces.contains(theExt.getNamespace()))) {
          theExtNamespaces.add(theExt.getNamespace());
        }

      }
    }

    return theExtNamespaces;
  }

  /**
   * Find the set of unique EPP extension XML namespace suffixes based on the
   * list of EPP extensions and the optional use of a list of XML namespace
   * suffixes to filter. An XML namespace suffix is the last element of an XML
   * namespace is "secDNS-1.1" for the XML namespace
   * "urn:ietf:params:xml:ns:secDNS-1.1" or "sync-1.0" for
   * "http://www.verisign.com/epp/sync-1.0". <br>
   * Only two forms of XML namespaces is supported:
   * <ul>
   * <li>IANA XML namespace - Uses ':' as a separator</li>
   * <li>URL namespace - Uses a uRL with "/" for the path separator</li>
   * </ul>
   * <br>
   * Any unsupported XML namespaces will be returned unmodified.
   * 
   * @param aExtensions
   *           List of EPP extensions to find unique EPP extension XML
   *           namespace suffixes.
   * @param aFilterExtNamespaceSuffixes
   *           Filter extension XML namespace suffixes from the returned set.
   *           Set to {@code null} for no filtering.
   * 
   * @return {@link Set} of unique EPP extension XML namespace suffixes.
   * 
   */
  public static Set<String> findExtNamespaceSuffixes(List<EPPCodecComponent> aExtensions,
        List<String> aFilterExtNamespaceSuffixes) {

    Set<String> theExtNamespaceSuffixes = new HashSet<String>();

    if (aExtensions == null) {
      return theExtNamespaceSuffixes;
    }

    Set<String> theExtNamespaces = findExtNamespaces(aExtensions, null);

    // Iterate through the list of extension namespaces
    for (String theExtNamespace : theExtNamespaces) {

      String theExtNamespaceSuffix = namespaceToNamespaceSuffix(theExtNamespace);

      if ((aFilterExtNamespaceSuffixes == null) || (!aFilterExtNamespaceSuffixes.contains(theExtNamespaceSuffix))) {
        theExtNamespaceSuffixes.add(theExtNamespaceSuffix);
      }

    }

    return theExtNamespaceSuffixes;
  }

  /**
   * Convert an XML namespace to an XML namespace suffix. An XML namespace
   * suffix is the last element of an XML namespace is "secDNS-1.1" for the XML
   * namespace "urn:ietf:params:xml:ns:secDNS-1.1" or "sync-1.0" for
   * "http://www.verisign.com/epp/sync-1.0". <br>
   * Only two forms of XML namespaces is supported:
   * <ul>
   * <li>IANA XML namespace - Uses ':' as a separator</li>
   * <li>URL namespace - Uses a uRL with "/" for the path separator</li>
   * </ul>
   * <br>
   * An unsupported XML namespace will be returned unmodified.
   * 
   * @param aNamespace
   *           XML namespace to convert to an XML namespace suffix.
   * 
   * @return XML namespace suffix if format is supported; unmodified XML
   *         namespace otherwise.
   * 
   */
  public static String namespaceToNamespaceSuffix(String aNamespace) {

    String theNamespaceSuffix;

    // URL form of namespace
    if (aNamespace.contains("/")) {
      String[] nsElms = aNamespace.split("/");
      theNamespaceSuffix = nsElms[nsElms.length - 1];
      if (theNamespaceSuffix.isEmpty()) {
        theNamespaceSuffix = aNamespace;
      }
    } // IANA form of namespace
    else if (aNamespace.contains(":")) {
      String[] nsElms = aNamespace.split(":");
      theNamespaceSuffix = nsElms[nsElms.length - 1];
      if (theNamespaceSuffix.isEmpty()) {
        theNamespaceSuffix = aNamespace;
      }
    } // Unsupported form of namespace
    else {
      theNamespaceSuffix = aNamespace;
    }

    return theNamespaceSuffix;
  }
  
  /**
   * Convert an XML namespace to an XML namespace suffix baseename. An XML namespace
   * suffix is the last element of an XML namespace is "secDNS-1.1" for the XML
   * namespace "urn:ietf:params:xml:ns:secDNS-1.1" or "sync-1.0" for
   * "http://www.verisign.com/epp/sync-1.0". The namespace suffix basename 
   * removes the version from the namespace suffix, so "secDNS-1.1" becomes 
   * "secDNS" and "sync-1.0" becomes "sync".<br>
   * Only two forms of XML namespaces is supported:
   * <ul>
   * <li>IANA XML namespace - Uses ':' as a separator</li>
   * <li>URL namespace - Uses a uRL with "/" for the path separator</li>
   * </ul>
   * <br>
   * An unsupported XML namespace or namespace suffix will be returned unmodified.
   * 
   * @param aNamespace
   *           XML namespace to convert to an XML namespace suffix basename.
   * 
   * @return XML namespace suffix basename if format is supported; unmodified XML
   *         namespace otherwise.
   * 
   */
  public static String namespaceToNamespaceSuffixBasename(String aNamespace) {
	  
	  String theNamespaceSuffix = namespaceToNamespaceSuffix(aNamespace);
		if (theNamespaceSuffix.contains("-")) {
			String[] theElms = theNamespaceSuffix.split("-");
			if (theElms.length == 2) {
				theNamespaceSuffix = theElms[0];
			}
		}
		
	    return theNamespaceSuffix;			
	}
}
