/***********************************************************
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.Date;
import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
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.policy.EPPRegistryZoneInterface;
import com.verisign.epp.util.EqualityUtil;

/**
 * Represents the detailed information of a zone object. Upon receiving an
 * &lt;info&gt; command, with a &lt;registry:name&gt; element in it, the server
 * puts a &lt;registry:zone&gt; element in the response. <br>
 * <br>
 *
 * Each element in the list contains the following info:
 *
 * <ul>
 * <li>&lt;registry:name&gt; - fully qualified name of the zone object. Zone
 * name can be at any level (top level, second level, third level, etc.). Use
 * {@link #getName()} and {@link #setName(String)} to get and set the element.
 * </li>
 * <li>&lt;registry:group&gt; - An OPTIONAL server defined grouping of zones.
 * Zone in one group share similar features and policies. Use
 * {@link #getGroup()} and {@link #setGroup(String)} to get and set the element.
 * </li>
 * <li>&lt;registry:services&gt; - The OPTIONAL EPP namespace URIs of the
 * objects and object extensions supported by the server based on RFC 5730. Use
 * {@link #getServices()} and {@link #setServices(EPPRegistryServices)} to get
 * and set the element.</li>
 * <li>&lt;registry:crID&gt; - The OPTIONAL identifier of the client that
 * created the zone. Use {@link #getCreatedBy()} and
 * {@link #setCreatedBy(String)} to get and set the element.</li>
 * <li>&lt;registry:crDate&gt; - The OPTIONAL date and time of zone object
 * creation. The &lt;registry:crDate&gt; element MUST be set if the zone object
 * has already been created. Use {@link #getCreatedDate()} and
 * {@link #setCreatedDate(Date)} to get and set the element.</li>
 * <li>&lt;registry:upID&gt; - The OPTIONAL identifier of the client that last
 * updated the zone object.This element MUST NOT be present if the zone has
 * never been modified. Use {@link #getLastUpdatedBy()} and
 * {@link #setLastUpdatedBy(String)} to get and set the element.</li>
 * <li>&lt;registry:upDate&gt; - The OPTIONAL date and time of the most recent
 * zone object modification. This element MUST NOT be present if the zone object
 * has never been modified. Use {@link #getLastUpdatedDate()} and
 * {@link #setLastUpdatedDate(Date)} to get and set the element.</li>
 * <li>&lt;registry:unsupportedData&gt; - The OPTIONAL policy associated with
 * receipt of unsupported data sent by the client to the server. The unsupported
 * data may be an unsupported element or extension. The server SHOULD be
 * consistent in the handling of unsupported data.</li>
 * <li>&lt;registry:batch&gt; - The OPTIONAL list of batch jobs.</li>
 * <li>&lt;registry:system&gt; - The OPTIONAL list of zones that makeup the
 * system when the "perSystem" share policy is used for the internal hosts,
 * external hosts, or contacts. The list of zones are listed independent of the
 * client's privileges to provision domains in the zone.</li>
 * <li>&lt;registry:domain&gt; - The domain name object policy information per
 * RFC 5731. Use {@link #getDomain()} and {@link #setDomain(EPPRegistryDomain)}
 * to get and set the element.</li>
 * <li>&lt;registry:host&gt; - The host object policy information per RFC 5732.
 * Use {@link #getHost()} and {@link #setHost(EPPRegistryHost)} to get and set
 * the element.</li>
 * <li>&lt;registry:contact&gt; - The contact object policy information per RFC
 * 5733. Use {@link #getContact()} and {@link #setContact(EPPRegistryContact)}
 * to get and set the element.</li>
 * </ul>
 *
 * @see com.verisign.epp.codec.registry.v02.EPPRegistryZoneSummary
 * @see com.verisign.epp.codec.registry.v02.EPPRegistryServices
 * @see com.verisign.epp.codec.registry.v02.EPPRegistryDomain
 * @see com.verisign.epp.codec.registry.v02.EPPRegistryHost
 * @see com.verisign.epp.codec.registry.v02.EPPRegistryContact
 */
