// ***************************************************************************************************************************
// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements.  See the NOTICE file *
// * distributed with this work for additional information regarding copyright ownership.  The ASF licenses this file        *
// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance            *
// * with the License.  You may obtain a copy of the License at                                                              *
// *                                                                                                                         *
// *  http://www.apache.org/licenses/LICENSE-2.0                                                                             *
// *                                                                                                                         *
// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an  *
// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the License for the        *
// * specific language governing permissions and limitations under the License.                                              *
// ***************************************************************************************************************************
package org.apache.juneau.xml;

import static org.apache.juneau.assertions.Assertions.*;
import static org.apache.juneau.internal.CollectionUtils.*;
import static org.apache.juneau.xml.annotation.XmlFormat.*;
import static org.junit.Assert.*;
import static org.junit.runners.MethodSorters.*;

import java.net.*;
import java.util.*;

import org.apache.juneau.annotation.*;
import org.apache.juneau.collections.*;
import org.apache.juneau.json.*;
import org.apache.juneau.testutils.XmlUtils;
import org.apache.juneau.xml.annotation.*;
import org.apache.juneau.xml.xml1a.*;
import org.apache.juneau.xml.xml1b.*;
import org.apache.juneau.xml.xml1c.*;
import org.junit.*;

@SuppressWarnings({"serial"})
@FixMethodOrder(NAME_ASCENDING)
public class XmlTest {

	//====================================================================================================
	// Simple comparison test with JSON serializer
	//====================================================================================================
	@Test
	public void testComparisonWithJson() throws Exception {

		String json1 =
			"{\n"
			+"	name: \"John Smith\",\n"
			+"	address: {\n"
			+"		streetAddress: \"21 2nd Street\",\n"
			+"		city: \"New York\",\n"
			+"		state: \"NY\",\n"
			+"		postalCode: 10021\n"
			+"	},\n"
			+"	phoneNumbers: [\n"
			+"		\"212 555-1111\",\n"
			+"		\"212 555-2222\"\n"
			+"	],\n"
			+"	additionalInfo: null,\n"
			+"	remote: false,\n"
			+"	height: 62.4,\n"
			+"	\"fico score\": \" > 640\"\n"
			+"}";

		String xml1 =
			"<object>\n"
			+"	<name>John Smith</name>\n"
			+"	<address _type='object'>\n"
			+"		<streetAddress>21 2nd Street</streetAddress>\n"
			+"		<city>New York</city>\n"
			+"		<state>NY</state>\n"
			+"		<postalCode _type='number'>10021</postalCode>\n"
			+"	</address>\n"
			+"	<phoneNumbers _type='array'>\n"
			+"		<string>212 555-1111</string>\n"
			+"		<string>212 555-2222</string>\n"
			+"	</phoneNumbers>\n"
			+"	<additionalInfo _type='null'/>\n"
			+"	<remote _type='boolean'>false</remote>\n"
			+"	<height _type='number'>62.4</height>\n"
			+"	<fico_x0020_score>_x0020_&gt; 640</fico_x0020_score>\n"
			+"</object>\n";

		JsonMap m = (JsonMap) XmlParser.DEFAULT.parse(xml1, Object.class);
		String json2 = JsonSerializer.create().simpleAttrs().ws().keepNullProperties().build().serialize(m);
		assertEquals(json1, json2);

		m = (JsonMap) JsonParser.DEFAULT.parse(json1, Object.class);
		String xml2 = XmlSerializer.create().sq().ws()
			.keepNullProperties()
			.build()
			.serialize(m);
		assertEquals(xml1, xml2);
	}

	//====================================================================================================
	// Test namespacing
	//====================================================================================================
	@Test
	public void testNamespaces() throws Exception {

		String json1 =
			"{\n"
			+"	name: \"John Smith\", \n"
			+"	address: {\n"
			+"		streetAddress: \"21 2nd Street\", \n"
			+"		city: \"New York\", \n"
			+"		state: \"NY\", \n"
			+"		postalCode: 10021\n"
			+"	}, \n"
			+"	phoneNumbers: [\n"
			+"		\"212 555-1111\", \n"
			+"		\"212 555-2222\"\n"
			+"	], \n"
			+"	additionalInfo: null, \n"
			+"	remote: false, \n"
			+"	height: 62.4, \n"
			+"	\"fico score\": \" > 640\"\n"
			+"}";

		String xml1 =
			"<object xmlns='http://www.apache.org'>\n"
			+"	<name>John Smith</name>\n"
			+"	<address _type='object'>\n"
			+"		<streetAddress>21 2nd Street</streetAddress>\n"
			+"		<city>New York</city>\n"
			+"		<state>NY</state>\n"
			+"		<postalCode _type='number'>10021</postalCode>\n"
			+"	</address>\n"
			+"	<phoneNumbers _type='array'>\n"
			+"		<string>212 555-1111</string>\n"
			+"		<string>212 555-2222</string>\n"
			+"	</phoneNumbers>\n"
			+"	<additionalInfo _type='null'/>\n"
			+"	<remote _type='boolean'>false</remote>\n"
			+"	<height _type='number'>62.4</height>\n"
			+"	<fico_x0020_score>_x0020_&gt; 640</fico_x0020_score>\n"
			+"</object>\n";

		JsonMap m = (JsonMap) JsonParser.DEFAULT.parse(json1, Object.class);
		String r = XmlSerializer.create().ns().sq().ws()
			.addNamespaceUrisToRoot()
			.defaultNamespace(Namespace.of("http://www.apache.org"))
			.keepNullProperties()
			.build()
			.serialize(m);
		assertEquals(xml1, r);
	}

