/***********************************************************
Copyright (C) 2018 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-0107  USA

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

import java.util.ArrayList;
import java.util.List;

import org.apache.log4j.Logger;
import org.w3c.dom.Document;
import org.w3c.dom.Element;

import com.verisign.epp.codec.gen.EPPCodecComponent;
import com.verisign.epp.codec.gen.EPPCodecException;
import com.verisign.epp.codec.gen.EPPDecodeException;
import com.verisign.epp.codec.gen.EPPEncodeException;
import com.verisign.epp.codec.gen.EPPUtil;
import com.verisign.epp.codec.registry.v02.EPPRegistryContact.SharePolicy;
import com.verisign.epp.util.EPPCatFactory;
import com.verisign.epp.util.EqualityUtil;

/**
 * Represents the domain name object policy information per RFC 5731. Instance
 * of this class is encoded into the &lt;registry:domain&gt; element in the
 * &lt;registry:zone&gt; element when the server responds with the detailed
 * information of the zone object. The &lt;registry:domain&gt; must contain the
 * following child elements:<br>
 * <br>
 *
 * <ul>
 * <li>&lt;registry:domainName&gt; - The domain name object policy information
 * per RFC 5731. Use {@link #getDomainNames()} and {@link #setDomainNames(List)}
 * to get and set the element.</li>
 *
 * <li>&lt;registry:idn&gt; - OPTIONAL Internationalized Domain Name (IDN)
 * policy information. Use {@link #getIdn()} and {@link #setIdn(EPPRegistryIDN)}
 * to get and set the element.</li>
 *
 * <li>&lt;registry:premiumSupport&gt; - OPTIONAL boolean value that indicates
 * whether the server supports premium domain names. Default value is
 * {@code false}. Use {@link #getPremiumSupport()} and
 * {@link #setPremiumSupport(Boolean)} to get and set the element.</li>
 *
 * <li>&lt;registry:contactsSupported&gt; - OPTIONAL boolean value that
 * indicates whether contacts are supported. Default value is {@code true}. Use
 * {@link #getContactsSupported()} and {@link #setContactsSupported(Boolean)} to
 * get and set the element.</li>
 *
 * <li>&lt;registry:contact&gt; - Zero to three elements that define the minimum
 * and maximum numbers of contacts by contact type. Valid contact types are:
 * admin, tech and billing. Use {@link #getContacts()} and
 * {@link #setContacts(List)} to get and set the element. Use
 * {@link #addContact(EPPRegistryDomainContact)} to append a contact to the
 * existing contact list.</li>
 *
 * <li>&lt;registry:ns&gt; - Defines the minimum and maximum number of delegated
 * host objects (name servers) that can be associated with a domain object. Use
 * {@link #getNameServerLimit()} and
 * {@link #setNameServerLimit(EPPRegistryDomainNSLimit)} to get and set the
 * element.</li>
 *
 * <li>&lt;registry:childHost&gt; - Defines the OPTIONAL minimum and maximum
 * number of subordinate host objects (child hosts) for a domain object. Use
 * {@link #getChildHostLimit()} and
 * {@link #setChildHostLimit(EPPRegistryDomainHostLimit)} to get and set the
 * element.</li>
 *
 * <li>&lt;registry:period&gt; - Zero or more elements that defines the
 * supported min/max registration periods and default periods by command type.
 * The required "command" attribute defines the command type with sample values
 * of "create", "renew", and "transfer". Use {@link #getPeriods()} and
 * {@link #setPeriods(List)} to get and set the element.</li>
 *
 * <li>&lt;registry:transferHoldPeriod&gt; - The period of time a domain object
 * is in the pending transfer before the transfer is auto approved by the
 * server. This element MUST have the "unit" attribute with the possible values
 * of "y" for year, "m" for month, and "d" for day. Use
 * {@link #getTransferHoldPeriod()} and
 * {@link #setTransferHoldPeriod(EPPRegistryTransferHoldPeriodType)} to get and
 * set the element.</li>
 *
 * <li>&lt;registry:gracePeriod&gt; - Zero or more elements that defines the
 * grace periods by operation type. The required "command" attribute defines the
 * operation type with the sample values of "create", "renew", "transfer", and
 * "autoRenew". This element requires the "unit" attribute with the possible
 * values of "d" for day, "h" for hour, and "m" for minute. Use
 * {@link #getGracePeriods()} and {@link #setGracePeriods(List)} to get and set
 * the element.</li>
 *
 * <li>&lt;registry:rgp&gt; - OPTIONAL Registry Grace Period (RGP) status
 * periods. Use {@link #getRgp()} and {@link #setRgp(EPPRegistryRGP)} to get and
 * set the element.</li>
 *
 * <li>&lt;registry:dnssec&gt; - OPTIONAL DNS Security Extensions (DNSSEC)
 * policies for the server. Use {@link #getDnssec()} and
 * {@link #setDnssec(EPPRegistryDNSSEC)} to get and set the element.</li>
 *
 * <li>&lt;registry:maxCheckDomain&gt; - The maximum number of domain names
 * (&lt;domain:name&gt; elements) that can be included in a domain check command
 * defined in RFC 5731 Use {@link #setMaxCheckDomain(Integer)} and
 * {@link #getMaxCheckDomain()} to get and set the element.</li>
 *
 * <li>&lt;registry:supportedStatus&gt; - The OPTIONAL set of supported domain
 * status defined in RFC 5731 Use {@link #getSupportedStatus()} and
 * {@link #setSupportedStatus(EPPRegistrySupportedStatus)} to get and set the
 * element.</li>
 *
 * <li>&lt;registry:authInfoRegEx&gt; - The OPTIONAL regular expression used to
 * validate the domain object authorization information value. Use
 * {@link #getAuthInfoRegex()} and {@link #setAuthInfoRegex(EPPRegistryRegex)}
 * to get and set the element.</li>
 * 
 * <li>&lt;registry:nullAuthInfoSupported&gt; - An OPTIONAL flag indicating
 * whether the &lt;domain:null&gt; element in [RFC5731] is supported to remove
 * the authorization information, with a default value of {@code false}. Use
 * {@link #getNullAuthInfoSupported()} and
 * {@link #setNullAuthInfoSupported(Boolean)} to get and set the element.</li>
 * 
 * <li>&lt;registry:hostModelSupported&gt; - The OPTIONAL definition of which
 * [RFC5731] host model is used by the server. The possible values include
 * {@code HostModelSupported.hostObj} for the host object model and
 * {@code HostModelSupported.hostAttr} for the host attribute model, with the
 * default value of "hostObj". Use {@link #getHostModelSupported()} and
 * {@link #setHostModelSupported(HostModelSupported)} to get and set the
 * element.</li>
 * 
 * 
 * </ul>
 *
 * @see com.verisign.epp.codec.registry.v02.EPPRegistryZone
 * @see com.verisign.epp.codec.registry.v02.EPPRegistryDomainName
 * @see com.verisign.epp.codec.registry.v02.EPPRegistryIDN
 * @see com.verisign.epp.codec.registry.v02.EPPRegistryDomainContact
 * @see com.verisign.epp.codec.registry.v02.EPPRegistryDomainPeriod
 * @see com.verisign.epp.codec.registry.v02.EPPRegistryDomainNSLimit
 * @see com.verisign.epp.codec.registry.v02.EPPRegistryDomainHostLimit
 * @see com.verisign.epp.codec.registry.v02.EPPRegistryDomainPeriod
 * @see com.verisign.epp.codec.registry.v02.EPPRegistryTransferHoldPeriodType
 * @see com.verisign.epp.codec.registry.v02.EPPRegistryGracePeriod
 * @see com.verisign.epp.codec.registry.v02.EPPRegistryRGP
 * @see com.verisign.epp.codec.registry.v02.EPPRegistryDNSSEC
 * @see com.verisign.epp.codec.registry.v02.EPPRegistrySupportedStatus
 */
