/***********************************************************
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.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Properties;
import java.util.Set;
import java.util.Vector;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
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 javax.xml.transform.stream.StreamSource;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.DocumentFragment;
import org.w3c.dom.Element;
import org.w3c.dom.Text;

import com.verisign.epp.exception.EPPException;
import com.verisign.epp.util.EPPEnv;
import com.verisign.epp.util.EPPSchemaCachingParser;
import com.verisign.epp.util.EPPXMLErrorHandler;
import com.verisign.epp.util.Environment;
import com.verisign.epp.util.TestThread;

import junit.framework.Assert;
import junit.framework.Test;
import junit.framework.TestCase;
import junit.framework.TestSuite;

/**
 * Is a unit test of the com.verisign.epp.codec.gen package. The unit test will
 * execute, gather statistics, and output the results of a test of each
 * com.verisign.epp.codec.gen package concrete <code>EPPMessage</code>'s and
 * their expected <code>EPPResponse</code>. The unit test is dependent on the
 * use of <a href=http://www.mcwestcorp.com/Junit.html>JUNIT 3.5</a><br>
 * <br>
 * The test <code>EPPResponse</code> is duplicated for <code>EPPMessage</code>
 * that do not contain a specialized <code>EPPResponse</code> to provide an
 * example of a full <code>EPPMessage</code> transaction. All of the
 * com.verisign.epp.codec.gen package <code>EPPMessage</code> classes are
 * associated with <code>EPPResponse</code>. <br>
 * <br>
 *
 * @author $Author: jim $
 * @version $Revision: 1.8 $
 */
public class EPPCodecTst extends TestCase {
	/**
	 * Number of unit test iterations to run. This is set in
	 * <code>EPPCodecTst.main</code>
	 */
	static private long numIterations = 1;

	/**
	 * Has the test been initialized?
	 */
	private static boolean initialized = false;

	private static final String NAMESPACE_TRANSFORM_XSL = "<?xml version=\"1.0\"?>"
	      + "<xsl:stylesheet xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\" version=\"1.0\">"
	      + "  <xsl:output method=\"xml\" encoding=\"utf-8\" indent=\"yes\"/>"
	      + "  <xsl:param name=\"use-this-prefix\"/>" + "  <xsl:template match=\"*[namespace-uri(.)]\">"
	      + "    <xsl:element name=\"{$use-this-prefix}{local-name()}\" namespace=\"{namespace-uri(.)}\">"
	      + "      <xsl:copy-of select=\"@*\"/>" + "      <xsl:apply-templates/>" + "    </xsl:element>"
	      + "  </xsl:template>" + "</xsl:stylesheet>";

	/**
	 * Allocates an <code>EPPCodecTst</code> with a logical name. The constructor
	 * will initialize the base class <code>TestCase</code> with the logical
	 * name.
	 *
	 * @param aName
	 *           Logical name of the test
	 */
	public EPPCodecTst(String aName) {
		super(aName);
	}

	/**
	 * Unit test of <code>EPPGreeting</code>. There is no response associated
	 * with <code>EPPGreeting</code> other than a <code>EPPLoginCmd</code> which
	 * is testing in <code>testLogin</code>.<br>
	 * <br>
	 * This test will be invoked by JUNIT, since it is prefixed with "test" and
	 * is a public method.
	 */
	public void testGreeting() {
		printStart("testGreeting");

		EPPGreeting theGreeting = new EPPGreeting();

		// Add services
		EPPServiceMenu serviceMenu = theGreeting.getServiceMenu();
		Vector services = new Vector();
		EPPService service = new EPPService("domain", "urn:ietf:params:xml:ns:domain-1.0");
		service.setServiceType(0);

		services.addElement(service);

		Vector statements = new Vector();

		// DCP Statement Element
		EPPStatement statement = new EPPStatement();

		// DCP Receipient element
		EPPRecipient recipient = new EPPRecipient();
		recipient.setOther(true);
		recipient.addOurs("My description");
		recipient.setPublic(true);

		// DCP Purpose element
		EPPPurpose purpose = new EPPPurpose(true, true, true, true);

		Assert.assertTrue(purpose.isAdmin());
		Assert.assertTrue(purpose.isContact());
		Assert.assertTrue(purpose.isOther());
		Assert.assertTrue(purpose.isProv());

		purpose.setAdmin(false);
		purpose.setProv(false);

		statement.setRecipient(recipient);
		statement.setPurpose(purpose);
		statement.setRetention(EPPStatement.RETENTION_BUSINESS);

		EPPStatement statement1 = new EPPStatement();
		EPPPurpose purpose2 = new EPPPurpose();
		purpose2.setAdmin(true);
		purpose2.setProv(true);
		statement1.setRecipient(recipient);
		statement1.setPurpose(purpose2);
		statement1.setRetention(EPPStatement.RETENTION_INDEFINITE);

		statements.addElement(statement);
		statements.addElement(statement1);

		serviceMenu.setObjectServices(services);

		// serviceMenu.setExtensionServices(extservices);
		EPPDcp dcp = new EPPDcp(EPPDcp.ACCESS_ALL, statements);

		theGreeting.setDcp(dcp);

		theGreeting.setServer("Example Company EPP server epp.example.com");
		theGreeting.setServerDate(new Date());

		// These settings are required since they are the default values, but used
		// to demonstrate the use of the API.
		theGreeting.getServiceMenu().setVersion("1.0");

		Vector langs = new Vector();
		langs.addElement("en");
		langs.addElement("fr");
		theGreeting.getServiceMenu().setLangs(langs);

		EPPEncodeDecodeStats stats = testEncodeDecode(theGreeting);
		System.out.println(stats);

		printEnd("testGreeting");
	}

	/**
	 * Unit test of <code>EPPLoginCmd</code>. The response to
	 * <code>EPPLoginCmd</code> is <code>EPPResponse</code>. <br>
	 * This test will be invoked by JUNIT, since it is prefixed with "test" and
	 * is a public method.
	 */
	public void testLogin() {
		printStart("testLogin");

		Vector services = new Vector();

		// Object services
		EPPService service = new EPPService("domain", "urn:ietf:params:xml:ns:domain-1.0");
		service.setServiceType(EPPService.OBJ_SERVICE);

		services.addElement(service);

		// Extension services
		Vector extservices = new Vector();
		EPPService extservice = new EPPService("domain-ext", "urn:ietf:params:xml:ns:domainext-1.0");
		extservice.setServiceType(EPPService.EXT_SERVICE);
		extservices.addElement(extservice);

		EPPLoginCmd theCommand = new EPPLoginCmd("ABC-12345", "ClientX", "foo-BAR2", "bar-FOO2");

		theCommand.setServices(services);
		theCommand.setExtensionServices(extservices);

		// These settings are required since they are the default values, but used
		// to demonstrate the use of the API.
		theCommand.setVersion("1.0");
		theCommand.setLang("en");

		EPPEncodeDecodeStats commandStats = testEncodeDecode(theCommand);
		System.out.println(commandStats);

		// Encode Login Response (Standard EPPResponse)
		EPPResponse theResponse;
		EPPTransId respTransId = new EPPTransId(theCommand.getTransId(), "54321-XYZ");
		theResponse = new EPPResponse(respTransId);
		theResponse.setResult(EPPResult.SUCCESS);

		EPPEncodeDecodeStats responseStats = testEncodeDecode(theResponse);
		System.out.println(responseStats);

		printEnd("testLogin");
	}