	//====================================================================================================
	// Test bean name annotation
	//====================================================================================================
	@Test
	public void testBeanNameAnnotation() throws Exception {
		String e =
			"<Person1>\n"
			+ "	<name>John Smith</name>\n"
			+ "	<age>123</age>\n"
			+ "</Person1>\n";
		String r = XmlSerializer.DEFAULT_SQ_READABLE.serialize(new Person1("John Smith", 123));
		assertEquals(e, r);
	}

	/** Class with explicitly specified properties */
	@Bean(typeName="Person1", properties="name,age")
	public static class Person1 {
		public int age;
		private String name;
		protected Person1(String name, int age) {
			this.name = name;
			this.age = age;
		}
		public String getName() {
			return name;
		}
		public void setName(String name) {
			this.name = name;
		}
	}

	//====================================================================================================
	// Test trimNulls property.
	//====================================================================================================
	@Test
	public void testTrimNulls() throws Exception {
		String e =
			"<Person1>\n"
			+ "	<age>123</age>\n"
			+ "</Person1>\n";
		String r = XmlSerializer.DEFAULT_SQ_READABLE.serialize(new Person1(null, 123));
		assertEquals(e, r);
	}

	//====================================================================================================
	// Element name.
	//====================================================================================================
	@Test
	public void testElementName() throws Exception {
		XmlSerializer s = XmlSerializer.DEFAULT_SQ;
		A t = new A();
		String r = s.serialize(t);
		assertEquals("<foo><f1>1</f1></foo>", r);
		XmlUtils.validateXml(t);
	}

	@Bean(typeName="foo")
	public static class A {
		public int f1 = 1;
	}

	//====================================================================================================
	// Element name on superclass.
	//====================================================================================================
	@Test
	public void testElementNameOnSuperclass() throws Exception {
		XmlSerializer s = XmlSerializer.DEFAULT_SQ;
		B2 t = new B2();
		String r = s.serialize(t);
		assertEquals("<foo><f1>1</f1></foo>", r);
		XmlUtils.validateXml(t);
	}

	public static class B1 extends A {}
	public static class B2 extends B1 {}

	//====================================================================================================
	// Element name on interface.
	//====================================================================================================
	@Test
	public void testElementNameOnInterface() throws Exception {
		XmlSerializer s = XmlSerializer.DEFAULT_SQ;
		C3 t = new C3();
		String r = s.serialize(t);
		assertEquals("<foo><f1>1</f1></foo>", r);
		XmlUtils.validateXml(t);
	}

	@Bean(typeName="foo")
	public interface C1 {}
	public static class C2 implements C1 {}
	public static class C3 extends C2 {
		public int f1 = 1;
	}

	//====================================================================================================
	// Element name with invalid XML characters.
	//====================================================================================================
	@Test
	public void testElementNameWithInvalidChars() throws Exception {
		XmlSerializer s = XmlSerializer.DEFAULT_SQ;
		XmlParser p = XmlParser.DEFAULT;
		D t = new D();
		String r = s.serialize(t);
		assertEquals("<_x007E__x0021__x0040__x0023__x0024__x0025__x005E__x0026__x002A__x0028__x0029___x002B__x0060_-_x003D__x007B__x007D__x007C__x005B__x005D__x005C__x003A__x0022__x003B__x0027__x003C__x003E__x003F__x002C_._x000A__x000D__x0009__x0008_><f1>1</f1></_x007E__x0021__x0040__x0023__x0024__x0025__x005E__x0026__x002A__x0028__x0029___x002B__x0060_-_x003D__x007B__x007D__x007C__x005B__x005D__x005C__x003A__x0022__x003B__x0027__x003C__x003E__x003F__x002C_._x000A__x000D__x0009__x0008_>", r);
		t = p.parse(r, D.class);
		XmlUtils.validateXml(t);
	}

	@Bean(typeName="~!@#$%^&*()_+`-={}|[]\\:\";'<>?,.\n\r\t\b")
	public static class D {
		public int f1 = 1;
	}

	//====================================================================================================
	// Field of type collection with element name.
	// Element name should be ignored.
	//====================================================================================================
	@Test
	public void testIgnoreCollectionFieldWithElementName() throws Exception {
		XmlSerializer s = XmlSerializer.DEFAULT_SQ;
		XmlParser p = XmlParser.DEFAULT;
		G t = new G();
		t.f1.add("bar");
		String r = s.serialize(t);
		assertEquals("<bar><foo _name='f1'><string>bar</string></foo></bar>", r);
		t = p.parse(r, G.class);
		XmlUtils.validateXml(t);
	}

	@Bean(typeName="foo")
	public static class F extends LinkedList<String>{}

	@Bean(typeName="bar")
	public static class G {
		public F f1 = new F();
	}

	//====================================================================================================
	// Element name on beans of a collection.
	//====================================================================================================
	@Test
	public void testElementNameOnBeansOfCollection() throws Exception {
		XmlSerializer s = XmlSerializer.DEFAULT_SQ;
		Object o = new J1();
		String r = s.serialize(o);
		assertEquals("<foo><f1><bar><f2>2</f2></bar></f1></foo>", r);
	}

	@Bean(typeName="foo")
	public static class J1 {
		@Beanp(properties="f2") public List<J2> f1 = list(new J2());
	}

	@Bean(typeName="bar")
	public static class J2 {
		public int f2 = 2;
		public int f3 = 3;
	}

	//====================================================================================================
	// @Xml.ns without matching nsUri.
	//====================================================================================================
	@Test
	public void testXmlNsWithoutMatchingNsUri() throws Exception {
		XmlSerializer s = XmlSerializer.DEFAULT_SQ;
		K t = new K();
		assertThrown(()->s.serialize(t)).asMessage().is("Found @Xml.prefix annotation with no matching URI.  prefix='foo'");
	}

	@Xml(prefix="foo")
	public static class K {
		public int f1;
	}