public class EPPRegistryDomain implements EPPCodecComponent {

	/**
	 * Logger
	 */
	private static Logger cat = Logger.getLogger(EPPRegistryDomain.class.getName(),
	      EPPCatFactory.getInstance().getFactory());

	/**
	 * Possible values for the {@code hostModelSupported} attribute.
	 */
	public static enum HostModelSupported {

		/**
		 * Constant for the host object model defined in RFC 5731.
		 */
		hostObj,

		/**
		 * Constant for the host attribute model defined in RFC 5731.
		 */
		hostAttr;
	}

	/**
	 * XML local name for {@code EPPRegistryDomain}.
	 */
	public static final String ELM_LOCALNAME = "domain";

	/**
	 * XML root tag for {@code EPPRegistryDomain}.
	 */
	public static final String ELM_NAME = EPPRegistryMapFactory.NS_PREFIX + ":" + ELM_LOCALNAME;

	/**
	 * XML Element Name of {@code premiumSupport} attribute.
	 */
	public final static String ELM_PREMIUM_SUPPORT = "premiumSupport";

	/**
	 * XML Element Name of {@code contactsSupported} attribute.
	 */
	public final static String ELM_REGISTRANT = "contactsSupported";

	/**
	 * XML Element Name of {@code exceedMaxExDate} attribute.
	 */
	public final static String ELM_EXCEED_MAX_EX_DATE = "exceedMaxExDate";

	/**
	 * XML Element Name of {@code maxCheckDomain} attribute.
	 */
	public final static String ELM_MAX_CHECK_DOMAIN = "maxCheckDomain";

	/**
	 * XML Element Name of {@code authInfoRegex} attribute.
	 */
	public final static String ELM_AUTH_INFO_REGEX = "authInfoRegex";

	/**
	 * XML Element Name of {@code nullAuthInfoSupported} attribute.
	 */
	public final static String ELM_NULL_AUTH_INFO_SUPPORTED = "nullAuthInfoSupported";

	/**
	 * XML Element Name of {@code hostModelSupported} attribute.
	 */
	public final static String ELM_HOST_MODEL_SUPPORTED = "hostModelSupported";

	/**
	 * {@code List} of {@code EPPRegistryDomainName} that specifies the domain
	 * name object policy
	 */
	private List<EPPRegistryDomainName> domainNames = new ArrayList<EPPRegistryDomainName>();

	/**
	 * Internationalized Domain Name (IDN) policy information.
	 */
	private EPPRegistryIDN idn = null;

	/**
	 * Indicates whether the server supports premium domain names.
	 */
	private Boolean premiumSupport = Boolean.FALSE;

	/**
	 * Indicates whether contacts are supported
	 */
	private Boolean contactsSupported = Boolean.TRUE;

	/**
	 * {@code List} of domain contact policy
	 */
	private List<EPPRegistryDomainContact> contacts = new ArrayList<EPPRegistryDomainContact>();

	/**
	 * Defines min and max number of delegated host objects that can be
	 * associated with a domain object.
	 */
	private EPPRegistryDomainNSLimit nameServerLimit = null;

	/**
	 * Defines the OPTIONAL minimum and maximum number of subordinate host
	 * objects (child hosts) for a domain object.
	 */
	private EPPRegistryDomainHostLimit childHostLimit = null;

	/**
	 * {@code List} of {@link EPPRegistryDomainPeriod} instances that define the
	 * supported min/max/default registration periods by command type. Command
	 * type must be one of "create, "renew" and "transfer".
	 */
	private List<EPPRegistryDomainPeriod> periods = new ArrayList<EPPRegistryDomainPeriod>();

	/**
	 * Zero or more &lt;registry:exceedMaxExDate&gt; elements that defines the
	 * action taken by the server when executing commands that will result in an
	 * expiration date that exceeds the maximum expiration date.
	 */
	private List<EPPRegistryExceedMaxExDate> exceedMaxExDates = new ArrayList<EPPRegistryExceedMaxExDate>();

	/**
	 * Transfer hold policy attribute
	 */
	private EPPRegistryTransferHoldPeriodType transferHoldPeriod = null;

	/**
	 * {@code List} of {@link EPPRegistryGracePeriod}
	 */
	private List<EPPRegistryGracePeriod> gracePeriods = new ArrayList<EPPRegistryGracePeriod>();

	/**
	 * Attribute for Registry Grace Period (RGP) status
	 */
	private EPPRegistryRGP rgp = null;