public class EPPRegistryZone implements EPPCodecComponent, EPPRegistryZoneInterface {

	/**
	 * Logger
	 */
	    private static Logger cat = LoggerFactory.getLogger(EPPRegistryZone.class);
	      

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

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

	/**
	 * XML tag name for the {@code group} attribute.
	 */
	private final static String ELM_GROUP = "group";

	/**
	 * XML tag name for the {@code createdDate} attribute.
	 */
	private final static String ELM_CRDATE = "crDate";

	/**
	 * XML tag name for the {@code createdBy} attribute.
	 */
	private final static String ELM_CRID = "crID";

	/**
	 * XML tag name for the {@code lastUpdatedDate} attribute.
	 */
	private final static String ELM_UPDATE = "upDate";

	/**
	 * XML tag name for the {@code lastUpdatedBy} attribute.
	 */
	private final static String ELM_UPID = "upID";

	/**
	 * XML tag name for the {@code updatedData} attribute.
	 */
	private final static String ELM_UNSUPPORTED_DATA = "unsupportedData";
	
	/**
	 * XML tag name for the {@code batchJobs} attribute.
	 */
	private final static String ELM_BATCH = "batch";

	/**
	 * XML tag name for the {@code systemZones} attribute.
	 */
	private final static String ELM_SYSTEM = "system";

	/**
	 * Possible values for the {@code unsupportedData} attribute.
	 */
	public static enum UnsupportedData {

		/**
		 * The server will fail the command that includes unsupported data.
		 */
		fail,

		/**
		 * The server will ignore the unsupported data and execute the command.
		 */
		ignore
	}

	/**
	 * Zone Name attribute
	 */
	private EPPRegistryZoneName name = null;

	/**
	 * Zone Group attribute
	 */
	private String group = null;

	/**
	 * Zone Services attribute
	 */
	private EPPRegistryServices services = null;

	/**
	 * Created by identifier attribute
	 */
	private String createdBy = null;

	/**
	 * Created date attribute
	 */
	private Date createdDate = null;

	/**
	 * Last updated identifier attribute
	 */
	private String lastUpdatedBy = null;

	/**
	 * Last updated date attribute
	 */
	private Date lastUpdatedDate = null;

	/**
	 * The OPTIONAL policy associated with receipt of unsupported data sent by
	 * the client to the server. The unsupported data may be an unsupported
	 * element or extension.
	 */
	private UnsupportedData unsupportedData = null;

	/**
	 * The OPTIONAL list of batch jobs
	 */
	private List<EPPRegistryBatchJob> batchJobs = new ArrayList<EPPRegistryBatchJob>();;

	/**
	 * System zones
	 */
	private List<EPPRegistryZoneName> systemZones = new ArrayList<EPPRegistryZoneName>();

	/**
	 * Domain policies attribute
	 */
	private EPPRegistryDomain domain = null;

	/**
	 * Host policies attribute
	 */
	private EPPRegistryHost host = null;

	/**
	 * Contact policies attribute
	 */
	private EPPRegistryContact contact = null;

	/**
	 * Default constructor. All non list attributes are initialized to
	 * {@code null}.<br>
	 * <br>
	 * Please make sure to set the required attributes before calling the
	 * {@link #encode(Document)} method:
	 *
	 * <ul>
	 * <li>{@link #setName(String)}</li>
	 * <li>{@link #setCreatedDate(Date)}</li>
	 * <li>{@link #setDomain(EPPRegistryDomain)}</li>
	 * <li>{@link #setHost(EPPRegistryHost)}</li>
	 * </ul>
	 */
	public EPPRegistryZone() {
	}

	/**
	 * Construct an {@code EPPRegistryZone} instance using a zone name as an
	 * aLabel.<br>
	 * <br>
	 * All other non list attributes are initialized to {@code null}. <br>
	 * Please make sure to set the required attributes before calling the
	 * {@link #encode(Document)} method:
	 *
	 * <ul>
	 * <li>{@link #setCreatedDate(Date)}</li>
	 * <li>{@link #setDomain(EPPRegistryDomain)}</li>
	 * <li>{@link #setHost(EPPRegistryHost)}</li>
	 * </ul>
	 *
	 * @param aName
	 *           fully qualified name of the zone object as an aLabel
	 */
	public EPPRegistryZone(String aName) {
		this.setName(aName);
	}