	//====================================================================================================
	// @Xml.format=ATTR.
	//====================================================================================================
	@Test
	public void testXmlFormatAttr() throws Exception {
		XmlSerializer s = XmlSerializer.DEFAULT_SQ;
		XmlParser p = XmlParser.DEFAULT;
		L t = new L();
		String r = s.serialize(t);
		assertEquals("<object f2='2'><f1>1</f1><f3>3</f3></object>", r);
		t.f1 = 4; t.f2 = 5; t.f3 = 6;
		t = p.parse(s.serialize(t), L.class);
		assertEquals(4, t.f1);
		assertEquals(5, t.f2);
		assertEquals(6, t.f3);
		XmlUtils.validateXml(t);
	}

	public static class L {
		public int f1 = 1;
		@Xml(format=ATTR)
		public int f2 = 2;
		public int f3 = 3;
	}

	//====================================================================================================
	// @Xml.format=ATTR with namespaces.
	//====================================================================================================
	@Test
	public void testXmlFormatAttrWithNs() throws Exception {
		XmlSerializer.Builder s = XmlSerializer.create().sq();
		XmlParser p = XmlParser.DEFAULT;
		M t = new M();
		String r = null;
		r = s.build().serialize(t);
		assertEquals("<object f1='1' f2='2' f3='3'/>", r);
		s.enableNamespaces().addNamespaceUrisToRoot().keepNullProperties();
		t.f1 = 4; t.f2 = 5; t.f3 = 6;
		r = s.build().serialize(t);
		assertEquals("<object xmlns='http://www.apache.org/2013/Juneau' xmlns:bar='http://bar' xmlns:foo='http://foo' xmlns:baz='http://baz' bar:f1='4' foo:f2='5' baz:f3='6'/>", r);
		t = p.parse(r, M.class);
		assertEquals(4, t.f1);
		assertEquals(5, t.f2);
		assertEquals(6, t.f3);
		XmlUtils.validateXml(t, s.build());
	}

	@Xml(prefix="bar", namespace="http://bar")
	public static class M {
		@Xml(format=ATTR)
		public int f1 = 1;
		@Xml(prefix="foo", format=ATTR, namespace="http://foo")
		public int f2 = 2;
		@Xml(prefix="baz", namespace="http://baz", format=ATTR)
		public int f3 = 3;
	}

	//====================================================================================================
	// _xXXXX_ notation.
	//====================================================================================================
	@Test
	public void testXXXXNotation() throws Exception {
		XmlSerializer s = XmlSerializer.DEFAULT_SQ;
		XmlParser p = XmlParser.DEFAULT;
		String in, r;

		in = "\u0001";
		r = s.serialize(in);
		assertEquals("<string>_x0001_</string>", r);
		in = p.parse(r, String.class);
		assertEquals("\u0001", in);

		in = "_x0001_";
		r = s.serialize(in);
		assertEquals("<string>_x005F_x0001_</string>", r);
		in = p.parse(r, String.class);
		assertEquals("_x0001_", in);

		in = "_x001_";
		r = s.serialize(in);
		assertEquals("<string>_x001_</string>", r);
		in = p.parse(r, String.class);
		assertEquals("_x001_", in);

		in = "_x00001_";
		r = s.serialize(in);
		assertEquals("<string>_x00001_</string>", r);
		in = p.parse(r, String.class);
		assertEquals("_x00001_", in);

		in = "_xx001_";
		r = s.serialize(in);
		assertEquals("<string>_xx001_</string>", r);
		in = p.parse(r, String.class);
		assertEquals("_xx001_", in);
	}

	//====================================================================================================
	// @Bean.uri annotation formatted as element
	//====================================================================================================
	@Test
	public void testBeanUriAnnotationFormattedAsElement() throws Exception {
		XmlParser p = XmlParser.DEFAULT;
		XmlSerializer s = XmlSerializer.DEFAULT_SQ;

		N t = new N("http://foo",123, "bar");
		String r = s.serialize(t);
		assertEquals("<object><url>http://foo</url><id>123</id><name>bar</name></object>", r);

		t = p.parse(r, N.class);
		assertEquals("http://foo", t.url.toString());
		assertEquals(123, t.id);
		assertEquals("bar", t.name);

		XmlUtils.validateXml(t, s);
	}

	@Bean(properties="url,id,name")
	public static class N {
		@Xml(format=ELEMENT) public URL url;
		public int id;
		public String name;
		public N() {}
		public N(String url, int id, String name) throws Exception {
			this.url = new URL(url);
			this.id = id;
			this.name = name;
		}
	}

	//====================================================================================================
	// @Bean.uri as elements, overridden element names
	//====================================================================================================
	@Test
	public void testOverriddenBeanUriAsElementNames() throws Exception {
		XmlParser p = XmlParser.DEFAULT;
		XmlSerializer s = XmlSerializer.DEFAULT_SQ;

		O t = new O("http://foo", 123, "bar");
		String r = s.serialize(t);
		assertEquals("<object><url2>http://foo</url2><id2>123</id2><name>bar</name></object>", r);

		t = p.parse(r, O.class);
		assertEquals("http://foo", t.url.toString());
		assertEquals(123, t.id);
		assertEquals("bar", t.name);

		XmlUtils.validateXml(t, s);
	}

	@Bean(properties="url2,id2,name")
	public static class O {
		@Beanp(name="url2") @Xml(format=ELEMENT) public URL url;
		@Beanp(name="id2") public int id;
		public String name;
		public O() {}
		public O(String url, int id, String name) throws Exception {
			this.url = new URL(url);
			this.id = id;
			this.name = name;
		}
	}