	/**
	 * Unit test of <code>EPPLogoutCmd</code>. The response to
	 * <code>EPPLogoutCmd</code> is <code>EPPResponse</code>. <br>
	 * This test will be invoked by JUNIT, since it is prefixed with "test" and
	 * is a public method.
	 */
	public void testLogout() {
		printStart("testLogout");

		EPPLogoutCmd theCommand = new EPPLogoutCmd("ABC-12345");

		EPPEncodeDecodeStats commandStats = testEncodeDecode(theCommand);
		System.out.println(commandStats);

		// Encode Logout Response (Standard EPPResponse)
		EPPResponse theResponse;
		EPPTransId respTransId = new EPPTransId(theCommand.getTransId(), "54321-XYZ");
		theResponse = new EPPResponse(respTransId);
		theResponse.setResult(EPPResult.SUCCESS_END_SESSION);

		EPPEncodeDecodeStats responseStats = testEncodeDecode(theResponse);
		System.out.println(responseStats);

		printEnd("testLogout");
	}

	/**
	 * Unit test of <code>EPPResponse</code>. This will test
	 * <code>EPPResponse</code> with various attribute values. <br>
	 * This test will be invoked by JUNIT, since it is prefixed with "test" and
	 * is a public method.
	 */
	public void testResponse() {
		printStart("testResponse");

		// Success
		EPPResponse theResponse;
		theResponse = new EPPResponse(new EPPTransId("ABC-12345", "54321-XYZ"));
		theResponse.setResult(EPPResult.SUCCESS);

		System.out.println("EPPResponse.toString = [" + theResponse + "]");

		EPPEncodeDecodeStats responseStats = testEncodeDecode(theResponse);
		System.out.println(responseStats);

		// Success with extValue
		theResponse = new EPPResponse(new EPPTransId("ABC-12345", "54321-XYZ"));

		EPPResult theResult = new EPPResult(EPPResult.SUCCESS);
		theResult.addValue(new EPPValue("<helloWorld/>"));
		theResult.addExtValue(new EPPExtValue("My Reason"));
		theResponse.setResult(theResult);

		System.out.println("EPPResponse.toString = [" + theResponse + "]");

		responseStats = testEncodeDecode(theResponse);
		System.out.println(responseStats);

		// Error response
		theResponse = new EPPResponse(new EPPTransId("ABC-12345", "54321-XYZ"));

		theResult = new EPPResult(EPPResult.PARAM_VALUE_POLICY_ERROR);
		theResult.addExtValue(new EPPExtValue("error reason"));
		theResponse.setResult(theResult);

		System.out.println("EPPResponse.toString = [" + theResponse + "]");

		responseStats = testEncodeDecode(theResponse);
		System.out.println(responseStats);

		printEnd("testResponse");
	}

	/**
	 * Test unhandled namespaces poll message, which is a poll message that
	 * contains EPP extensions (object or command / response) that are not
	 * supported by the client based on the client login services.
	 */
	public void testUnhandledNamespaces() {
		printStart("testUnhandledNamespaces");

		EPPResponse theResponse;
		theResponse = new EPPResponse(new EPPTransId("ABC-12345", "54321-XYZ"));
		EPPResult theResult = new EPPResult(EPPResult.SUCCESS_POLL_MSG);
		// Unsupported urn:ietf:params:xml:ns:domain-1.0 extension
		theResult.addExtValue(new EPPExtValue(
		      "Message incomplete due to missing \"urn:ietf:params:xml:ns:domain-1.0\" objURI in login services",
		      "<objURI>urn:ietf:params:xml:ns:domain-1.0</objURI>"));
		// Unsupported urn:ietf:params:xml:ns:changePoll-1.0 extension
		theResult.addExtValue(new EPPExtValue(
		      "Message incomplete due to missing \"urn:ietf:params:xml:ns:changePoll-1.0\" extURI in login services",
		      "<extURI>urn:ietf:params:xml:ns:changePoll-1.0</extURI>"));
		theResponse.setResult(theResult);
		theResponse
		      .setMsgQueue(new EPPMsgQueue(Long.valueOf(1), "201", new Date(), "Registry initiated update of domain."));

		System.out.println("EPPResponse.toString = [" + theResponse + "]");

		EPPEncodeDecodeStats responseStats = testEncodeDecode(theResponse);
		System.out.println(responseStats);

		printEnd("testUnhandledNamespaces");
	}

	/**
	 * A stubbed out test extension used for tests that simply add, remove, check
	 * for existence and get an extension. This extension does not really encode
	 * or decode XML, so it should not be used with
	 * <code>EPPCodecTst.testEncodeDecode(EPPMessage)</code>.
	 */
	private static class Test1Extension implements EPPCodecComponent {

		public static final String NS = "urn:ietf:params:xml:ns:epp:test1";

		public Element encode(Document aDocument) throws EPPEncodeException {
			Element root = aDocument.createElementNS(EPPCodec.NS, NS);
			return root;
		}

		public void decode(Element aElement) throws EPPDecodeException {

		}

		public Object clone() throws CloneNotSupportedException {
			return null;
		}

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

	/**
	 * A stubbed out test extension used for tests that simply add, remove, check
	 * for existence and get an extension. This extension does not really encode
	 * or decode XML, so it should not be used with
	 * {@code EPPCodecTst.testEncodeDecode(EPPMessage)}.
	 */
	private static class Test2Extension implements EPPCodecComponent {

		public static final String NS = "http://www.verisign-grs.com/epp/test2";

		public Element encode(Document aDocument) throws EPPEncodeException {
			Element root = aDocument.createElementNS(EPPCodec.NS, NS);
			return root;
		}

		public void decode(Element aElement) throws EPPDecodeException {

		}

		public Object clone() throws CloneNotSupportedException {
			return null;
		}

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

	}

	/**
	 * A stubbed out test extension used for tests use of an unsupported XML
	 * namespace. The namespace in this case does not include a supported suffix.
	 */
	private static class Test1UnsupportedNamespaceExtension implements EPPCodecComponent {

		public static final String NS = "http://www.verisign-grs.com/epp/";

		public Element encode(Document aDocument) throws EPPEncodeException {
			Element root = aDocument.createElementNS(EPPCodec.NS, NS);
			return root;
		}

		public void decode(Element aElement) throws EPPDecodeException {

		}