	/**
	 * DNS Security Extensions attribute
	 */
	private EPPRegistryDNSSEC dnssec = null;

	/**
	 * Attribute that defines the maximum number of domain names
	 * (&lt;domain:name&gt; elements) that can be included in a domain check
	 * command defined in RFC 5731.
	 */
	private Integer maxCheckDomain = null;

	/**
	 * List of domain status supported by the server
	 */
	private EPPRegistrySupportedStatus supportedStatus = null;

	/**
	 * Attribute about regular expression used to validate the domain object
	 * authorization information value
	 */
	private EPPRegistryRegex authInfoRegex = null;

	/**
	 * Indicates whether the &lt;domain:null&gt; element in [RFC5731] is
	 * supported, which a default of {@code false}.
	 */
	private Boolean nullAuthInfoSupported = Boolean.FALSE;

	/**
	 * The OPTIONAL definition of which host model is used by the server with the
	 * default set to {@link HostModelSupported#hostObj}.
	 */
	private HostModelSupported hostModelSupported = HostModelSupported.hostObj;

	/**
	 * Encode a DOM Element tree from the attributes of the
	 * {@code EPPRegistryDomain} instance.
	 *
	 * @param aDocument
	 *           DOM Document that is being built. Used as an Element factory.
	 *
	 * @return Element Root DOM Element representing the
	 *         {@code EPPRegistryDomain} instance.
	 *
	 * @exception EPPEncodeException
	 *               - Unable to encode {@code EPPRegistryDomain} instance.
	 */
	@Override
	public Element encode(Document aDocument) throws EPPEncodeException {
		try {
			validateState();
		}
		catch (EPPCodecException e) {
			throw new EPPEncodeException("Invalid state on EPPRegistryDomain.encode: " + e);
		}

		Element root = aDocument.createElementNS(EPPRegistryMapFactory.NS, ELM_NAME);

		// Domain Names
		EPPUtil.encodeCompList(aDocument, root, this.domainNames);

		// IDN
		EPPUtil.encodeComp(aDocument, root, this.idn);

		// Premium Support
		if (!this.hasPremiumSupport()) {
			this.premiumSupport = Boolean.FALSE;
		}
		EPPUtil.encodeString(aDocument, root, this.premiumSupport.toString(), EPPRegistryMapFactory.NS,
		      EPPRegistryMapFactory.NS_PREFIX + ":" + ELM_PREMIUM_SUPPORT);

		// Contact Supported
		if (!this.hasContactsSupported()) {
			this.contactsSupported = Boolean.TRUE;
		}
		EPPUtil.encodeString(aDocument, root, this.contactsSupported.toString(), EPPRegistryMapFactory.NS,
		      EPPRegistryMapFactory.NS_PREFIX + ":" + ELM_REGISTRANT);

		// Contacts
		EPPUtil.encodeCompList(aDocument, root, this.contacts);

		// Name Server Limit
		EPPUtil.encodeComp(aDocument, root, this.nameServerLimit);

		// Child Host Limit
		EPPUtil.encodeComp(aDocument, root, this.childHostLimit);

		// Periods
		EPPUtil.encodeCompList(aDocument, root, this.periods);

		// Exceed Maximum Expiration Dates
		EPPUtil.encodeCompList(aDocument, root, this.exceedMaxExDates);

		// Transfer Hold Period
		EPPUtil.encodeComp(aDocument, root, this.transferHoldPeriod);

		// Grace Periods
		EPPUtil.encodeCompList(aDocument, root, this.gracePeriods);

		// RGP
		if (this.rgp != null) {
			EPPUtil.encodeComp(aDocument, root, this.rgp);
		}

		// DNSSEC
		if (this.dnssec != null) {
			EPPUtil.encodeComp(aDocument, root, this.dnssec);
		}

		// Max Check Domain
		EPPUtil.encodeString(aDocument, root, this.maxCheckDomain.toString(), EPPRegistryMapFactory.NS,
		      EPPRegistryMapFactory.NS_PREFIX + ":" + ELM_MAX_CHECK_DOMAIN);

		// Supported Status
		if (this.supportedStatus != null) {
			EPPUtil.encodeComp(aDocument, root, this.supportedStatus);
		}

		// Auth Info Regex
		if (this.authInfoRegex != null) {
			EPPUtil.encodeComp(aDocument, root, this.authInfoRegex);
		}

		// Null Auth Info Supported
		if (!this.hasNullAuthInfoSupported()) {
			this.nullAuthInfoSupported = Boolean.FALSE;
		}
		EPPUtil.encodeString(aDocument, root, this.nullAuthInfoSupported.toString(), EPPRegistryMapFactory.NS,
		      EPPRegistryMapFactory.NS_PREFIX + ":" + ELM_NULL_AUTH_INFO_SUPPORTED);

		// Host Model Supported
		EPPUtil.encodeString(aDocument, root, this.hostModelSupported.toString(), EPPRegistryMapFactory.NS,
		      EPPRegistryMapFactory.NS_PREFIX + ":" + ELM_HOST_MODEL_SUPPORTED);

		return root;
	}