	/**
	 * Construct an {@code EPPRegistryZone} instance using a zone name.<br>
	 * <br>
	 * All other non list attributes are initialized to {@code null}. <br>
	 * Please make sure to set the required attributes before calling the
	 * {@link #encode(Document)} method:
	 *
	 * <ul>
	 * <li>{@link #setCreatedDate(Date)}</li>
	 * <li>{@link #setDomain(EPPRegistryDomain)}</li>
	 * <li>{@link #setHost(EPPRegistryHost)}</li>
	 * </ul>
	 *
	 * @param aName
	 *           fully qualified name of the zone object
	 */
	public EPPRegistryZone(EPPRegistryZoneName aName) {
		this.setName(aName);
	}

	/**
	 * Construct an {@code EPPRegistryZone} instance using a zone name as an
	 * aLabel, a create id and a create date. <br>
	 * <br>
	 * All other non list attributes are initialized to {@code null}.
	 * {@code phases} is initialized to empty {@code List}. <br>
	 * <br>
	 * Please make sure to set the required attributes before calling the
	 * {@link #encode(Document)} method:
	 *
	 * <ul>
	 * <li>{@link #setDomain(EPPRegistryDomain)}</li>
	 * <li>{@link #setHost(EPPRegistryHost)}</li>
	 * </ul>
	 *
	 * @param aName
	 *           fully qualified name of the zone as an aLabel
	 * @param aCreatedBy
	 *           identifier of the client that created the zone
	 * @param aCreatedDate
	 *           creation date of the zone
	 */
	public EPPRegistryZone(String aName, String aCreatedBy, Date aCreatedDate) {
		this(aName);
		this.createdBy = aCreatedBy;
		this.createdDate = aCreatedDate;
	}

	/**
	 * Construct an {@code EPPRegistryZone} instance using a zone name, a
	 * create id and a create date. <br>
	 * <br>
	 * All other non list attributes are initialized to {@code null}.
	 * {@code phases} is initialized to empty {@code List}. <br>
	 * <br>
	 * Please make sure to set the required attributes before calling the
	 * {@link #encode(Document)} method:
	 *
	 * <ul>
	 * <li>{@link #setDomain(EPPRegistryDomain)}</li>
	 * <li>{@link #setHost(EPPRegistryHost)}</li>
	 * </ul>
	 *
	 * @param aName
	 *           fully qualified name of the zone
	 * @param aCreatedBy
	 *           identifier of the client that created the zone
	 * @param aCreatedDate
	 *           creation date of the zone
	 */
	public EPPRegistryZone(EPPRegistryZoneName aName, String aCreatedBy, Date aCreatedDate) {
		this(aName);
		this.createdBy = aCreatedBy;
		this.createdDate = aCreatedDate;
	}

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

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

		// Zone Name
		EPPUtil.encodeComp(aDocument, root, this.name);

		// Group
		EPPUtil.encodeString(aDocument, root, this.group, EPPRegistryMapFactory.NS,
		      EPPRegistryMapFactory.NS_PREFIX + ":" + ELM_GROUP);

		// Services
		EPPUtil.encodeComp(aDocument, root, this.services);

		// Created By
		EPPUtil.encodeString(aDocument, root, this.createdBy, EPPRegistryMapFactory.NS,
		      EPPRegistryMapFactory.NS_PREFIX + ":" + ELM_CRID);

		// Created Date
		EPPUtil.encodeTimeInstant(aDocument, root, this.createdDate, EPPRegistryMapFactory.NS,
		      EPPRegistryMapFactory.NS_PREFIX + ":" + ELM_CRDATE);

		// Updated By
		EPPUtil.encodeString(aDocument, root, this.lastUpdatedBy, EPPRegistryMapFactory.NS,
		      EPPRegistryMapFactory.NS_PREFIX + ":" + ELM_UPID);