	//====================================================================================================
	// @Bean.uri and @Bean.id annotations, overridden attribute names
	//====================================================================================================
	@Test
	public void testOverriddenBeanUriAndIdAnnotations() throws Exception {
		XmlParser p = XmlParser.DEFAULT;
		XmlSerializer s = XmlSerializer.DEFAULT_SQ;

		P t = new P("http://foo", 123, "bar");
		String r = s.serialize(t);
		assertEquals("<object url2='http://foo' id2='123'><name>bar</name></object>", r);

		t = p.parse(r, P.class);
		assertEquals("http://foo", t.url.toString());
		assertEquals(123, t.id);
		assertEquals("bar", t.name);

		XmlUtils.validateXml(t, s);
	}

	@Bean(properties="url2,id2,name")
	public static class P {
		@Beanp(name="url2") @Xml(format=ATTR) public URL url;
		@Beanp(name="id2") @Xml(format=ATTR) public int id;
		public String name;
		public P() {}
		public P(String url, int id, String name) throws Exception {
			this.url = new URL(url);
			this.id = id;
			this.name = name;
		}
	}

	//====================================================================================================
	// Namespace on class
	//====================================================================================================
	@Test
	public void testNsOnClass() throws Exception {
		XmlSerializer.Builder s = XmlSerializer.create().sq().disableAutoDetectNamespaces();
		XmlParser p = XmlParser.DEFAULT;

		T1 t = new T1();
		String r = s.build().serialize(t);
		assertEquals("<object><f1>1</f1><f2>2</f2><f3>3</f3><f4>4</f4></object>", r);
		assertTrue(t.equals(p.parse(r, T1.class)));

		s.enableNamespaces();
		r = s.build().serialize(t);
		assertEquals("<object><foo:f1>1</foo:f1><bar:f2>2</bar:f2><foo:f3>3</foo:f3><baz:f4>4</baz:f4></object>", r);

		// Add namespace URIs to root, but don't auto-detect.
		// Only xsi should be added to root.
		s.addNamespaceUrisToRoot();
		r = s.build().serialize(t);
		assertEquals("<object xmlns='http://www.apache.org/2013/Juneau'><foo:f1>1</foo:f1><bar:f2>2</bar:f2><foo:f3>3</foo:f3><baz:f4>4</baz:f4></object>", r);

		// Manually set namespaces
		s.namespaces(
			Namespace.of("foo","http://foo"),
			Namespace.of("bar","http://bar"),
			Namespace.of("baz","http://baz")
		);
		r = s.build().serialize(t);
		assertEquals("<object xmlns='http://www.apache.org/2013/Juneau' xmlns:foo='http://foo' xmlns:bar='http://bar' xmlns:baz='http://baz'><foo:f1>1</foo:f1><bar:f2>2</bar:f2><foo:f3>3</foo:f3><baz:f4>4</baz:f4></object>", r);
		assertTrue(t.equals(p.parse(r, T1.class)));
		XmlUtils.validateXml(t, s.build());

		// Auto-detect namespaces.
		s = XmlSerializer.create().sq();
		r = s.build().serialize(t);
		assertEquals("<object><f1>1</f1><f2>2</f2><f3>3</f3><f4>4</f4></object>", r);
		assertTrue(t.equals(p.parse(r, T1.class)));

		s.addNamespaceUrisToRoot();
		r = s.build().serialize(t);
		assertEquals("<object><f1>1</f1><f2>2</f2><f3>3</f3><f4>4</f4></object>", r);
		assertTrue(t.equals(p.parse(r, T1.class)));

		s.enableNamespaces();
		r = s.build().serialize(t);
		assertEquals("<object xmlns='http://www.apache.org/2013/Juneau' xmlns:foo='http://foo' xmlns:bar='http://bar' xmlns:baz='http://baz'><foo:f1>1</foo:f1><bar:f2>2</bar:f2><foo:f3>3</foo:f3><baz:f4>4</baz:f4></object>", r);
		assertTrue(t.equals(p.parse(r, T1.class)));
		XmlUtils.validateXml(t, s.build());
	}

	//====================================================================================================
	// Namespace on class with element name.
	//====================================================================================================
	@Test
	public void testNsOnClassWithElementName() throws Exception {
		XmlSerializer.Builder s = XmlSerializer.create().sq().disableAutoDetectNamespaces();
		XmlParser p = XmlParser.DEFAULT;

		T2 t = new T2();
		String r = s.build().serialize(t);
		assertEquals("<T2><f1>1</f1><f2>2</f2><f3>3</f3><f4>4</f4></T2>", r);
		assertTrue(t.equals(p.parse(r, T2.class)));

		s.enableNamespaces();
		r = s.build().serialize(t);
		assertEquals("<foo:T2><foo:f1>1</foo:f1><bar:f2>2</bar:f2><foo:f3>3</foo:f3><baz:f4>4</baz:f4></foo:T2>", r);

		// Add namespace URIs to root, but don't auto-detect.
		// Only xsi should be added to root.
		s.addNamespaceUrisToRoot();
		r = s.build().serialize(t);
		assertEquals("<foo:T2 xmlns='http://www.apache.org/2013/Juneau'><foo:f1>1</foo:f1><bar:f2>2</bar:f2><foo:f3>3</foo:f3><baz:f4>4</baz:f4></foo:T2>", r);

		// Manually set namespaces
		s.namespaces(
			Namespace.of("foo","http://foo"),
			Namespace.of("bar","http://bar"),
			Namespace.of("baz","http://baz")
		);
		r = s.build().serialize(t);
		assertEquals("<foo:T2 xmlns='http://www.apache.org/2013/Juneau' xmlns:foo='http://foo' xmlns:bar='http://bar' xmlns:baz='http://baz'><foo:f1>1</foo:f1><bar:f2>2</bar:f2><foo:f3>3</foo:f3><baz:f4>4</baz:f4></foo:T2>", r);
		assertTrue(t.equals(p.parse(r, T2.class)));
		XmlUtils.validateXml(t, s.build());

		// Auto-detect namespaces.
		s = XmlSerializer.create().sq();
		r = s.build().serialize(t);
		assertEquals("<T2><f1>1</f1><f2>2</f2><f3>3</f3><f4>4</f4></T2>", r);

		s.addNamespaceUrisToRoot();
		r = s.build().serialize(t);
		assertEquals("<T2><f1>1</f1><f2>2</f2><f3>3</f3><f4>4</f4></T2>", r);

		s.enableNamespaces();
		r = s.build().serialize(t);
		assertEquals("<foo:T2 xmlns='http://www.apache.org/2013/Juneau' xmlns:foo='http://foo' xmlns:bar='http://bar' xmlns:baz='http://baz'><foo:f1>1</foo:f1><bar:f2>2</bar:f2><foo:f3>3</foo:f3><baz:f4>4</baz:f4></foo:T2>", r);
		assertTrue(t.equals(p.parse(r, T2.class)));
		XmlUtils.validateXml(t, s.build());
	}