		public Object clone() throws CloneNotSupportedException {
			return null;
		}

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

	}

	/**
	 * A stubbed out test extension used for tests use of an unsupported XML
	 * namespace. The namespace in this case does not include a supported
	 * separator.
	 */
	private static class Test2UnsupportedNamespaceExtension implements EPPCodecComponent {

		public static final String NS = "invalid;namespace";

		public Element encode(Document aDocument) throws EPPEncodeException {
			Element root = aDocument.createElementNS(EPPCodec.NS, NS);
			return root;
		}

		public void decode(Element aElement) throws EPPDecodeException {

		}

		public Object clone() throws CloneNotSupportedException {
			return null;
		}

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

	}

	/**
	 * Unit test for using {@code EPPCommand.getExtension(Class, boolean)}.
	 */
	public void testCommandGetExtensionFailOnDuplicate() {
		printStart("testCommandGetExtensionFailOnDuplicate");

		EPPPollCmd theCommand = new EPPPollCmd("ABC-12345", "req");

		EPPCodecComponent theExt1 = new Test1Extension();
		theCommand.addExtension(theExt1);

		EPPCodecComponent theExt2 = new Test2Extension();
		theCommand.addExtension(theExt2);

		try {
			Assert.assertEquals(theExt1, theCommand.getExtension(Test1Extension.class, true));
			Assert.assertEquals(theExt2, theCommand.getExtension(Test2Extension.class, true));
		}
		catch (EPPDuplicateExtensionException e) {
			Assert.fail("testCommandGetExtensionFailOnDuplicate: Failed on duplicate: " + e);
		}

		// Add duplicates
		theCommand.addExtension(new Test1Extension());
		theCommand.addExtension(new Test2Extension());

		// Ensure that duplicate returns first inserted without duplicate test
		try {
			Assert.assertEquals(theExt1, theCommand.getExtension(Test1Extension.class, false));
		}
		catch (EPPDuplicateExtensionException e) {
			Assert.fail(
			      "testCommandGetExtensionFailOnDuplicate: Failed on getting duplicate extension with failOnDuplicate parameter as false: "
			            + e);
		}

		// Test that duplicate extension 1 is caught
		try {
			theCommand.getExtension(Test1Extension.class, true);
			Assert.fail(
			      "testCommandGetExtensionFailOnDuplicate: Should have failed with EPPDuplicateExtensionException for Test1Extension.class");
		}
		catch (EPPDuplicateExtensionException e) {
			Assert.assertEquals(theExt1.getClass(), e.getExtension().getClass());
		}

		// Test that duplicate extension 2 is caught
		try {
			theCommand.getExtension(Test2Extension.class, true);
			Assert.fail(
			      "testCommandGetExtensionFailOnDuplicate: Should have failed with EPPDuplicateExtensionException for Test2Extension.class");
		}
		catch (EPPDuplicateExtensionException e) {
			Assert.assertEquals(theExt2.getClass(), e.getExtension().getClass());
		}

		printEnd("testCommandGetExtensionFailOnDuplicate");
	}

	/**
	 * Unit test of using {code EPPUtil.findDuplicateExtNamespaces(List)} and
	 * the corresponding {@code EPPCommand.findDuplicateExtNamespaces()} and
	 * {@code EPPResponse.findDuplicateExtNamespaces()}. Test the following:<br>
	 * <ol>
	 * <li>Test that no duplicates are found
	 * <li>Test that duplicates are found when there are a single set of
	 * duplicates
	 * <li>Test that multiple duplicate instances result in the same set of found
	 * duplicates.
	 * </ol>
	 */
	public void testFindDuplicateExtNamespaces() {
		printStart("testFindDuplicateExtNamespaces");

		EPPPollCmd theCommand = new EPPPollCmd("ABC-12345", "req");

		// First instances
		theCommand.addExtension(new Test1Extension());
		theCommand.addExtension(new Test2Extension());

		Set<String> theDuplicateExtNamespaces = EPPUtil.findDuplicateExtNamespaces(theCommand.getExtensions());
		Assert.assertNull(theDuplicateExtNamespaces);

		theDuplicateExtNamespaces = theCommand.findDuplicateExtNamespaces();
		Assert.assertNull(theDuplicateExtNamespaces);

		// Second instances
		theCommand.addExtension(new Test1Extension());
		theCommand.addExtension(new Test2Extension());

		theDuplicateExtNamespaces = EPPUtil.findDuplicateExtNamespaces(theCommand.getExtensions());
		Assert.assertNotNull(theDuplicateExtNamespaces);
		Assert.assertEquals(2, theDuplicateExtNamespaces.size());
		Assert.assertTrue(theDuplicateExtNamespaces.contains(Test1Extension.NS));
		Assert.assertTrue(theDuplicateExtNamespaces.contains(Test2Extension.NS));

		theDuplicateExtNamespaces = theCommand.findDuplicateExtNamespaces();
		Assert.assertNotNull(theDuplicateExtNamespaces);
		Assert.assertEquals(2, theDuplicateExtNamespaces.size());
		Assert.assertTrue(theDuplicateExtNamespaces.contains(Test1Extension.NS));
		Assert.assertTrue(theDuplicateExtNamespaces.contains(Test2Extension.NS));

		// Third instances
		theCommand.addExtension(new Test1Extension());
		theCommand.addExtension(new Test2Extension());

		theDuplicateExtNamespaces = EPPUtil.findDuplicateExtNamespaces(theCommand.getExtensions());
		Assert.assertNotNull(theDuplicateExtNamespaces);
		Assert.assertEquals(2, theDuplicateExtNamespaces.size());
		Assert.assertTrue(theDuplicateExtNamespaces.contains(Test1Extension.NS));
		Assert.assertTrue(theDuplicateExtNamespaces.contains(Test2Extension.NS));

		theDuplicateExtNamespaces = theCommand.findDuplicateExtNamespaces();
		Assert.assertNotNull(theDuplicateExtNamespaces);
		Assert.assertEquals(2, theDuplicateExtNamespaces.size());
		Assert.assertTrue(theDuplicateExtNamespaces.contains(Test1Extension.NS));
		Assert.assertTrue(theDuplicateExtNamespaces.contains(Test2Extension.NS));

		// Test EPPResponse
		EPPResponse theResponse = new EPPResponse();
		theResponse.addExtension(new Test1Extension());
		theResponse.addExtension(new Test2Extension());
		theResponse.addExtension(new Test1Extension());
		theResponse.addExtension(new Test2Extension());

		theDuplicateExtNamespaces = theResponse.findDuplicateExtNamespaces();
		Assert.assertNotNull(theDuplicateExtNamespaces);
		Assert.assertEquals(2, theDuplicateExtNamespaces.size());
		Assert.assertTrue(theDuplicateExtNamespaces.contains(Test1Extension.NS));
		Assert.assertTrue(theDuplicateExtNamespaces.contains(Test2Extension.NS));

		printEnd("testFindDuplicateExtNamespaces");
	}