		// Update Date
		EPPUtil.encodeTimeInstant(aDocument, root, this.lastUpdatedDate, EPPRegistryMapFactory.NS,
		      EPPRegistryMapFactory.NS_PREFIX + ":" + ELM_UPDATE);
		
		// Unsupported Data
		if (this.hasUnsupportedData()) {
			EPPUtil.encodeString(aDocument, root, this.unsupportedData.toString(), EPPRegistryMapFactory.NS,
			      EPPRegistryMapFactory.NS_PREFIX + ":" + ELM_UNSUPPORTED_DATA);			
		}

		// Batch Jobs
		if (this.hasBatchJobs()) {
			Element theBatchElm = aDocument.createElementNS(EPPRegistryMapFactory.NS,
			      EPPRegistryMapFactory.NS_PREFIX + ":" + ELM_BATCH);
			root.appendChild(theBatchElm);

			EPPUtil.encodeCompList(aDocument, theBatchElm, this.batchJobs);
		}

		// System Zones
		if (this.hasSystemZones()) {
			Element theSystemElm = aDocument.createElementNS(EPPRegistryMapFactory.NS,
			      EPPRegistryMapFactory.NS_PREFIX + ":" + ELM_SYSTEM);
			root.appendChild(theSystemElm);

			EPPUtil.encodeCompList(aDocument, theSystemElm, this.systemZones);
		}

		// Domain
		EPPUtil.encodeComp(aDocument, root, this.domain);

		// Host
		EPPUtil.encodeComp(aDocument, root, this.host);

		// Contact
		EPPUtil.encodeComp(aDocument, root, this.contact);