	//====================================================================================================
	// Namespace on package, no namespace on class.
	//====================================================================================================
	@Test
	public void testNsOnPackageNoNsOnClass() throws Exception {
		XmlSerializer.Builder s = XmlSerializer.create().sq();
		XmlParser p = XmlParser.DEFAULT;

		T3 t = new T3();
		String r = s.build().serialize(t);
		assertEquals("<object><f1>1</f1><f2>2</f2><f3>3</f3><f4>4</f4></object>", r);
		assertTrue(t.equals(p.parse(r, T3.class)));
		XmlUtils.validateXml(t, s.build());

		s.enableNamespaces();
		r = s.build().serialize(t);
		assertEquals("<object><p1:f1>1</p1:f1><bar:f2>2</bar:f2><p1:f3>3</p1:f3><baz:f4>4</baz:f4></object>", r);

		// Add namespace URIs to root, but don't auto-detect.
		// Only xsi should be added to root.
		s.addNamespaceUrisToRoot().disableAutoDetectNamespaces();
		r = s.build().serialize(t);
		assertEquals("<object xmlns='http://www.apache.org/2013/Juneau'><p1:f1>1</p1:f1><bar:f2>2</bar:f2><p1:f3>3</p1:f3><baz:f4>4</baz:f4></object>", r);

		// Manually set namespaces
		s.disableAutoDetectNamespaces();
		s.namespaces(
			Namespace.of("p1","http://p1"),
			Namespace.of("bar","http://bar"),
			Namespace.of("baz","http://baz")
		);
		r = s.build().serialize(t);
		assertEquals("<object xmlns='http://www.apache.org/2013/Juneau' xmlns:p1='http://p1' xmlns:bar='http://bar' xmlns:baz='http://baz'><p1:f1>1</p1:f1><bar:f2>2</bar:f2><p1:f3>3</p1:f3><baz:f4>4</baz:f4></object>", r);
		assertTrue(t.equals(p.parse(r, T3.class)));
		XmlUtils.validateXml(t, s.build());

		// Auto-detect namespaces.
		s = XmlSerializer.create().sq();
		r = s.build().serialize(t);
		assertEquals("<object><f1>1</f1><f2>2</f2><f3>3</f3><f4>4</f4></object>", r);

		s.addNamespaceUrisToRoot();
		r = s.build().serialize(t);
		assertEquals("<object><f1>1</f1><f2>2</f2><f3>3</f3><f4>4</f4></object>", r);

		s.enableNamespaces();
		r = s.build().serialize(t);
		assertEquals("<object xmlns='http://www.apache.org/2013/Juneau' xmlns:p1='http://p1' xmlns:bar='http://bar' xmlns:baz='http://baz'><p1:f1>1</p1:f1><bar:f2>2</bar:f2><p1:f3>3</p1:f3><baz:f4>4</baz:f4></object>", r);
		assertTrue(t.equals(p.parse(r, T3.class)));
		XmlUtils.validateXml(t, s.build());
	}

	//====================================================================================================
	// Namespace on package, no namespace on class, element name on class.
	//====================================================================================================
	@Test
	public void testNsOnPackageNoNsOnClassElementNameOnClass() throws Exception {
		XmlSerializer.Builder s = XmlSerializer.create().sq().disableAutoDetectNamespaces();
		XmlParser p = XmlParser.DEFAULT;

		T4 t = new T4();
		String r = s.build().serialize(t);
		assertEquals("<T4><f1>1</f1><f2>2</f2><f3>3</f3><f4>4</f4></T4>", r);
		assertTrue(t.equals(p.parse(r, T4.class)));

		s.enableNamespaces();
		r = s.build().serialize(t);
		assertEquals("<p1:T4><p1:f1>1</p1:f1><bar:f2>2</bar:f2><p1:f3>3</p1:f3><baz:f4>4</baz:f4></p1:T4>", r);

		// Add namespace URIs to root, but don't auto-detect.
		// Only xsi should be added to root.
		s.addNamespaceUrisToRoot();
		r = s.build().serialize(t);
		assertEquals("<p1:T4 xmlns='http://www.apache.org/2013/Juneau'><p1:f1>1</p1:f1><bar:f2>2</bar:f2><p1:f3>3</p1:f3><baz:f4>4</baz:f4></p1:T4>", r);

		// Manually set namespaces
		s.namespaces(
			Namespace.of("foo","http://foo"),
			Namespace.of("bar","http://bar"),
			Namespace.of("baz","http://baz"),
			Namespace.of("p1","http://p1")
		);
		r = s.build().serialize(t);
		assertEquals("<p1:T4 xmlns='http://www.apache.org/2013/Juneau' xmlns:foo='http://foo' xmlns:bar='http://bar' xmlns:baz='http://baz' xmlns:p1='http://p1'><p1:f1>1</p1:f1><bar:f2>2</bar:f2><p1:f3>3</p1:f3><baz:f4>4</baz:f4></p1:T4>", r);
		assertTrue(t.equals(p.parse(r, T4.class)));
		XmlUtils.validateXml(t, s.build());

		// Auto-detect namespaces.
		s = XmlSerializer.create().sq();
		r = s.build().serialize(t);
		assertEquals("<T4><f1>1</f1><f2>2</f2><f3>3</f3><f4>4</f4></T4>", r);

		s.addNamespaceUrisToRoot();
		r = s.build().serialize(t);
		assertEquals("<T4><f1>1</f1><f2>2</f2><f3>3</f3><f4>4</f4></T4>", r);

		s.enableNamespaces();
		r = s.build().serialize(t);
		assertEquals("<p1:T4 xmlns='http://www.apache.org/2013/Juneau' xmlns:p1='http://p1' xmlns:bar='http://bar' xmlns:baz='http://baz'><p1:f1>1</p1:f1><bar:f2>2</bar:f2><p1:f3>3</p1:f3><baz:f4>4</baz:f4></p1:T4>", r);
		assertTrue(t.equals(p.parse(r, T4.class)));
		XmlUtils.validateXml(t, s.build());
	}