	/**
	 * Unit test of using
	 * {@code EPPUtil.findUnsupportedExtNamespaces(List, List)} and the
	 * corresponding {@code EPPCommand.findUnsupportedExtNamespaces(List)} and
	 * {@code EPPResponse.findUnsupportedExtNamespaces(List)}. Test the
	 * following:<br>
	 * <ol>
	 * <li>Test that no unsupported extensions found
	 * <li>Test that there is an unsupported extension.
	 * <li>Test that all extensions are unsupported.
	 * </ol>
	 */
	public void testFindUnsupportedExtNamespaces() {
		printStart("testFindUnsupportedExtNamespaces");

		EPPPollCmd theCommand = new EPPPollCmd("ABC-12345", "req");

		// Test that no unsupported extensions found
		String[] theSupportedExtensionsArray = { Test1Extension.NS, Test2Extension.NS };
		List<String> theSuppportedExtensionsList = Arrays.asList(theSupportedExtensionsArray);
		theCommand.addExtension(new Test1Extension());
		theCommand.addExtension(new Test2Extension());

		Set<String> theUnsupportedExtNamespaces = EPPUtil.findUnsupportedExtNamespaces(theSuppportedExtensionsList,
		      theCommand.getExtensions());
		Assert.assertNull(theUnsupportedExtNamespaces);

		theUnsupportedExtNamespaces = theCommand.findUnsupportedExtNamespaces(theSuppportedExtensionsList);
		Assert.assertNull(theUnsupportedExtNamespaces);

		// Test that one unsupported extension found
		theCommand.addExtension(new Test1Extension());
		theCommand.addExtension(new Test2Extension());
		theSuppportedExtensionsList = new ArrayList<String>();
		theSuppportedExtensionsList.add(Test1Extension.NS);

		theUnsupportedExtNamespaces = EPPUtil.findUnsupportedExtNamespaces(theSuppportedExtensionsList,
		      theCommand.getExtensions());
		Assert.assertNotNull(theUnsupportedExtNamespaces);
		Assert.assertEquals(1, theUnsupportedExtNamespaces.size());
		Assert.assertTrue(theUnsupportedExtNamespaces.contains(Test2Extension.NS));

		theUnsupportedExtNamespaces = theCommand.findUnsupportedExtNamespaces(theSuppportedExtensionsList);
		Assert.assertNotNull(theUnsupportedExtNamespaces);
		Assert.assertEquals(1, theUnsupportedExtNamespaces.size());
		Assert.assertTrue(theUnsupportedExtNamespaces.contains(Test2Extension.NS));

		// Test that all extensions are unsupported.
		EPPResponse theResponse = new EPPResponse();
		theResponse.addExtension(new Test1Extension());
		theResponse.addExtension(new Test2Extension());

		theUnsupportedExtNamespaces = EPPUtil.findUnsupportedExtNamespaces(null, theResponse.getExtensions());
		Assert.assertNotNull(theUnsupportedExtNamespaces);
		Assert.assertEquals(2, theUnsupportedExtNamespaces.size());
		Assert.assertTrue(theUnsupportedExtNamespaces.contains(Test1Extension.NS));
		Assert.assertTrue(theUnsupportedExtNamespaces.contains(Test2Extension.NS));

		theUnsupportedExtNamespaces = theResponse.findUnsupportedExtNamespaces(null);
		Assert.assertNotNull(theUnsupportedExtNamespaces);
		Assert.assertEquals(2, theUnsupportedExtNamespaces.size());
		Assert.assertTrue(theUnsupportedExtNamespaces.contains(Test1Extension.NS));
		Assert.assertTrue(theUnsupportedExtNamespaces.contains(Test2Extension.NS));

		printEnd("testFindUnsupportedExtNamespaces");
	}

	/**
	 * Unit test of using
	 * {@code EPPUtil.findUnsupportedExtNamespaceSuffixes(List, List)} and the
	 * corresponding {@code EPPCommand.findUnsupportedExtNamespaceSuffixes(List)}
	 * and {@code EPPResponse.findUnsupportedExtNamespaceSuffixes(List)}. Test
	 * the following:<br>
	 * <ol>
	 * <li>Test that no unsupported extensions found
	 * <li>Test that there is an unsupported extension.
	 * <li>Test that all extensions are unsupported.
	 * <li>Test inclusion of an unsupported namespace.
	 * </ol>
	 */
	public void testFindUnsupportedExtNamespaceSuffixes() {
		printStart("testFindUnsupportedExtNamespaceSuffixes");

		EPPPollCmd theCommand = new EPPPollCmd("ABC-12345", "req");

		// Test that no unsupported extensions found
		String[] theSupportedExtensionsArray = { EPPUtil.namespaceToNamespaceSuffix(Test1Extension.NS),
		      EPPUtil.namespaceToNamespaceSuffix(Test2Extension.NS) };
		List<String> theSupportedExtensionsList = Arrays.asList(theSupportedExtensionsArray);
		theCommand.addExtension(new Test1Extension());
		theCommand.addExtension(new Test2Extension());

		Set<String> theUnsupportedExtNamespaceSuffixes = EPPUtil
		      .findUnsupportedExtNamespaceSuffixes(theSupportedExtensionsList, theCommand.getExtensions());
		Assert.assertNull(theUnsupportedExtNamespaceSuffixes);

		theUnsupportedExtNamespaceSuffixes = theCommand.findUnsupportedExtNamespaceSuffixes(theSupportedExtensionsList);
		Assert.assertNull(theUnsupportedExtNamespaceSuffixes);

		// Test that one unsupported extension found
		theCommand.addExtension(new Test1Extension());
		theCommand.addExtension(new Test2Extension());
		theSupportedExtensionsList = new ArrayList<String>();
		theSupportedExtensionsList.add(EPPUtil.namespaceToNamespaceSuffix(Test1Extension.NS));

		theUnsupportedExtNamespaceSuffixes = EPPUtil.findUnsupportedExtNamespaceSuffixes(theSupportedExtensionsList,
		      theCommand.getExtensions());
		Assert.assertNotNull(theUnsupportedExtNamespaceSuffixes);
		Assert.assertEquals(1, theUnsupportedExtNamespaceSuffixes.size());
		Assert.assertTrue(
		      theUnsupportedExtNamespaceSuffixes.contains(EPPUtil.namespaceToNamespaceSuffix(Test2Extension.NS)));

		theUnsupportedExtNamespaceSuffixes = theCommand.findUnsupportedExtNamespaceSuffixes(theSupportedExtensionsList);
		Assert.assertNotNull(theUnsupportedExtNamespaceSuffixes);
		Assert.assertEquals(1, theUnsupportedExtNamespaceSuffixes.size());
		Assert.assertTrue(
		      theUnsupportedExtNamespaceSuffixes.contains(EPPUtil.namespaceToNamespaceSuffix(Test2Extension.NS)));

		// Test that all extensions are unsupported.
		EPPResponse theResponse = new EPPResponse();
		theResponse.addExtension(new Test1Extension());
		theResponse.addExtension(new Test2Extension());

		theUnsupportedExtNamespaceSuffixes = EPPUtil.findUnsupportedExtNamespaceSuffixes(null,
		      theResponse.getExtensions());
		Assert.assertNotNull(theUnsupportedExtNamespaceSuffixes);
		Assert.assertEquals(2, theUnsupportedExtNamespaceSuffixes.size());
		Assert.assertTrue(
		      theUnsupportedExtNamespaceSuffixes.contains(EPPUtil.namespaceToNamespaceSuffix(Test1Extension.NS)));
		Assert.assertTrue(
		      theUnsupportedExtNamespaceSuffixes.contains(EPPUtil.namespaceToNamespaceSuffix(Test2Extension.NS)));

		theUnsupportedExtNamespaceSuffixes = theResponse.findUnsupportedExtNamespaceSuffixes(null);
		Assert.assertNotNull(theUnsupportedExtNamespaceSuffixes);
		Assert.assertEquals(2, theUnsupportedExtNamespaceSuffixes.size());
		Assert.assertTrue(
		      theUnsupportedExtNamespaceSuffixes.contains(EPPUtil.namespaceToNamespaceSuffix(Test1Extension.NS)));
		Assert.assertTrue(
		      theUnsupportedExtNamespaceSuffixes.contains(EPPUtil.namespaceToNamespaceSuffix(Test2Extension.NS)));

		// Test inclusion of an unsupported namespace, which will match the full
		// namespace
		theResponse = new EPPResponse();
		theResponse.addExtension(new Test1UnsupportedNamespaceExtension());
		theResponse.addExtension(new Test2UnsupportedNamespaceExtension());
		theUnsupportedExtNamespaceSuffixes = EPPUtil.findUnsupportedExtNamespaceSuffixes(null,
		      theResponse.getExtensions());
		Assert.assertNotNull(theUnsupportedExtNamespaceSuffixes);
		Assert.assertEquals(2, theUnsupportedExtNamespaceSuffixes.size());
		Assert.assertTrue(theUnsupportedExtNamespaceSuffixes
		      .contains(EPPUtil.namespaceToNamespaceSuffix(Test1UnsupportedNamespaceExtension.NS)));
		Assert.assertTrue(theUnsupportedExtNamespaceSuffixes
		      .contains(EPPUtil.namespaceToNamespaceSuffix(Test2UnsupportedNamespaceExtension.NS)));

		printEnd("testFindUnsupportedExtNamespaceSuffixes");
	}