	/**
	 * Decode the {@code EPPRegistryDomain} attributes from the aElement DOM
	 * Element tree.
	 *
	 * @param aElement
	 *           Root DOM Element to decode {@code EPPRegistryDomain} from.
	 *
	 * @exception EPPDecodeException
	 *               Unable to decode aElement
	 */
	@Override
	public void decode(Element aElement) throws EPPDecodeException {
		// Domain Names
		this.domainNames = EPPUtil.decodeCompList(aElement, EPPRegistryMapFactory.NS, EPPRegistryDomainName.ELM_NAME,
		      EPPRegistryDomainName.class);

		// IDN
		this.idn = (EPPRegistryIDN) EPPUtil.decodeComp(aElement, EPPRegistryMapFactory.NS, EPPRegistryIDN.ELM_NAME,
		      EPPRegistryIDN.class);

		// Premium Domain
		this.premiumSupport = EPPUtil.decodeBoolean(aElement, EPPRegistryMapFactory.NS, ELM_PREMIUM_SUPPORT);
		if (this.premiumSupport == null) {
			this.premiumSupport = Boolean.FALSE;
		}

		// Contacts Supported
		this.contactsSupported = EPPUtil.decodeBoolean(aElement, EPPRegistryMapFactory.NS, ELM_REGISTRANT);
		if (this.contactsSupported == null) {
			this.contactsSupported = Boolean.TRUE;
		}

		// Contacts
		this.contacts = EPPUtil.decodeCompList(aElement, EPPRegistryMapFactory.NS, EPPRegistryDomainContact.ELM_NAME,
		      EPPRegistryDomainContact.class);

		// Name Server Limit
		this.nameServerLimit = (EPPRegistryDomainNSLimit) EPPUtil.decodeComp(aElement, EPPRegistryMapFactory.NS,
		      EPPRegistryDomainNSLimit.ELM_NAME, EPPRegistryDomainNSLimit.class);

		// Child Host Limit
		this.childHostLimit = (EPPRegistryDomainHostLimit) EPPUtil.decodeComp(aElement, EPPRegistryMapFactory.NS,
		      EPPRegistryDomainHostLimit.ELM_NAME, EPPRegistryDomainHostLimit.class);

		// Periods
		this.periods = EPPUtil.decodeCompList(aElement, EPPRegistryMapFactory.NS, EPPRegistryDomainPeriod.ELM_NAME,
		      EPPRegistryDomainPeriod.class);

		// Exceed Maximum Expiration Dates
		this.exceedMaxExDates = EPPUtil.decodeCompList(aElement, EPPRegistryMapFactory.NS,
		      EPPRegistryExceedMaxExDate.ELM_NAME, EPPRegistryExceedMaxExDate.class);

		// Transfer Hold Period
		this.transferHoldPeriod = (EPPRegistryTransferHoldPeriodType) EPPUtil.decodeComp(aElement,
		      EPPRegistryMapFactory.NS, EPPRegistryTransferHoldPeriodType.ELM_LOCALNAME,
		      EPPRegistryTransferHoldPeriodType.class);

		// Grace Periods
		this.gracePeriods = EPPUtil.decodeCompList(aElement, EPPRegistryMapFactory.NS,
		      EPPRegistryGracePeriod.ELM_LOCALNAME, EPPRegistryGracePeriod.class);

		// RGP
		this.rgp = (EPPRegistryRGP) EPPUtil.decodeComp(aElement, EPPRegistryMapFactory.NS, EPPRegistryRGP.ELM_NAME,
		      EPPRegistryRGP.class);

		// DNSSEC
		this.dnssec = (EPPRegistryDNSSEC) EPPUtil.decodeComp(aElement, EPPRegistryMapFactory.NS,
		      EPPRegistryDNSSEC.ELM_NAME, EPPRegistryDNSSEC.class);

		// Supported Status
		this.supportedStatus = (EPPRegistrySupportedStatus) EPPUtil.decodeComp(aElement, EPPRegistryMapFactory.NS,
		      EPPRegistrySupportedStatus.ELM_NAME, EPPRegistrySupportedStatus.class);

		// Max Check Domain
		this.maxCheckDomain = EPPUtil.decodeInteger(aElement, EPPRegistryMapFactory.NS, ELM_MAX_CHECK_DOMAIN);

		// Auth Info Regex
		this.setAuthInfoRegex((EPPRegistryRegex) EPPUtil.decodeComp(aElement, EPPRegistryMapFactory.NS,
		      ELM_AUTH_INFO_REGEX, EPPRegistryRegex.class));

		// Null Auth Info Supported
		this.nullAuthInfoSupported = EPPUtil.decodeBoolean(aElement, EPPRegistryMapFactory.NS,
		      ELM_NULL_AUTH_INFO_SUPPORTED);
		if (this.nullAuthInfoSupported == null) {
			this.nullAuthInfoSupported = Boolean.FALSE;
		}

		// Host Model Supported
		String theHostModelSupportedStr = EPPUtil.decodeString(aElement, EPPRegistryMapFactory.NS,
		      ELM_HOST_MODEL_SUPPORTED);
		if (theHostModelSupportedStr == null) {
			this.hostModelSupported = HostModelSupported.hostObj;
		}
		else {
			this.hostModelSupported = HostModelSupported.valueOf(theHostModelSupportedStr);
		}
	}