	//====================================================================================================
	// Namespace on package, namespace on class, element name on class.
	//====================================================================================================
	@Test
	public void testNsOnPackageNsOnClassElementNameOnClass() throws Exception {
		XmlSerializer.Builder s = XmlSerializer.create().sq();
		XmlParser p = XmlParser.DEFAULT;

		T5 t = new T5();
		String r = s.build().serialize(t);
		assertEquals("<T5><f1>1</f1><f2>2</f2><f3>3</f3><f4>4</f4></T5>", r);
		assertTrue(t.equals(p.parse(r, T5.class)));
		XmlUtils.validateXml(t, s.build());

		s.ns().disableAutoDetectNamespaces();
		r = s.build().serialize(t);
		assertEquals("<foo:T5><foo:f1>1</foo:f1><bar:f2>2</bar:f2><foo:f3>3</foo:f3><baz:f4>4</baz:f4></foo:T5>", r);

		// Add namespace URIs to root, but don't auto-detect.
		// Only xsi should be added to root.
		s.addNamespaceUrisToRoot();
		r = s.build().serialize(t);
		assertEquals("<foo:T5 xmlns='http://www.apache.org/2013/Juneau'><foo:f1>1</foo:f1><bar:f2>2</bar:f2><foo:f3>3</foo:f3><baz:f4>4</baz:f4></foo:T5>", r);

		// Manually set namespaces
		s.namespaces(
			Namespace.of("foo","http://foo"),
			Namespace.of("bar","http://bar"),
			Namespace.of("baz","http://baz")
		);
		r = s.build().serialize(t);
		assertEquals("<foo:T5 xmlns='http://www.apache.org/2013/Juneau' xmlns:foo='http://foo' xmlns:bar='http://bar' xmlns:baz='http://baz'><foo:f1>1</foo:f1><bar:f2>2</bar:f2><foo:f3>3</foo:f3><baz:f4>4</baz:f4></foo:T5>", r);
		assertTrue(t.equals(p.parse(r, T5.class)));
		XmlUtils.validateXml(t, s.build());

		// Auto-detect namespaces.
		s = XmlSerializer.create().sq();
		r = s.build().serialize(t);
		assertEquals("<T5><f1>1</f1><f2>2</f2><f3>3</f3><f4>4</f4></T5>", r);
		XmlUtils.validateXml(t, s.build());

		s.addNamespaceUrisToRoot();
		r = s.build().serialize(t);
		assertEquals("<T5><f1>1</f1><f2>2</f2><f3>3</f3><f4>4</f4></T5>", r);
		XmlUtils.validateXml(t, s.build());

		s.ns();
		r = s.build().serialize(t);
		assertEquals("<foo:T5 xmlns='http://www.apache.org/2013/Juneau' xmlns:foo='http://foo' xmlns:bar='http://bar' xmlns:baz='http://baz'><foo:f1>1</foo:f1><bar:f2>2</bar:f2><foo:f3>3</foo:f3><baz:f4>4</baz:f4></foo:T5>", r);
		assertTrue(t.equals(p.parse(r, T5.class)));
		XmlUtils.validateXml(t, s.build());
	}