	/**
	 * Unit test for using {code EPPResponse.getExtension(Class, boolean)}.
	 */
	public void testResponseGetExtensionFailOnDuplicate() {
		printStart("testResponseGetExtensionFailOnDuplicate");

		EPPResponse theResponse = new EPPResponse(new EPPTransId("ABC-12345", "54321-XYZ"));
		theResponse.setResult(EPPResult.SUCCESS);

		EPPCodecComponent theExt1 = new Test1Extension();
		theResponse.addExtension(theExt1);

		EPPCodecComponent theExt2 = new Test2Extension();
		theResponse.addExtension(theExt2);

		try {
			Assert.assertEquals(theExt1, theResponse.getExtension(Test1Extension.class, true));
			Assert.assertEquals(theExt2, theResponse.getExtension(Test2Extension.class, true));
		}
		catch (EPPDuplicateExtensionException e) {
			Assert.fail("testResponseGetExtensionFailOnDuplicate: Failed on duplicate: " + e);
		}

		// Add duplicates
		theResponse.addExtension(new Test1Extension());
		theResponse.addExtension(new Test2Extension());

		// Ensure that duplicate returns first inserted without duplicate test
		try {
			Assert.assertEquals(theExt1, theResponse.getExtension(Test1Extension.class, false));
		}
		catch (EPPDuplicateExtensionException e) {
			Assert.fail(
			      "testResponseGetExtensionFailOnDuplicate: Failed on getting duplicate extension with failOnDuplicate parameter as false: "
			            + e);
		}

		// Test that duplicate extension 1 is caught
		try {
			theResponse.getExtension(Test1Extension.class, true);
			Assert.fail(
			      "testResponseGetExtensionFailOnDuplicate: Should have failed with EPPDuplicateExtensionException for Test1Extension.class");
		}
		catch (EPPDuplicateExtensionException e) {
			Assert.assertEquals(theExt1.getClass(), e.getExtension().getClass());
		}

		// Test that duplicate extension 2 is caught
		try {
			theResponse.getExtension(Test2Extension.class, true);
			Assert.fail(
			      "testResponseGetExtensionFailOnDuplicate: Should have failed with EPPDuplicateExtensionException for Test2Extension.class");
		}
		catch (EPPDuplicateExtensionException e) {
			Assert.assertEquals(theExt2.getClass(), e.getExtension().getClass());
		}

		printEnd("testResponseGetExtensionFailOnDuplicate");
	}

	/**
	 * Unit test of using {@code EPPUtil.testFindExtNamespaces(List, List)} and
	 * the corresponding {@code EPPCommand.testFindExtNamespaces(List)} and
	 * {@code EPPResponse.testFindExtNamespaces(List)}. Test the following:<br>
	 * <ol>
	 * <li>Test that it doesn't fail with no extensions.
	 * <li>Test with many extensions.
	 * <li>Test the filtering of extensions.
	 * </ol>
	 */
	public void testFindExtNamespaces() {
		printStart("testFindExtNamespaces");

		// Test that it doesn't fail with no extensions.
		EPPPollCmd theCommand = new EPPPollCmd("ABC-12345", "req");

		Set<String> theExtNamespaces = EPPUtil.findExtNamespaces(theCommand.getExtensions(), null);
		Assert.assertEquals(0, theExtNamespaces.size());

		// Test with many extensions.
		theCommand = new EPPPollCmd("ABC-12345", "req");

		theCommand.addExtension(new Test1Extension());
		theCommand.addExtension(new Test2Extension());

		theExtNamespaces = EPPUtil.findExtNamespaces(theCommand.getExtensions(), null);
		Assert.assertEquals(2, theExtNamespaces.size());
		System.out.println("EPPUtil: Extension namespaces = " + theExtNamespaces);

		theExtNamespaces = theCommand.findExtNamespaces(null);
		Assert.assertEquals(2, theExtNamespaces.size());
		System.out.println("EPPCommand: Extension namespaces = " + theExtNamespaces);

		EPPResponse theResponse = new EPPResponse();
		theResponse.addExtension(new Test1Extension());
		theResponse.addExtension(new Test2Extension());

		theExtNamespaces = theResponse.findExtNamespaces(null);
		Assert.assertEquals(2, theExtNamespaces.size());
		System.out.println("EPPResponse: Extension namespaces = " + theExtNamespaces);

		// Test the filtering of extensions.
		theCommand = new EPPPollCmd("ABC-12345", "req");

		theCommand.addExtension(new Test1Extension());
		theCommand.addExtension(new Test2Extension());

		theExtNamespaces = EPPUtil.findExtNamespaces(theCommand.getExtensions(), Arrays.asList(Test2Extension.NS));
		Assert.assertEquals(1, theExtNamespaces.size());
		System.out.println("EPPUtil Filtered: Extension namespaces = " + theExtNamespaces);

		theExtNamespaces = theCommand.findExtNamespaces(Arrays.asList(Test1Extension.NS));
		Assert.assertEquals(1, theExtNamespaces.size());
		System.out.println("EPPCommand Filtered: Extension namespaces = " + theExtNamespaces);

		theResponse = new EPPResponse();
		theResponse.addExtension(new Test1Extension());
		theResponse.addExtension(new Test2Extension());

		theExtNamespaces = theResponse.findExtNamespaces(Arrays.asList(Test1Extension.NS, Test2Extension.NS));
		Assert.assertEquals(0, theExtNamespaces.size());
		System.out.println("EPPResponse Filtered: Extension namespaces = " + theExtNamespaces);

		printEnd("testFindExtNamespaces");
	}