	/**
	 * implements a deep {@code EPPRegistryDomain} compare.
	 *
	 * @param aObject
	 *           {@code EPPRegistryDomain} instance to compare with
	 *
	 * @return {@code true} if this object is the same as the aObject argument;
	 *         {@code false} otherwise
	 */
	@Override
	public boolean equals(Object aObject) {
		if (!(aObject instanceof EPPRegistryDomain)) {
			return false;
		}

		EPPRegistryDomain theComp = (EPPRegistryDomain) aObject;

		// Domain Names
		if (!EqualityUtil.equals(this.domainNames, theComp.domainNames)) {
			cat.error("EPPRegistryDomain.equals(): domainNames not equal");
			return false;
		}

		// Premium Support
		if (!EqualityUtil.equals(this.premiumSupport, theComp.premiumSupport)) {
			cat.error("EPPRegistryDomain.equals(): premiumSupport not equal");
			return false;
		}

		// IDN
		if (!EqualityUtil.equals(this.idn, theComp.idn)) {
			cat.error("EPPRegistryDomain.equals(): idn not equal");
			return false;
		}

		// Contacts Supported
		if (!EqualityUtil.equals(this.contactsSupported, theComp.contactsSupported)) {
			cat.error("EPPRegistryDomain.equals(): contactsSupported not equal");
			return false;
		}

		// Contacts
		if (!EqualityUtil.equals(this.contacts, theComp.contacts)) {
			cat.error("EPPRegistryDomain.equals(): contacts not equal");
			return false;
		}

		// Name Server Limit
		if (!EqualityUtil.equals(this.nameServerLimit, theComp.nameServerLimit)) {
			cat.error("EPPRegistryDomain.equals(): nameServerLimit not equal");
			return false;
		}

		// Child Host Limit
		if (!EqualityUtil.equals(this.childHostLimit, theComp.childHostLimit)) {
			cat.error("EPPRegistryDomain.equals(): childHostLimit not equal");
			return false;
		}

		// Periods
		if (!EqualityUtil.equals(this.periods, theComp.periods)) {
			cat.error("EPPRegistryDomain.equals(): periods not equal");
			return false;
		}

		// Exceed maximim expiration dates
		if (!EqualityUtil.equals(this.exceedMaxExDates, theComp.exceedMaxExDates)) {
			cat.error("EPPRegistryDomain.equals(): exceedMaxExDates not equal");
			return false;
		}

		// Transfer Hold Period
		if (!EqualityUtil.equals(this.transferHoldPeriod, theComp.transferHoldPeriod)) {
			cat.error("EPPRegistryDomain.equals(): transferHoldPeriod not equal");
			return false;
		}

		// Grace Periods
		if (!EqualityUtil.equals(this.gracePeriods, theComp.gracePeriods)) {
			cat.error("EPPRegistryDomain.equals(): gracePeriods not equal");
			return false;
		}

		// RGP
		if (!EqualityUtil.equals(this.rgp, theComp.rgp)) {
			cat.error("EPPRegistryDomain.equals(): rgp not equal");
			return false;
		}

		// DNSSEC
		if (!EqualityUtil.equals(this.dnssec, theComp.dnssec)) {
			cat.error("EPPRegistryDomain.equals(): dnssec not equal");
			return false;
		}

		// Max Check Domain
		if (!EqualityUtil.equals(this.maxCheckDomain, theComp.maxCheckDomain)) {
			cat.error("EPPRegistryDomain.equals(): maxCheckDomain not equal");
			return false;
		}

		// Supported Status
		if (!EqualityUtil.equals(this.supportedStatus, theComp.supportedStatus)) {
			cat.error("EPPRegistryDomain.equals(): supportedStatus not equal");
			return false;
		}

		// Auth Info Regex
		if (!EqualityUtil.equals(this.authInfoRegex, theComp.authInfoRegex)) {
			cat.error("EPPRegistryDomain.equals(): authInfoRegex not equal");
			return false;
		}

		// Null Auth Info Supports
		if (!EqualityUtil.equals(this.nullAuthInfoSupported, theComp.nullAuthInfoSupported)) {
			cat.error("EPPRegistryDomain.equals(): nullAuthInfoSupported not equal");
			return false;
		}

		// Host Model Supported
		if (!EqualityUtil.equals(this.hostModelSupported, theComp.hostModelSupported)) {
			cat.error("EPPRegistryDomain.equals(): hostModelSupported not equal");
			return false;
		}

		return true;
	}

	/**
	 * Validate the state of the {@code EPPRegistryDomain} instance. A valid
	 * state means that all of the required attributes have been set. If
	 * validateState returns without an exception, the state is valid. If the
	 * state is not valid, the {@code EPPCodecException} will contain a
	 * description of the error.
	 *
	 * @throws EPPCodecException
	 *            Status is not valid
	 */
	void validateState() throws EPPCodecException {
		if (this.domainNames == null || this.domainNames.size() == 0) {
			throw new EPPCodecException("domainNames element is not set");
		}
		if (this.contacts != null && this.contacts.size() > 3) {
			throw new EPPCodecException("number of contact element cannot exceed 3");
		}
		if (this.nameServerLimit == null) {
			throw new EPPCodecException("ns element is not set");
		}
		if (this.transferHoldPeriod == null) {
			throw new EPPCodecException("transferHoldPeriod element is not set");
		}
		if (this.maxCheckDomain == null || this.maxCheckDomain.intValue() <= 0) {
			throw new EPPCodecException("maxCheckDomain is required and should be greater than 0");
		}
	}

	/**
	 * Clone {@code EPPRegistryDomain}.
	 *
	 * @return clone of {@code EPPRegistryDomain}
	 *
	 * @exception CloneNotSupportedException
	 *               standard Object.clone exception
	 */
	@Override
	public Object clone() throws CloneNotSupportedException {
		EPPRegistryDomain clone = (EPPRegistryDomain) super.clone();
		// Domain Names
		if (this.domainNames != null) {
			clone.domainNames = (List) ((ArrayList) this.domainNames).clone();
		}

		// IDN
		if (this.idn != null) {
			clone.idn = (EPPRegistryIDN) this.idn.clone();
		}

		// Contacts
		if (this.contacts != null) {
			clone.contacts = (List) ((ArrayList) this.contacts).clone();
		}

		// Name Server Limit
		if (this.nameServerLimit != null) {
			clone.nameServerLimit = (EPPRegistryDomainNSLimit) this.nameServerLimit.clone();
		}

		// Child Host Limit
		if (this.childHostLimit != null) {
			clone.childHostLimit = (EPPRegistryDomainHostLimit) this.childHostLimit.clone();
		}

		// Periods
		if (this.periods != null) {
			clone.periods = (List) ((ArrayList) this.periods).clone();
		}

		// Exceed Maximum Expiration Dates
		if (this.exceedMaxExDates != null) {
			clone.exceedMaxExDates = (List) ((ArrayList) this.exceedMaxExDates).clone();
		}

		// Transfer Hold Period
		if (this.transferHoldPeriod != null) {
			clone.transferHoldPeriod = (EPPRegistryTransferHoldPeriodType) this.transferHoldPeriod.clone();
		}

		// Grace Periods
		if (this.gracePeriods != null) {
			clone.gracePeriods = (List) ((ArrayList) this.gracePeriods).clone();
		}

		// RGP
		if (this.rgp != null) {
			clone.rgp = (EPPRegistryRGP) this.rgp.clone();
		}

		// DNSSEC
		if (this.dnssec != null) {
			clone.dnssec = (EPPRegistryDNSSEC) this.dnssec.clone();
		}

		// Supported Status
		if (this.supportedStatus != null) {
			clone.supportedStatus = (EPPRegistrySupportedStatus) this.supportedStatus.clone();
		}

		// Auth Info Regex
		if (this.authInfoRegex != null) {
			clone.authInfoRegex = (EPPRegistryRegex) this.authInfoRegex.clone();
		}

		return clone;
	}