	//====================================================================================================
	// Namespace on package, namespace on class, no element name on class.
	//====================================================================================================
	@Test
	public void testNsOnPackageNsOnClassNoElementNameOnClass() throws Exception {
		XmlSerializer.Builder s = XmlSerializer.create().sq().disableAutoDetectNamespaces();
		XmlParser p = XmlParser.DEFAULT;

		T6 t = new T6();
		String r = s.build().serialize(t);
		assertEquals("<object><f1>1</f1><f2>2</f2><f3>3</f3><f4>4</f4></object>", r);
		assertTrue(t.equals(p.parse(r, T6.class)));

		s.ns();
		r = s.build().serialize(t);
		assertEquals("<object><foo:f1>1</foo:f1><bar:f2>2</bar:f2><foo:f3>3</foo:f3><baz:f4>4</baz:f4></object>", r);

		// Add namespace URIs to root, but don't auto-detect.
		// Only xsi should be added to root.
		s.addNamespaceUrisToRoot().disableAutoDetectNamespaces();
		r = s.build().serialize(t);
		assertEquals("<object xmlns='http://www.apache.org/2013/Juneau'><foo:f1>1</foo:f1><bar:f2>2</bar:f2><foo:f3>3</foo:f3><baz:f4>4</baz:f4></object>", r);

		// Manually set namespaces
		s.namespaces(
			Namespace.of("foo","http://foo"),
			Namespace.of("bar","http://bar"),
			Namespace.of("baz","http://baz")
		);
		r = s.build().serialize(t);
		assertEquals("<object xmlns='http://www.apache.org/2013/Juneau' xmlns:foo='http://foo' xmlns:bar='http://bar' xmlns:baz='http://baz'><foo:f1>1</foo:f1><bar:f2>2</bar:f2><foo:f3>3</foo:f3><baz:f4>4</baz:f4></object>", r);
		assertTrue(t.equals(p.parse(r, T6.class)));
		XmlUtils.validateXml(t, s.build());

		// Auto-detect namespaces.
		s = XmlSerializer.create().sq();
		r = s.build().serialize(t);
		assertEquals("<object><f1>1</f1><f2>2</f2><f3>3</f3><f4>4</f4></object>", r);
		XmlUtils.validateXml(t, s.build());

		s.addNamespaceUrisToRoot();
		r = s.build().serialize(t);
		assertEquals("<object><f1>1</f1><f2>2</f2><f3>3</f3><f4>4</f4></object>", r);
		XmlUtils.validateXml(t, s.build());

		s.ns();
		r = s.build().serialize(t);
		assertEquals("<object xmlns='http://www.apache.org/2013/Juneau' xmlns:foo='http://foo' xmlns:bar='http://bar' xmlns:baz='http://baz'><foo:f1>1</foo:f1><bar:f2>2</bar:f2><foo:f3>3</foo:f3><baz:f4>4</baz:f4></object>", r);
		assertTrue(t.equals(p.parse(r, T6.class)));
		XmlUtils.validateXml(t, s.build());
	}

	//====================================================================================================
	// Combination of namespaces and overridden bean property names.
	//====================================================================================================
	@Test
	public void testComboOfNsAndOverriddenBeanPropertyNames() throws Exception {
		XmlSerializer.Builder s = XmlSerializer.create().sq().disableAutoDetectNamespaces();
		XmlParser p = XmlParser.DEFAULT;

		T7 t = new T7();
		String r = s.build().serialize(t);
		assertEquals("<object><g1>1</g1><g2>2</g2><g3>3</g3><g4>4</g4></object>", r);
		assertTrue(t.equals(p.parse(r, T7.class)));

		s.enableNamespaces();
		r = s.build().serialize(t);
		assertEquals("<object><p1:g1>1</p1:g1><bar:g2>2</bar:g2><p1:g3>3</p1:g3><baz:g4>4</baz:g4></object>", r);

		// Add namespace URIs to root, but don't auto-detect.
		// Only xsi should be added to root.
		s.addNamespaceUrisToRoot().disableAutoDetectNamespaces();
		r = s.build().serialize(t);
		assertEquals("<object xmlns='http://www.apache.org/2013/Juneau'><p1:g1>1</p1:g1><bar:g2>2</bar:g2><p1:g3>3</p1:g3><baz:g4>4</baz:g4></object>", r);

		// Manually set namespaces
		s.namespaces(
			Namespace.of("foo","http://foo"),
			Namespace.of("bar","http://bar"),
			Namespace.of("baz","http://baz"),
			Namespace.of("p1","http://p1")
		);
		r = s.build().serialize(t);
		assertEquals("<object xmlns='http://www.apache.org/2013/Juneau' xmlns:foo='http://foo' xmlns:bar='http://bar' xmlns:baz='http://baz' xmlns:p1='http://p1'><p1:g1>1</p1:g1><bar:g2>2</bar:g2><p1:g3>3</p1:g3><baz:g4>4</baz:g4></object>", r);
		assertTrue(t.equals(p.parse(r, T7.class)));

		// Auto-detect namespaces.
		s = XmlSerializer.create().sq();
		r = s.build().serialize(t);
		assertEquals("<object><g1>1</g1><g2>2</g2><g3>3</g3><g4>4</g4></object>", r);

		r = s.build().serialize(t);
		assertEquals("<object><g1>1</g1><g2>2</g2><g3>3</g3><g4>4</g4></object>", r);

		s.ns().addNamespaceUrisToRoot();
		r = s.build().serialize(t);
		assertEquals("<object xmlns='http://www.apache.org/2013/Juneau' xmlns:p1='http://p1' xmlns:bar='http://bar' xmlns:baz='http://baz'><p1:g1>1</p1:g1><bar:g2>2</bar:g2><p1:g3>3</p1:g3><baz:g4>4</baz:g4></object>", r);
		assertTrue(t.equals(p.parse(r, T7.class)));
		XmlUtils.validateXml(t, s.build());
	}