	/**
	 * Unit test of using
	 * {@code EPPUtil.testFindExtNamespaceSuffixes(List, List)} and the
	 * corresponding {@code EPPCommand.testFindExtNamespaceSuffixes(List)} and
	 * {@code EPPResponse.testFindExtNamespaceSuffixes(List)}. Test the
	 * following:<br>
	 * <ol>
	 * <li>Test that it doesn't fail with no extensions.
	 * <li>Test with many extensions.
	 * <li>Test the filtering of extensions.
	 * </ol>
	 */
	public void testFindExtNamespaceSuffixes() {
		printStart("testFindExtNamespaceSuffixes");

		// Test that it doesn't fail with no extensions.
		EPPPollCmd theCommand = new EPPPollCmd("ABC-12345", "req");

		Set<String> theExtNamespaceSuffixes = EPPUtil.findExtNamespaceSuffixes(theCommand.getExtensions(), null);
		Assert.assertEquals(0, theExtNamespaceSuffixes.size());

		// Test with many extensions.
		theCommand = new EPPPollCmd("ABC-12345", "req");

		theCommand.addExtension(new Test1Extension());
		theCommand.addExtension(new Test2Extension());

		theExtNamespaceSuffixes = EPPUtil.findExtNamespaceSuffixes(theCommand.getExtensions(), null);
		Assert.assertEquals(2, theExtNamespaceSuffixes.size());
		System.out.println("EPPUtil: Extension namespace suffixes = " + theExtNamespaceSuffixes);

		theExtNamespaceSuffixes = theCommand.findExtNamespaceSuffixes(null);
		Assert.assertEquals(2, theExtNamespaceSuffixes.size());
		System.out.println("EPPCommand: Extension namespace suffixes = " + theExtNamespaceSuffixes);

		EPPResponse theResponse = new EPPResponse();
		theResponse.addExtension(new Test1Extension());
		theResponse.addExtension(new Test2Extension());

		theExtNamespaceSuffixes = theResponse.findExtNamespaceSuffixes(null);
		Assert.assertEquals(2, theExtNamespaceSuffixes.size());
		System.out.println("EPPResponse: Extension namespace suffixes = " + theExtNamespaceSuffixes);

		// Test the filtering of extensions.
		theCommand = new EPPPollCmd("ABC-12345", "req");

		theCommand.addExtension(new Test1Extension());
		theCommand.addExtension(new Test2Extension());

		theExtNamespaceSuffixes = EPPUtil.findExtNamespaceSuffixes(theCommand.getExtensions(),
		      Arrays.asList(EPPUtil.namespaceToNamespaceSuffix(Test2Extension.NS)));
		Assert.assertEquals(1, theExtNamespaceSuffixes.size());
		System.out.println("EPPUtil Filtered: Extension namespace suffixes = " + theExtNamespaceSuffixes);

		theExtNamespaceSuffixes = theCommand
		      .findExtNamespaceSuffixes(Arrays.asList(EPPUtil.namespaceToNamespaceSuffix(Test1Extension.NS)));
		Assert.assertEquals(1, theExtNamespaceSuffixes.size());
		System.out.println("EPPCommand Filtered: Extension namespace suffixes = " + theExtNamespaceSuffixes);

		theResponse = new EPPResponse();
		theResponse.addExtension(new Test1Extension());
		theResponse.addExtension(new Test2Extension());

		theExtNamespaceSuffixes = theResponse
		      .findExtNamespaceSuffixes(Arrays.asList(EPPUtil.namespaceToNamespaceSuffix(Test1Extension.NS),
		            EPPUtil.namespaceToNamespaceSuffix(Test2Extension.NS)));
		Assert.assertEquals(0, theExtNamespaceSuffixes.size());
		System.out.println("EPPResponse Filtered: Extension namespace suffixes = " + theExtNamespaceSuffixes);

		printEnd("testFindExtNamespaceSuffixes");
	}

	/**
	 * JUNIT <code>setUp</code> method, which sets the default client Id to
	 * "theRegistrar".
	 */
	protected void setUp() {
		// EPPTransId.setDefaultClientId("ClientX");
	}

	/**
	 * JUNIT <code>tearDown</code>, which currently does nothing.
	 */
	protected void tearDown() {
	}

	/**
	 * Initialize the SDK environment based on the system properties:<br>
	 * <br>
	 * <ul>
	 * <li>validate - <code>true</code> or <code>false</code> value indicating if
	 * XML schema validation is enabled. The default value is <code>true</code>
	 * <li>fullschemachecking - <code>true</code> or <code>false</code> value
	 * indicating if full XML schema validation is enabled. The default value is
	 * <code>true</code>.
	 * <li>logfile - Log file name. The default value is <code>epp.log</code>.
	 * </ul>
	 * <br>
	 * Both the <code>Environment</code> class and the logging will be
	 * initialized.
	 */
	public static void initEnvironment() {
		if (!initialized) {
			// Initialize SDK properties
			Properties theProps = new Properties();
			theProps.setProperty("EPP.Validating", System.getProperty("validate", "true"));
			theProps.setProperty("EPP.FullSchemaChecking", System.getProperty("fullschemachecking", "true"));
			Environment.setProperties(theProps);

			initialized = true;
		}
	}

	/**
	 * JUNIT <code>suite</code> static method, which returns the tests associated
	 * with <code>EPPCodecTst</code>.
	 *
	 * @return Test suite
	 */
	public static Test suite() {

		initEnvironment();

		TestSuite suite = new TestSuite(EPPCodecTst.class);

		// iterations Property
		String numIterProp = System.getProperty("iterations");

		if (numIterProp != null) {
			numIterations = Integer.parseInt(numIterProp);
		}

		return suite;
	}