	/**
	 * Implementation of {@code Object.toString}, which will result in an
	 * indented XML {@code String} representation of the concrete
	 * {@code EPPCodecComponent}.
	 *
	 * @return Indented XML {@code String} if successful; {@code ERROR}
	 *         otherwise.
	 */
	@Override
	public String toString() {
		return EPPUtil.toString(this);
	}

	/**
	 * Is the domain name policies defined?
	 *
	 * @return {@code true} if the domain name policies is defined; {@code false}
	 *         otherwise.
	 */
	public boolean hasDomainNames() {
		return (this.domainNames != null && !this.domainNames.isEmpty() ? true : false);
	}

	/**
	 * Get the {@code List} of {@code EPPRegistryDomainName} that specifies the
	 * domain name object policy.
	 *
	 * @return the {@code List} of {@code EPPRegistryDomainName} that specifies
	 *         the domain name object policy
	 */
	public List<EPPRegistryDomainName> getDomainNames() {
		return this.domainNames;
	}

	/**
	 * Set the {@code List} of {@code EPPRegistryDomainName} that specifies the
	 * domain name object policy.
	 *
	 * @param aDomainNames
	 *           the {@code List} of {@code EPPRegistryDomainName} that specifies
	 *           the domain name object policy
	 */
	public void setDomainNames(List<EPPRegistryDomainName> aDomainNames) {
		if (aDomainNames == null) {
			this.domainNames = new ArrayList<EPPRegistryDomainName>();
		}
		else {
			this.domainNames = aDomainNames;
		}
	}

	/**
	 * Add a domain name object policy to the list of domain name policies.
	 *
	 * @param aDomainName
	 *           Domain name policy to add
	 */
	public void addDomainName(EPPRegistryDomainName aDomainName) {
		if (aDomainName == null) {
			return;
		}

		if (this.domainNames == null) {
			this.domainNames = new ArrayList<EPPRegistryDomainName>();
		}

		this.domainNames.add(aDomainName);
	}

	/**
	 * Get the Internationalized Domain Name (IDN) policy information.
	 *
	 * @return Internationalized Domain Name (IDN) policy information
	 */
	public EPPRegistryIDN getIdn() {
		return this.idn;
	}

	/**
	 * Set the Internationalized Domain Name (IDN) policy information.
	 *
	 * @param idn
	 *           the Internationalized Domain Name (IDN) policy information.
	 */
	public void setIdn(EPPRegistryIDN idn) {
		this.idn = idn;
	}

	/**
	 * Is the premium support flag defined?
	 *
	 * @return {@code true} if the premium support flag is defined; {@code false}
	 *         otherwise.
	 */
	public boolean hasPremiumSupport() {
		return (this.premiumSupport != null ? true : false);
	}

	/**
	 * Get premium support flag.
	 *
	 * @return flag that indicates whether the server supports premium domain
	 *         names
	 */
	public Boolean getPremiumSupport() {
		return this.premiumSupport;
	}

	/**
	 * Set premium support flag.
	 *
	 * @param aPremiumSupport
	 *           flag that indicates whether the server supports premium domain
	 *           names
	 */
	public void setPremiumSupport(Boolean aPremiumSupport) {
		this.premiumSupport = aPremiumSupport;
	}

	/**
	 * Is the contact supported flag flag defined?
	 *
	 * @return {@code true} if the contact supported flag is defined;
	 *         {@code false} otherwise.
	 */
	public boolean hasContactsSupported() {
		return (this.contactsSupported != null ? true : false);
	}

	/**
	 * Get the contact supported flag.
	 *
	 * @return flag that indicates whether contacts are supported
	 */
	public Boolean getContactsSupported() {
		return this.contactsSupported;
	}

	/**
	 * Set the contact supported flag.
	 *
	 * @param aContactsSupported
	 *           flag that indicates whether contacts are supported
	 */
	public void setContactsSupported(Boolean aContactsSupported) {
		this.contactsSupported = aContactsSupported;
	}

	/**
	 * Is the contacts defined?
	 *
	 * @return {@code true} if the contacts is defined; {@code false} otherwise.
	 */
	public boolean hasContacts() {
		return (this.contacts != null && !this.contacts.isEmpty() ? true : false);
	}

	/**
	 * Get domain contact policy.
	 *
	 * @return {@code List} of domain contact policy
	 */
	public List<EPPRegistryDomainContact> getContacts() {
		return this.contacts;
	}

	/**
	 * Set domain contact policy.
	 *
	 * @param aContacts
	 *           {@code List} of domain contact policy
	 */
	public void setContacts(List<EPPRegistryDomainContact> aContacts) {
		if (aContacts == null) {
			this.contacts = new ArrayList<EPPRegistryDomainContact>();
		}
		else {
			this.contacts = aContacts;
		}
	}

	/**
	 * Append a domain contact policy to the existing list.
	 *
	 * @param aContact
	 *           domain contact policy for one of the "admin", "tech", or
	 *           "billing" contact.
	 */
	public void addContact(EPPRegistryDomainContact aContact) {
		if (aContact == null) {
			return;
		}

		if (this.contacts == null) {
			this.contacts = new ArrayList<EPPRegistryDomainContact>();
		}

		this.contacts.add(aContact);
	}

	/**
	 * Get NS limit definition.
	 *
	 * @return instance of {@code EPPRegistryDomainNSLimit} that defines min/max
	 *         number of delegated host objects (name servers) that can be
	 *         associated with a domain object
	 */
	public EPPRegistryDomainNSLimit getNameServerLimit() {
		return this.nameServerLimit;
	}

	/**
	 * Set NS limit definition.
	 *
	 * @param nameServerLimit
	 *           instance of {@code EPPRegistryDomainNSLimit} that defines
	 *           min/max number of delegated host objects (name servers) that can
	 *           be associated with a domain object
	 */
	public void setNameServerLimit(EPPRegistryDomainNSLimit nameServerLimit) {
		this.nameServerLimit = nameServerLimit;
	}

	/**
	 * Get child host limit.
	 *
	 * @return Instance of {@code EPPRegistryDomainHostLimit} that defines the
	 *         minimum and maximum number of subordinate host objects (child
	 *         hosts) for a domain object.
	 */
	public EPPRegistryDomainHostLimit getChildHostLimit() {
		return this.childHostLimit;
	}