	//====================================================================================================
	// @XmlNs annotation
	//====================================================================================================
	@Test
	public void testXmlNsAnnotation() throws Exception {
		XmlSerializer.Builder s = XmlSerializer.create().sq().disableAutoDetectNamespaces();
		XmlParser p = XmlParser.DEFAULT;

		T8 t = new T8();
		String r = s.build().serialize(t);
		assertEquals("<object><f1>1</f1><f2>2</f2><f3>3</f3><f4>4</f4></object>", r);
		assertTrue(t.equals(p.parse(r, T8.class)));

		s.ns().disableAutoDetectNamespaces();
		r = s.build().serialize(t);
		assertEquals("<object><p2:f1>1</p2:f1><p1:f2>2</p1:f2><c1:f3>3</c1:f3><f1:f4>4</f1:f4></object>", r);

		// Add namespace URIs to root, but don't auto-detect.
		// Only xsi should be added to root.
		s.addNamespaceUrisToRoot();
		r = s.build().serialize(t);
		assertEquals("<object xmlns='http://www.apache.org/2013/Juneau'><p2:f1>1</p2:f1><p1:f2>2</p1:f2><c1:f3>3</c1:f3><f1:f4>4</f1:f4></object>", r);

		// Manually set namespaces
		s.namespaces(
			Namespace.of("foo","http://foo"),
			Namespace.of("bar","http://bar"),
			Namespace.of("baz","http://baz")
		);
		r = s.build().serialize(t);
		assertEquals("<object xmlns='http://www.apache.org/2013/Juneau' xmlns:foo='http://foo' xmlns:bar='http://bar' xmlns:baz='http://baz'><p2:f1>1</p2:f1><p1:f2>2</p1:f2><c1:f3>3</c1:f3><f1:f4>4</f1:f4></object>", r);

		// Auto-detect namespaces.
		s = XmlSerializer.create().sq();
		r = s.build().serialize(t);
		assertEquals("<object><f1>1</f1><f2>2</f2><f3>3</f3><f4>4</f4></object>", r);
		assertTrue(t.equals(p.parse(r, T8.class)));
		XmlUtils.validateXml(t, s.build());

		s.addNamespaceUrisToRoot();
		r = s.build().serialize(t);
		assertEquals("<object><f1>1</f1><f2>2</f2><f3>3</f3><f4>4</f4></object>", r);
		XmlUtils.validateXml(t, s.build());

		s.ns();
		r = s.build().serialize(t);
		assertEquals("<object xmlns='http://www.apache.org/2013/Juneau' xmlns:p2='http://p2' xmlns:p1='http://p1' xmlns:c1='http://c1' xmlns:f1='http://f1'><p2:f1>1</p2:f1><p1:f2>2</p1:f2><c1:f3>3</c1:f3><f1:f4>4</f1:f4></object>", r);
		assertTrue(t.equals(p.parse(r, T8.class)));
		XmlUtils.validateXml(t, s.build());
	}

	//====================================================================================================
	// @Xml.ns on package, @Xml.nsUri not on package but in @XmlNs.
	//====================================================================================================
	@Test
	public void testXmlNsOnPackageNsUriInXmlNs() throws Exception {
		XmlSerializer.Builder s = XmlSerializer.create().sq().disableAutoDetectNamespaces();
		XmlParser p = XmlParser.DEFAULT;

		T9 t = new T9();
		String r = s.build().serialize(t);
		assertEquals("<object><f1>1</f1></object>", r);
		assertTrue(t.equals(p.parse(r, T9.class)));

		s.ns().disableAutoDetectNamespaces();
		r = s.build().serialize(t);
		assertEquals("<object><p1:f1>1</p1:f1></object>", r);

		// Add namespace URIs to root, but don't auto-detect.
		// Only xsi should be added to root.
		s.addNamespaceUrisToRoot();
		r = s.build().serialize(t);
		assertEquals("<object xmlns='http://www.apache.org/2013/Juneau'><p1:f1>1</p1:f1></object>", r);

		// Manually set namespaces
		s.namespaces(
			Namespace.of("foo","http://foo"),
			Namespace.of("bar","http://bar"),
			Namespace.of("baz","http://baz")
		);
		r = s.build().serialize(t);
		assertEquals("<object xmlns='http://www.apache.org/2013/Juneau' xmlns:foo='http://foo' xmlns:bar='http://bar' xmlns:baz='http://baz'><p1:f1>1</p1:f1></object>", r);

		// Auto-detect namespaces.
		s = XmlSerializer.create().sq();
		r = s.build().serialize(t);
		assertEquals("<object><f1>1</f1></object>", r);
		assertTrue(t.equals(p.parse(r, T9.class)));
		XmlUtils.validateXml(t, s.build());

		s.addNamespaceUrisToRoot();
		r = s.build().serialize(t);
		assertEquals("<object><f1>1</f1></object>", r);
		XmlUtils.validateXml(t, s.build());

		s.ns();
		r = s.build().serialize(t);
		assertEquals("<object xmlns='http://www.apache.org/2013/Juneau' xmlns:p1='http://p1'><p1:f1>1</p1:f1></object>", r);
		assertTrue(t.equals(p.parse(r, T9.class)));
		XmlUtils.validateXml(t, s.build());
	}

	//====================================================================================================
	// @Xml.format=ATTR
	//====================================================================================================
	@Test
	public void testXmlAttrs() throws Exception {
		XmlSerializer.Builder s = XmlSerializer.create().sq();
		XmlParser p = XmlParser.DEFAULT;
		String r;

		Q t = new Q();
		t.f1 = new URL("http://xf1");
		t.f2 = "xf2";
		t.f3 = "xf3";
		r = s.build().serialize(t);
		assertEquals("<object f1='http://xf1' f2='xf2' x3='xf3'/>", r);
		t = p.parse(r, Q.class);
		assertEquals("http://xf1", t.f1.toString());
		assertEquals("xf2", t.f2);
		assertEquals("xf3", t.f3);

		s.ns().addNamespaceUrisToRoot();
		r = s.build().serialize(t);
		assertEquals("<object xmlns='http://www.apache.org/2013/Juneau' xmlns:ns='http://ns' xmlns:nsf1='http://nsf1' xmlns:nsf3='http://nsf3' nsf1:f1='http://xf1' ns:f2='xf2' nsf3:x3='xf3'/>", r);
		XmlUtils.validateXml(t, s.build());

		t = p.parse(r, Q.class);
		assertEquals("http://xf1", t.f1.toString());
		assertEquals("xf2", t.f2);
		assertEquals("xf3", t.f3);
	}

	@Xml(prefix="ns", namespace="http://ns")
	public static class Q {

		@Xml(format=ATTR, prefix="nsf1", namespace="http://nsf1")
		public URL f1;

		@Xml(format=ATTR)
		public String f2;

		@Beanp(name="x3")
		@Xml(format=ATTR, prefix="nsf3", namespace="http://nsf3")
		public String f3;

		public Q() throws Exception {
			f1 = new URL("http://f1");
			f2 = "f2";
			f3 = "f3";
		}
	}
}