	/**
	 * Unit test main, which accepts the following system property options:<br>
	 * 
	 * <ul>
	 * <li>iterations Number of unit test iterations to run</li>
	 * <li>validate Turn XML validation on (<code>true</code>) or off (
	 * <code>false</code>). If validate is not specified, validation will be off.
	 * </li>
	 * </ul>
	 * 
	 *
	 * @param args
	 *           DOCUMENT ME!
	 */
	public static void main(String[] args) {
		// Number of Threads
		int numThreads = 1;
		String threadsStr = System.getProperty("threads");

		if (threadsStr != null) {
			numThreads = Integer.parseInt(threadsStr);
		}

		// Run test suite in multiple threads?
		if (numThreads > 1) {
			// Spawn each thread passing in the Test Suite
			for (int i = 0; i < numThreads; i++) {
				TestThread thread = new TestThread("EPPCodecTst Thread " + i, EPPCodecTst.suite());
				thread.start();
			}
		}
		else { // Single threaded mode.
			junit.textui.TestRunner.run(EPPCodecTst.suite());
		}
	}

	/**
	 * Print the start of a test with the <code>Thread</code> name if the current
	 * thread is a <code>TestThread</code>.
	 *
	 * @param aTest
	 *           name for the test
	 */
	public static void printStart(String aTest) {
		if (Thread.currentThread() instanceof TestThread) {
			System.out.print(Thread.currentThread().getName() + ": ");
		}

		System.out.println("Start of " + aTest);
		System.out.println("*************************************************************************************\n");
	}

	/**
	 * Print the end of a test with the <code>Thread</code> name if the current
	 * thread is a <code>TestThread</code>.
	 *
	 * @param aTest
	 *           name for the test
	 */
	public static void printEnd(String aTest) {
		System.out.println("*************************************************************************************");

		if (Thread.currentThread() instanceof TestThread) {
			System.out.print(Thread.currentThread().getName() + ": ");
		}

		System.out.println("End of " + aTest);
		System.out.println("\n");
	}

	/**
	 * Tests the encoding and decoding of an {@code EPPMessage}. If there is an
	 * error with encoding or decoding the message, an {@code EPPException} will
	 * be thrown.
	 * 
	 * @param aMessage
	 *           Message to test the XML encoding and decoding
	 * @param aStats
	 *           Testing statistics. Pass <code>null</code> for no statistics to
	 *           be gathered.
	 * @throws EPPException
	 *            Error encoding or decoding the message
	 */
	public static void testXMLEncodeDecode(EPPMessage aMessage, EPPEncodeDecodeStats aStats) throws EPPException {
		Document doc = null;
		EPPCodec codec = EPPCodec.getInstance();

		DocumentBuilder parser = null;

		parser = new EPPSchemaCachingParser();
		parser.setErrorHandler(new EPPXMLErrorHandler());

		if (aStats != null) {
			aStats.setXmlValidating(parser.isValidating());
		}

		try {
			if (aStats != null) {
				aStats.startXmlTimer();
			}

			// EPPMessage -> Document
			doc = codec.encode(aMessage);

			// Serialize XML
			ByteArrayOutputStream byteOut = new ByteArrayOutputStream();

			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.STANDALONE, "no");
			trans.transform(new DOMSource(doc.getDocumentElement()), new StreamResult(byteOut));
			byteOut.close();

			System.out.println("\n");
			System.out.write(byteOut.toByteArray());
			System.out.println("\n");

			// De-serialize XML
			ByteArrayInputStream byteIn = new ByteArrayInputStream(byteOut.toByteArray());

			Document decodedDoc = parser.parse(byteIn);

			// Document -> EPPMessage
			EPPMessage decodedMessage = codec.decode(decodedDoc);

			if (aStats != null) {
				aStats.stopXmlTimer();
			}

			// EPPMessage's not equal?
			assertEquals(aMessage, decodedMessage);

			// Write format to stats
			if (aStats.getXmlFormat() == null) {
				aStats.setXmlFormat(byteOut.toByteArray());
			}

			// Use xslt to change prefix and test again
			aStats.startXmlTimer();

			TransformerFactory factory = TransformerFactory.newInstance();
			Transformer transformer = factory.newTransformer(new StreamSource(new StringReader(NAMESPACE_TRANSFORM_XSL)));

			ByteArrayOutputStream byteOutNamespaceTransformed = new ByteArrayOutputStream();
			transformer.transform(new StreamSource(new ByteArrayInputStream(byteOut.toByteArray())),
			      new StreamResult(byteOutNamespaceTransformed));
			byteOutNamespaceTransformed.close();
			byteOut = byteOutNamespaceTransformed;

			// Decode the transformed encoded xml
			System.out.println("\n");
			System.out.write(byteOut.toByteArray());
			System.out.println("\n");

			// De-serialize XML
			byteIn = new ByteArrayInputStream(byteOut.toByteArray());

			// System.out.println("before parsing");
			decodedDoc = parser.parse(byteIn);

			// System.out.println("arriving until here 1");
			// Document -> EPPMessage
			decodedMessage = codec.decode(decodedDoc);

			// System.out.println("after decoding the document"+decodedMessage);
			aStats.stopXmlTimer();

			// EPPMessage's not equal?
			if (!aMessage.equals(decodedMessage)) {
				throw new EPPException("Message [" + aMessage + "] != Decoded Message [" + decodedMessage + "]");
			}

			// Write format to stats
			if (aStats.getXmlFormat() == null) {
				aStats.setXmlFormat(byteOut.toByteArray());
			}
		}
		catch (EPPEncodeException ex) {
			throw ex;
		}
		catch (EPPDecodeException ex) {
			throw ex;
		}
		catch (Exception ex) {
			throw new EPPException("EPPCodecTst.testXMLEncodeDecode: Error [" + ex.getMessage() + "]", ex);
		}

	}