	/**
	 * Set child host limit.
	 *
	 * @param childHostLimit
	 *           Instance of {@code EPPRegistryDomainHostLimit} that defines the
	 *           minimum and maximum number of subordinate host objects (child
	 *           hosts) for a domain object.
	 */
	public void setChildHostLimit(EPPRegistryDomainHostLimit childHostLimit) {
		this.childHostLimit = childHostLimit;
	}

	/**
	 * Is the periods defined?
	 *
	 * @return {@code true} if the periods is defined; {@code false} otherwise.
	 */
	public boolean hasPeriods() {
		return (this.periods != null && !this.periods.isEmpty() ? true : false);
	}

	/**
	 * Get {@code List} of {@link EPPRegistryDomainPeriod} instances that define
	 * the supported min/max/default registration periods by command type.
	 * Command type must be one of "create, "renew" and "transfer".
	 *
	 * @return {@code List} of {@link EPPRegistryDomainPeriod} instances
	 */
	public List<EPPRegistryDomainPeriod> getPeriods() {
		return this.periods;
	}

	/**
	 * Set {@code List} of {@link EPPRegistryDomainPeriod} instances that define
	 * the supported min/max/default periods by command type. Command type must
	 * be one of "create, "renew" and "transfer".
	 *
	 * @param aPeriods
	 *           {@code List} of {@link EPPRegistryDomainPeriod} instances
	 */
	public void setPeriods(List<EPPRegistryDomainPeriod> aPeriods) {
		if (aPeriods == null) {
			this.periods = new ArrayList<EPPRegistryDomainPeriod>();
		}
		else {
			this.periods = aPeriods;
		}
	}

	/**
	 * Adds a supported period to the list of periods.
	 * 
	 * @param aPeriod
	 *           A period to add to the list
	 */
	public void addPeriod(EPPRegistryDomainPeriod aPeriod) {
		if (aPeriod == null) {
			return;
		}

		if (this.periods == null) {
			this.periods = new ArrayList<EPPRegistryDomainPeriod>();
		}

		this.periods.add(aPeriod);
	}

	/**
	 * Is the exceed maximum expiration dates defined?
	 *
	 * @return {@code true} if the exceed maximum expiration dates is defined;
	 *         {@code false} otherwise.
	 */
	public boolean hasExceedMaxExDates() {
		return (this.exceedMaxExDates != null && !this.exceedMaxExDates.isEmpty() ? true : false);
	}

	/**
	 * Gets the {@code List} of {@link EPPRegistryExceedMaxExDate} instances that the
	 * policy with exceeding the maximum expiration date for renewable commands.
	 *
	 * @return {@code List} of {@link EPPRegistryExceedMaxExDate} instances if
	 *         defined; {@code null} otherwise.
	 */
	public List<EPPRegistryExceedMaxExDate> getExceedMaxExDates() {
		return this.exceedMaxExDates;
	}

	/**
	 * Sets the {@code List} of {@link EPPRegistryExceedMaxExDate} instances that the
	 * policy with exceeding the maximum expiration date for renewable commands.
	 *
	 * @param aExceedMaxExDates
	 *           {@code List} of {@link EPPRegistryExceedMaxExDate} instances
	 */
	public void setExceedMaxExDates(List<EPPRegistryExceedMaxExDate> aExceedMaxExDates) {
		if (aExceedMaxExDates == null) {
			this.exceedMaxExDates = new ArrayList<EPPRegistryExceedMaxExDate>();
		}
		else {
			this.exceedMaxExDates = aExceedMaxExDates;
		}
	}

	/**
	 * Adds a exceed maximum expiration date policy to the list.
	 * 
	 * @param aExceedMaxExDate
	 *           A exceed maximum expiration date policy to add
	 */
	public void addExceedMaxExDate(EPPRegistryExceedMaxExDate aExceedMaxExDate) {
		if (aExceedMaxExDate == null) {
			return;
		}

		if (this.exceedMaxExDates == null) {
			this.exceedMaxExDates = new ArrayList<EPPRegistryExceedMaxExDate>();
		}

		this.exceedMaxExDates.add(aExceedMaxExDate);
	}

	/**
	 * Get the period of time a domain object is in the pending transfer before
	 * the transfer is auto approved by the server
	 *
	 * @return instance of {@link EPPRegistryTransferHoldPeriodType}
	 */
	public EPPRegistryTransferHoldPeriodType getTransferHoldPeriod() {
		return this.transferHoldPeriod;
	}

	/**
	 * Set the period of time a domain object is in the pending transfer before
	 * the transfer is auto approved by the server
	 *
	 * @param transferHoldPeriod
	 *           instance of {@link EPPRegistryTransferHoldPeriodType}
	 */
	public void setTransferHoldPeriod(EPPRegistryTransferHoldPeriodType transferHoldPeriod) {
		this.transferHoldPeriod = transferHoldPeriod;
	}

	/**
	 * Is the grace periods defined?
	 *
	 * @return {@code true} if the grace periods is defined; {@code false}
	 *         otherwise.
	 */
	public boolean hasGracePeriods() {
		return (this.gracePeriods != null && !this.gracePeriods.isEmpty() ? true : false);
	}

	/**
	 * Append one instance of {@link EPPRegistryGracePeriod} to the existing
	 * {@code List}.
	 *
	 * @param aGracePeriod
	 *           instance of {@link EPPRegistryGracePeriod}
	 */
	public void addGracePeriod(EPPRegistryGracePeriod aGracePeriod) {
		if (aGracePeriod == null) {
			return;
		}

		if (this.gracePeriods == null) {
			this.gracePeriods = new ArrayList<EPPRegistryGracePeriod>();
		}

		this.gracePeriods.add(aGracePeriod);
	}

	/**
	 * Get the {@code List} of attributes that defines the grace periods by
	 * operation type.
	 *
	 * @return {@code List} of {@link EPPRegistryGracePeriod}
	 */
	public List<EPPRegistryGracePeriod> getGracePeriods() {
		return this.gracePeriods;
	}