		return root;
	}

	/**
	 * Decode the {@code EPPRegistryZone} attributes from the aElement DOM
	 * Element tree.
	 *
	 * @param aElement
	 *           Root DOM Element to decode {@code EPPRegistryZone} from.
	 *
	 * @exception EPPDecodeException
	 *               Unable to decode aElement
	 */
	@Override
	public void decode(Element aElement) throws EPPDecodeException {
		// Zone Name
		this.name = (EPPRegistryZoneName) EPPUtil.decodeComp(aElement, EPPRegistryMapFactory.NS,
		      EPPRegistryZoneName.ELM_ZONE_NAME, EPPRegistryZoneName.class);

		// Group
		this.group = EPPUtil.decodeString(aElement, EPPRegistryMapFactory.NS, ELM_GROUP);

		// Services
		this.services = (EPPRegistryServices) EPPUtil.decodeComp(aElement, EPPRegistryMapFactory.NS,
		      EPPRegistryServices.ELM_NAME, EPPRegistryServices.class);

		// Created By
		this.createdBy = EPPUtil.decodeString(aElement, EPPRegistryMapFactory.NS, ELM_CRID);

		// Created Date
		this.createdDate = EPPUtil.decodeTimeInstant(aElement, EPPRegistryMapFactory.NS, ELM_CRDATE);

		// Updated By
		this.lastUpdatedBy = EPPUtil.decodeString(aElement, EPPRegistryMapFactory.NS, ELM_UPID);

		// Updated Date
		this.lastUpdatedDate = EPPUtil.decodeTimeInstant(aElement, EPPRegistryMapFactory.NS, ELM_UPDATE);

		// Unsupported Data
		String theUnsupportedDataStr = EPPUtil.decodeString(aElement, EPPRegistryMapFactory.NS, ELM_UNSUPPORTED_DATA);
		if (theUnsupportedDataStr == null) {
			this.unsupportedData = null;
		}
		else {
			this.unsupportedData = UnsupportedData.valueOf(theUnsupportedDataStr);
		}
		
		// Batch Jobs
		Element theBatchElm = EPPUtil.getElementByTagNameNS(aElement, EPPRegistryMapFactory.NS, ELM_BATCH);
		if (theBatchElm != null) {
			this.batchJobs = EPPUtil.decodeCompList(theBatchElm, EPPRegistryMapFactory.NS,
			      EPPRegistryBatchJob.ELM_LOCALNAME, EPPRegistryBatchJob.class);
		}
		else {
			this.batchJobs = new ArrayList<EPPRegistryBatchJob>();
		}

		// System Zones
		Element theSystemZoneElm = EPPUtil.getElementByTagNameNS(aElement, EPPRegistryMapFactory.NS, ELM_SYSTEM);
		if (theSystemZoneElm != null) {
			this.systemZones = EPPUtil.decodeCompList(theSystemZoneElm, EPPRegistryMapFactory.NS,
			      EPPRegistryZoneName.ELM_ZONE, EPPRegistryZoneName.class);
		}
		else {
			this.systemZones = new ArrayList<EPPRegistryZoneName>();
		}

		// Domain
		this.domain = (EPPRegistryDomain) EPPUtil.decodeComp(aElement, EPPRegistryMapFactory.NS,
		      EPPRegistryDomain.ELM_NAME, EPPRegistryDomain.class);

		// Host
		this.host = (EPPRegistryHost) EPPUtil.decodeComp(aElement, EPPRegistryMapFactory.NS, EPPRegistryHost.ELM_NAME,
		      EPPRegistryHost.class);

		// Contact
		this.contact = (EPPRegistryContact) EPPUtil.decodeComp(aElement, EPPRegistryMapFactory.NS,
		      EPPRegistryContact.ELM_NAME, EPPRegistryContact.class);
	}

	/**
	 * implements a deep {@code EPPRegistryZone} compare.
	 *
	 * @param aObject
	 *           {@code EPPRegistryZone} 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 EPPRegistryZone)) {
			return false;
		}

		EPPRegistryZone theComp = (EPPRegistryZone) aObject;

		// Zone Name
		if (!EqualityUtil.equals(this.name, theComp.name)) {
			cat.error("EPPRegistryZone.equals(): name not equal");
			return false;
		}

		// Group
		if (!EqualityUtil.equals(this.group, theComp.group)) {
			cat.error("EPPRegistryZone.equals(): group not equal");
			return false;
		}

		// Created By
		if (!EqualityUtil.equals(this.createdBy, theComp.createdBy)) {
			cat.error("EPPRegistryZone.equals(): createdBy not equal");
			return false;
		}

		// Created Date
		if (!EqualityUtil.equals(this.createdDate, theComp.createdDate)) {
			cat.error("EPPRegistryZone.equals(): createdDate not equal");
			return false;
		}

		// Updated By
		if (!EqualityUtil.equals(this.lastUpdatedBy, theComp.lastUpdatedBy)) {
			cat.error("EPPRegistryZone.equals(): lastUpdatedBy not equal");
			return false;
		}

		// Update Date
		if (!EqualityUtil.equals(this.lastUpdatedDate, theComp.lastUpdatedDate)) {
			cat.error("EPPRegistryZone.equals(): lastUpdatedDate not equal");
			return false;
		}

		// Unsupported Data
		if (!EqualityUtil.equals(this.unsupportedData, theComp.unsupportedData)) {
			cat.error("EPPRegistryZone.equals(): unsupportedData not equal");
			return false;
		}
		
		// Batch Jobs
		if (!EqualityUtil.equals(this.batchJobs, theComp.batchJobs)) {
			cat.error("EPPRegistryZone.equals(): batchJobs not equal");
			return false;
		}

		// System Zones
		if (!EqualityUtil.equals(this.systemZones, theComp.systemZones)) {
			cat.error("EPPRegistryZone.equals(): systemZones not equal");
			return false;
		}

		// Services
		if (!EqualityUtil.equals(this.services, theComp.services)) {
			cat.error("EPPRegistryZone.equals(): services not equal");
			return false;
		}

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

		// Host
		if (!EqualityUtil.equals(this.host, theComp.host)) {
			cat.error("EPPRegistryZone.equals(): host not equal");
			return false;
		}

		// Contact
		if (!EqualityUtil.equals(this.contact, theComp.contact)) {
			cat.error("EPPRegistryZone.equals(): contact not equal");
			return false;
		}

		return true;
	}

	/**
	 * Validate the state of the {@code EPPRegistryZone} 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 EPPCodecException will contain a description of
	 * the error. throws EPPCodecException State error. This will contain the
	 * name of the attribute that is not valid.
	 *
	 * @throws EPPCodecException
	 *            Validation error
	 */
	void validateState() throws EPPCodecException {

		if (this.name == null) {
			throw new EPPCodecException("name required element is not set");
		}

		if (this.domain == null) {
			throw new EPPCodecException("domain required element is not set");
		}

		if (this.host == null) {
			throw new EPPCodecException("host required element is not set");
		}

	}

	/**
	 * Clone {@code EPPRegistryZone}.
	 *
	 * @return clone of {@code EPPRegistryZone}
	 *
	 * @exception CloneNotSupportedException
	 *               standard Object.clone exception
	 */
	@Override
	public Object clone() throws CloneNotSupportedException {
		EPPRegistryZone clone = (EPPRegistryZone) super.clone();

		// Batch Jobs
		if (this.batchJobs != null) {
			clone.batchJobs = (List) ((ArrayList) this.batchJobs).clone();
		}

		// System Zones
		if (this.systemZones != null) {
			clone.systemZones = (List) ((ArrayList) this.systemZones).clone();
		}

		// Services
		if (this.services != null) {
			clone.services = (EPPRegistryServices) this.services.clone();
		}

		// Domain
		if (this.domain != null) {
			clone.domain = (EPPRegistryDomain) this.domain.clone();
		}

		// Host
		if (this.host != null) {
			clone.host = (EPPRegistryHost) this.host.clone();
		}

		// Contact
		if (this.contact != null) {
			clone.contact = (EPPRegistryContact) this.contact.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 name defined?
	 *
	 * @return {@code true} if the name is defined; {@code false} otherwise.
	 */
	public boolean hasName() {
		return (this.name != null ? true : false);
	}

	/**
	 * Gets the name of zone.
	 *
	 * @return fully qualified zone name
	 */
	public EPPRegistryZoneName getName() {
		return this.name;
	}

	/**
	 * Sets the name of zone.
	 *
	 * @param aName
	 *           fully qualified zone name.
	 */
	public void setName(EPPRegistryZoneName aName) {
		this.name = aName;
	}

	/**
	 * Sets the name of zone as an aLabel.
	 *
	 * @param aName
	 *           fully qualified zone name as an aLabel.
	 */
	public void setName(String aName) {
		if (aName == null) {
			this.name = null;
		}
		else {
			this.name = new EPPRegistryZoneName(aName, EPPRegistryZoneName.Form.aLabel);

		}
	}

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

	/**
	 * Get services supported by the zone
	 *
	 * @return instance of {@link EPPRegistryServices} that lists namespace URIs
	 *         of the objects and object extensions supported by the zone
	 */
	public EPPRegistryServices getServices() {
		return this.services;
	}

	/**
	 * Set services supported by the zone
	 *
	 * @param services
	 *           instance of {@link EPPRegistryServices} that lists namespace
	 *           URIs of the objects and object extensions supported by the zone
	 */
	public void setServices(EPPRegistryServices services) {
		this.services = services;
	}

	/**
	 * Is the created by defined?
	 *
	 * @return {@code true} if the created by is defined; {@code false}
	 *         otherwise.
	 */
	public boolean hasCreatedBy() {
		return (this.createdBy != null ? true : false);
	}

	/**
	 * Get the identifier of the client that created the zone.
	 *
	 * @return the identifier of the client that created the zone
	 */
	public String getCreatedBy() {
		return this.createdBy;
	}

	/**
	 * Set the identifier of the client that created the zone
	 *
	 * @param createdBy
	 *           the identifier of the client that created the zone
	 */
	public void setCreatedBy(String createdBy) {
		this.createdBy = createdBy;
	}

	/**
	 * Is the created date defined?
	 *
	 * @return {@code true} if the created date is defined; {@code false}
	 *         otherwise.
	 */
	public boolean hasCreatedDate() {
		return (this.createdDate != null ? true : false);
	}

	/**
	 * Get zone creation date.
	 *
	 * @return zone creation date
	 */
	public Date getCreatedDate() {
		return this.createdDate;
	}

	/**
	 * set zone create date.
	 *
	 * @param createdDate
	 *           zone creation date
	 */
	public void setCreatedDate(Date createdDate) {
		this.createdDate = createdDate;
	}

	/**
	 * Is the last updated by defined?
	 *
	 * @return {@code true} if the last updated by is defined; {@code false}
	 *         otherwise.
	 */
	public boolean hasLastUpdatedBy() {
		return (this.lastUpdatedBy != null ? true : false);
	}

	/**
	 * Get the identifier of the client that last updated the zone object.
	 *
	 * @return the identifier of the client that last updated the zone object, or
	 *         {@code null} if the zone object has never been updated.
	 */
	public String getLastUpdatedBy() {
		return this.lastUpdatedBy;
	}

	/**
	 * Set the identifier of the client that last updated the zone object.
	 *
	 * @param lastUpdatedBy
	 *           the identifier of the client that last updated the zone object
	 */
	public void setLastUpdatedBy(String lastUpdatedBy) {
		this.lastUpdatedBy = lastUpdatedBy;
	}

	/**
	 * Is the last updated date defined?
	 *
	 * @return {@code true} if the last updated date is defined; {@code false}
	 *         otherwise.
	 */
	public boolean hasLastUpdatedDate() {
		return (this.lastUpdatedDate != null ? true : false);
	}

	/**
	 * Get the zone last updated date.
	 *
	 * @return the last updated date of the zone object, or {@code null} if the
	 *         zone has never been updated.
	 */
	public Date getLastUpdatedDate() {
		return this.lastUpdatedDate;
	}

	/**
	 * Set the zone last updated date.
	 *
	 * @param lastUpdatedDate
	 *           the last updated date of the zone object
	 */
	public void setLastUpdatedDate(Date lastUpdatedDate) {
		this.lastUpdatedDate = lastUpdatedDate;
	}

	/**
	 * Is the unsupported data policy defined?
	 *
	 * @return {@code true} if the unsupported data policy is defined; {@code false}
	 *         otherwise.
	 */
	public boolean hasUnsupportedData() {
		return (this.unsupportedData != null ? true : false);
	}

	/**
	 * Gets the unsupported data policy.
	 *
	 * @return unsupported data policy if defined; {@code null} otherwise.
	 */
	public UnsupportedData getUnsupportedData() {
		return this.unsupportedData;
	}

	/**
	 * Sets the unsupported data policy.
	 *
	 * @param aUnsupportedData
	 *           Unsupported data policy. Set to {@code null} to clear it.
	 */
	public void setUnsupportedData(UnsupportedData aUnsupportedData) {
		this.unsupportedData = aUnsupportedData;
	}
	
	
	/**
	 * Is the batch jobs defined?
	 *
	 * @return {@code true} if the batch jobs is defined; {@code false}
	 *         otherwise.
	 */
	public boolean hasBatchJobs() {
		return (this.batchJobs != null && !this.batchJobs.isEmpty() ? true : false);
	}

	/**
	 * Get the {@code List} of {@code EPPRegistryBatchJob} that specifies the
	 * batch jobs.
	 *
	 * @return the {@code List} of {@code EPPRegistryBatchJob} that specifies the
	 *         batch jobs
	 */
	public List<EPPRegistryBatchJob> getBatchJobs() {
		return this.batchJobs;
	}

	/**
	 * Set the {@code List} of {@code EPPRegistryBatchJob} that includes all of
	 * the batch jobs.
	 *
	 * @param aBatchJobs
	 *           the {@code List} of {@code EPPRegistryBatchJob} includes all of
	 *           the batch jobs.
	 */
	public void setBatchJobs(List<EPPRegistryBatchJob> aBatchJobs) {
		if (aBatchJobs == null) {
			this.batchJobs = new ArrayList<EPPRegistryBatchJob>();
		}
		else {
			this.batchJobs = aBatchJobs;
		}
	}

	/**
	 * Add a batch job to the list of batch jobs.
	 *
	 * @param aBatchJob
	 *           Batch job to add to the list
	 */
	public void addBatchJob(EPPRegistryBatchJob aBatchJob) {
		if (aBatchJob == null) {
			return;
		}

		if (this.batchJobs == null) {
			this.batchJobs = new ArrayList<EPPRegistryBatchJob>();
		}

		this.batchJobs.add(aBatchJob);
	}

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

	/**
	 * Get the {@code List} of {@code EPPRegistryZoneName} that specifies the
	 * system zones.
	 *
	 * @return the {@code List} of {@code EPPRegistryZoneName} that specifies the
	 *         system zones
	 */
	public List<EPPRegistryZoneName> getSystemZones() {
		return this.systemZones;
	}

	/**
	 * Set the {@code List} of {@code EPPRegistryZoneName} that includes all of
	 * the system zones.
	 *
	 * @param aSystemZones
	 *           the {@code List} of {@code EPPRegistryBatchJob} includes all of
	 *           the batch jobs.
	 */
	public void setSystemZones(List<EPPRegistryZoneName> aSystemZones) {
		if (aSystemZones == null) {
			this.systemZones = new ArrayList<EPPRegistryZoneName>();
		}
		else {
			this.systemZones = aSystemZones;

			// Ensure that the root element is set properly
			for (EPPRegistryZoneName theZone : this.systemZones) {
				theZone.setRootName(EPPRegistryZoneName.ELM_ZONE);
			}
		}
	}

	/**
	 * Add a zone to the list of system zones.
	 *
	 * @param aSystemZone
	 *           Zone to add to the list of system zones
	 */
	public void addSystemZone(EPPRegistryZoneName aSystemZone) {
		if (aSystemZone == null) {
			return;
		}

		if (this.systemZones == null) {
			this.systemZones = new ArrayList<EPPRegistryZoneName>();
		}

		// Ensure the root element is properly set
		aSystemZone.setRootName(EPPRegistryZoneName.ELM_ZONE);

		this.systemZones.add(aSystemZone);
	}

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

	/**
	 * Get the domain name object policy information.
	 *
	 * @return the domain name object policy information per RFC 5731
	 */
	public EPPRegistryDomain getDomain() {
		return this.domain;
	}

	/**
	 * Set the domain name object policy information.
	 *
	 * @param domain
	 *           the domain name object policy information per RFC 5731
	 */
	public void setDomain(EPPRegistryDomain domain) {
		this.domain = domain;
	}

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

	/**
	 * Get the host object policy information.
	 *
	 * @return the host object policy information per RFC 5732
	 */
	public EPPRegistryHost getHost() {
		return this.host;
	}

	/**
	 * Set the host object policy information.
	 *
	 * @param host
	 *           the host object policy information per RFC 5732
	 */
	public void setHost(EPPRegistryHost host) {
		this.host = host;
	}

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

	/**
	 * Get the contact object policy information.
	 *
	 * @return the contact object policy information per RFC 5733.
	 */
	public EPPRegistryContact getContact() {
		return this.contact;
	}

	/**
	 * Set the contact object policy information.
	 *
	 * @param contact
	 *           the contact object policy information per RFC 5733.
	 */
	public void setContact(EPPRegistryContact contact) {
		this.contact = contact;
	}

	/**
	 * Is the zone group defined?
	 *
	 * @return {@code true} if the zone group is defined; {@code false}
	 *         otherwise.
	 */
	public boolean hasGroup() {
		return (this.group != null ? true : false);
	}

	/**
	 * Gets the zone group.
	 *
	 * @return server defined grouping of zones that the zone belongs to with
	 *         similar features and policies
	 */
	public String getGroup() {
		return this.group;
	}

	/**
	 * Sets the zone group.
	 *
	 * @param aGroup
	 *           server defined grouping of zones that the zone belongs to with
	 *           similar features and policies
	 */
	public void setGroup(String aGroup) {
		this.group = aGroup;
	}

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

}