	/**
	 * Reusable unit test algorithm, which does the following:<br>
	 * <br>
	 * 
	 * <ul>
	 * <li>encodes the <code>EPPMessage</code> in XML</li>
	 * <li>decodes the <code>EPPMessage</code> from XML</li>
	 * <li>Serializes <code>EPPMessage</code> using Java Serialization</li>
	 * <li>De-serializes <code>EPPMessage</code> using Java Serialization</li>
	 * <li>Gathers unit test statistics and output them to standard out</li>
	 * </ul>
	 * 
	 *
	 * @param aMessage
	 *           EPP Message to test encoding and decoding
	 *
	 * @return encode and decode statistics
	 */
	public static EPPEncodeDecodeStats testEncodeDecode(EPPMessage aMessage) {
		Document doc = null;
		EPPCodec codec = EPPCodec.getInstance();
		DocumentBuilder parser = null;

		EPPEncodeDecodeStats retStats = new EPPEncodeDecodeStats(aMessage);

		parser = new EPPSchemaCachingParser();
		parser.setErrorHandler(new EPPXMLErrorHandler());

		retStats.setXmlValidating(parser.isValidating());

		for (int i = 1; i <= numIterations; i++) {
			// XML Encoding/Decoding
			try {
				testXMLEncodeDecode(aMessage, retStats);
			}
			catch (EPPEncodeException e) {
				e.printStackTrace();
				Assert.fail("EPPEncodeException encoding " + retStats.getName() + ": " + e);
			}
			catch (EPPDecodeException e) {
				e.printStackTrace();
				Assert.fail("EPPDecodeException decoding " + retStats.getName() + ": " + e);
			}
			catch (Exception e) {
				e.printStackTrace();
				Assert.fail("General Exception encoding/decoding of " + retStats.getName() + ": " + e);
			}

			// Java Serialization
			try {
				retStats.startSerialTimer();

				ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
				ObjectOutputStream out = new ObjectOutputStream(byteOut);

				out.writeObject(aMessage);
				out.close();

				ObjectInputStream in = new ObjectInputStream(new ByteArrayInputStream(byteOut.toByteArray()));

				EPPMessage serializedMessage = (EPPMessage) in.readObject();
				in.close();

				retStats.stopSerialTimer();

				// EPPMessage's not equal?
				assertEquals(aMessage, serializedMessage);

				// Write size to stats
				if (retStats.getSerialSize() == 0) {
					retStats.setSerialSize(byteOut.toByteArray().length);
				}
			}
			catch (Exception e) {
				e.printStackTrace();
				Assert.fail("Java Serialization of " + retStats.getName() + ": " + e);
			}

			// Java Cloning
			try {
				EPPMessage clone = (EPPMessage) aMessage.clone();
				assertEquals(aMessage, clone);
			}
			catch (Exception e) {
				e.printStackTrace();
				Assert.fail("Java Cloning Exception of " + retStats.getName() + ": " + e);
			}
		}

		return retStats;
	}

	/**
	 * Sets the number of iterations to run per test.
	 *
	 * @param aNumIterations
	 *           number of iterations to run per test
	 */
	public static void setNumIterations(long aNumIterations) {
		numIterations = aNumIterations;
	}

	/**
	 * Unit test of <code>EPPHello</code>. The response to <code>EPPHello</code>
	 * is <code>EPPResponse</code>. <br>
	 * This test will be invoked by JUNIT, since it is prefixed with "test" and
	 * is a public method.
	 */
	public void testHello() {
		printStart("testLogout");

		EPPHello theCommand = new EPPHello();

		EPPEncodeDecodeStats commandStats = testEncodeDecode(theCommand);
		System.out.println(commandStats);

		printEnd("testLogout");
	}

	/**
	 * Unit test of <code>EPPPollCmd</code>. The response to
	 * <code>EPPPollCmd</code> is <code>EPPResponse</code>. <br>
	 * This test will be invoked by JUNIT, since it is prefixed with "test" and
	 * is a public method.
	 * 
	 * @throws ParserConfigurationException
	 *            Error creating DOM document.
	 */
	public void testPoll() throws ParserConfigurationException {
		printStart("testPoll");

		// Poll command
		EPPPollCmd theCommand = new EPPPollCmd("ABC-12345", "req");
		EPPEncodeDecodeStats commandStats = testEncodeDecode(theCommand);
		System.out.println(commandStats);

		theCommand = new EPPPollCmd("ABC-12345", "ack", "12345");
		commandStats = testEncodeDecode(theCommand);
		System.out.println(commandStats);

		// Poll Response with message
		EPPResponse theResponse;
		EPPTransId respTransId = new EPPTransId(theCommand.getTransId(), "54321-XYZ-1");
		theResponse = new EPPResponse(respTransId);
		theResponse.setResult(EPPResult.SUCCESS_POLL_MSG);
		theResponse.setMsgQueue(new EPPMsgQueue(Long.valueOf(5), "12345", new Date(), "Transfer request pending"));

		EPPEncodeDecodeStats responseStats = testEncodeDecode(theResponse);
		System.out.println(responseStats);

		// Poll Response with mixed message
		respTransId = new EPPTransId(theCommand.getTransId(), "54321-MIXED-MSG-1");
		theResponse = new EPPResponse(respTransId);
		theResponse.setResult(EPPResult.SUCCESS_POLL_MSG);

		// Create NodeList
		Element theElm;
		Text theText;
		DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
		DocumentBuilder builder = dbf.newDocumentBuilder();
		Document theDoc = builder.newDocument();
		DocumentFragment theFragment = theDoc.createDocumentFragment();

		theElm = theDoc.createElement("code");
		theText = theDoc.createTextNode("1");
		theElm.appendChild(theText);
		theFragment.appendChild(theElm);

		theElm = theDoc.createElement("txt");
		theText = theDoc.createTextNode("Pending domain created completed.");
		theElm.appendChild(theText);
		theFragment.appendChild(theElm);

		theElm = theDoc.createElement("objectId");
		theText = theDoc.createTextNode("example.com");
		theElm.appendChild(theText);
		theFragment.appendChild(theElm);

		theResponse.setMsgQueue(new EPPMsgQueue(Long.valueOf(5), "54321", new Date(), theFragment.getChildNodes()));

		responseStats = testEncodeDecode(theResponse);
		System.out.println(responseStats);

		// Poll Response with no messages
		respTransId = new EPPTransId(theCommand.getTransId(), "54321-XYZ-2");
		theResponse = new EPPResponse(respTransId);
		theResponse.setResult(EPPResult.SUCCESS_POLL_NO_MSGS);

		responseStats = testEncodeDecode(theResponse);
		System.out.println(responseStats);

		printEnd("testPoll");
	}

	/**
	 * Test the encoding and decoding of the <code>timeInstantFormat</code>
	 * supported by the {@code EPPUtil.encodeTimeInstant(Date)} and
	 * {@code EPPUtil.decodeTimeInstant(String)}.
	 */
	public void testTimeInstantFormat() {
		printStart("testTimeInstantFormat");

		String theDateStr = "2014-04-24T19:42:58.588Z";

		Date theDecodedDate = EPPUtil.decodeTimeInstant(theDateStr);

		String theEncodedDateStr = EPPUtil.encodeTimeInstant(theDecodedDate);

		Assert.assertEquals(theDateStr, theEncodedDateStr);

		// Set just the time instant format
		EPPUtil.setTimeInstantFormat("yyyy-MM-dd'T'HH':'mm':'ss'Z'");

		theEncodedDateStr = EPPUtil.encodeTimeInstant(theDecodedDate);

		Assert.assertEquals(theEncodedDateStr, "2014-04-24T19:42:58Z");

		EPPUtil.setTimeInstantFormat(EPPUtil.DEFAULT_TIME_INSTANT_FORMAT);

		printEnd("testTimeInstantFormat");
	}

}