	/**
	 * Set the {@code List} of attributes that defines the grace periods by
	 * operation type.
	 *
	 * @param aGracePeriods
	 *           {@code List} of {@link EPPRegistryGracePeriod}
	 */
	public void setGracePeriods(List<EPPRegistryGracePeriod> aGracePeriods) {
		if (aGracePeriods == null) {
			this.gracePeriods = new ArrayList<EPPRegistryGracePeriod>();
		}
		else {
			this.gracePeriods = aGracePeriods;
		}
	}

	/**
	 * Get the information about Registry Grace Period (RGP).
	 *
	 * @return instance of {@link EPPRegistryRGP}.
	 */
	public EPPRegistryRGP getRgp() {
		return this.rgp;
	}

	/**
	 * Set the information about Registry Grace Period (RGP).
	 *
	 * @param rgp
	 *           instance of {@link EPPRegistryRGP}.
	 */
	public void setRgp(EPPRegistryRGP rgp) {
		this.rgp = rgp;
	}

	/**
	 * Get the DNS Security Extensions (DNSSEC) policies.
	 *
	 * @return instance of {@link EPPRegistryDNSSEC} that defines the DNS
	 *         Security Extensions (DNSSEC) policies.
	 */
	public EPPRegistryDNSSEC getDnssec() {
		return this.dnssec;
	}

	/**
	 * Set the DNS Security Extensions (DNSSEC) policies.
	 *
	 * @param dnssec
	 *           instance of {@link EPPRegistryDNSSEC} that defines the DNS
	 *           Security Extensions (DNSSEC) policies.
	 */
	public void setDnssec(EPPRegistryDNSSEC dnssec) {
		this.dnssec = dnssec;
	}

	/**
	 * Get the attribute that defines the maximum number of domain names
	 * (&lt;domain:name&gt; elements) that can be included in a domain check
	 * command defined in RFC 5731.
	 *
	 * @return maximum number of domain names (&lt;domain:name&gt; elements) that
	 *         can be included in a domain check command defined in RFC 5731.
	 */
	public Integer getMaxCheckDomain() {
		return this.maxCheckDomain;
	}

	/**
	 * Set the attribute that defines the maximum number of domain names
	 * (&lt;domain:name&gt; elements) that can be included in a domain check
	 * command defined in RFC 5731.
	 *
	 * @param maxCheckDomain
	 *           maximum number of domain names (&lt;domain:name&gt; elements)
	 *           that can be included in a domain check command defined in RFC
	 *           5731.
	 */
	public void setMaxCheckDomain(Integer maxCheckDomain) {
		this.maxCheckDomain = maxCheckDomain;
	}

	/**
	 * Get info about regular expression used to validate the domain object
	 * authorization information value.
	 *
	 * @return instance of {@link EPPRegistryRegex} that specifies regular
	 *         expression used to validate the domain object authorization
	 *         information value
	 */
	public EPPRegistryRegex getAuthInfoRegex() {
		return this.authInfoRegex;
	}

	/**
	 * Set info about regular expression used to validate the domain object
	 * authorization information value.
	 *
	 * @param authInfoRegex
	 *           instance of {@link EPPRegistryRegex} that specifies regular
	 *           expression used to validate the domain object authorization
	 *           information value
	 */
	public void setAuthInfoRegex(EPPRegistryRegex authInfoRegex) {
		if (authInfoRegex != null) {
			authInfoRegex.setRootName(ELM_AUTH_INFO_REGEX);
		}
		this.authInfoRegex = authInfoRegex;
	}

	/**
	 * Is the null auth info supported flag defined?
	 *
	 * @return {@code true} if the null auth info supported flag is defined;
	 *         {@code false} otherwise.
	 */
	public boolean hasNullAuthInfoSupported() {
		return (this.nullAuthInfoSupported != null ? true : false);
	}

	/**
	 * Get null auth info supported flag.
	 *
	 * @return flag that indicates whether the server supports the
	 *         &lt;domain:null&gt; element in [RFC5731] if defined; {@code null}
	 *         otherwise.
	 */
	public Boolean getNullAuthInfoSupported() {
		return this.nullAuthInfoSupported;
	}

	/**
	 * Set null auth info supported flag.
	 *
	 * @param aNullAuthInfoSupported
	 *           flag that indicates whether the server supports the
	 *           &lt;domain:null&gt; element in [RFC5731]
	 */
	public void setNullAuthInfoSupported(Boolean aNullAuthInfoSupported) {
		this.nullAuthInfoSupported = aNullAuthInfoSupported;
	}

	/**
	 * Get domain status supported by the server.
	 *
	 * @return instance of {@link EPPRegistrySupportedStatus} that contains a
	 *         list of supported domain status by the server
	 */
	public EPPRegistrySupportedStatus getSupportedStatus() {
		return this.supportedStatus;
	}

	/**
	 * Set domain status supported by the server.
	 *
	 * @param supportedStatus
	 *           instance of {@link EPPRegistrySupportedStatus} that contains a
	 *           list of supported domain status by the server
	 */
	public void setSupportedStatus(EPPRegistrySupportedStatus supportedStatus) {
		this.supportedStatus = supportedStatus;
	}

	/**
	 * Gets the host model supported from RFC 5731, with the default set to
	 * {@link HostModelSupported#hostObj}.
	 *
	 * @return Host model supported
	 */
	public HostModelSupported getHostModelSupported() {
		return this.hostModelSupported;
	}

	/**
	 * Sets the host model supported from RFC 5731, with the default set to
	 * {@link HostModelSupported#hostObj}.
	 *
	 * @param aHostModelSupported
	 *           Host model supported enumerated value. If set to {@code null},
	 *           the default value of {@link HostModelSupported#hostObj} will be
	 *           used.
	 */
	public void setHostModelSupported(HostModelSupported aHostModelSupported) {
		if (aHostModelSupported == null) {
			this.hostModelSupported = HostModelSupported.hostObj;
		}
		else {
			this.hostModelSupported = aHostModelSupported;
		}
	}

	/**
	 * Returns the XML namespace associated with the {@code EPPCodecComponent}.
	 *
	 * @return XML namespace for the {@code EPPCodecComponent}.
	 */
	@Override
	public String getNamespace() {
		return EPPRegistryMapFactory.NS;
	}

}
