// ***************************************************************************************************************************
// * 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.rest.test.client;

import static org.apache.juneau.http.HttpMethodName.*;
import static org.apache.juneau.internal.ArrayUtils.*;
import static org.apache.juneau.internal.StringUtils.*;
import static org.junit.Assert.*;

import java.io.*;
import java.util.*;

import org.apache.juneau.http.annotation.*;
import org.apache.juneau.http.annotation.FormData;
import org.apache.juneau.http.annotation.Header;
import org.apache.juneau.http.annotation.Path;
import org.apache.juneau.http.annotation.Query;
import org.apache.juneau.httppart.*;
import org.apache.juneau.rest.*;
import org.apache.juneau.rest.annotation.*;
import org.apache.juneau.rest.client.*;
import org.apache.juneau.http.remote.*;
import org.apache.juneau.rest.mock2.*;
import org.apache.juneau.serializer.*;
import org.apache.juneau.uon.*;
import org.apache.juneau.urlencoding.*;
import org.apache.juneau.utils.*;
import org.junit.*;
import org.junit.runners.*;

@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class RequestBeanProxyTest {

	//=================================================================================================================
	// @Query
	//=================================================================================================================

	@Rest
	public static class A {
		@RestMethod(name=GET, path="/echoQuery")
		public String echoQuery(RestRequest req) throws Exception {
			return req.getQuery().toString(true);
		}
	}

	//=================================================================================================================
	// @Query - Simple values
	//=================================================================================================================

	@Remote(path="/")
	public static interface A01_RemoteResource {

		@RemoteMethod(method="GET", path="/echoQuery")
		String normal(@Request A01_BeanImpl rb);

		@RemoteMethod(method="GET", path="/echoQuery")
		String serialized(@Request(partSerializer=XSerializer.class) A01_BeanImpl rb);
	}

	public static interface A01_BeanInterface {
		@Query String getA();
		@Query("b") String getX1();
		@Query(name="c") String getX2();
		@Query(name="e",allowEmptyValue=true) String getX4();
		@Query("f") String getX5();
		@Query("g") String getX6();
		@Query("h") String getX7();
	}

	public static class A01_BeanImpl implements A01_BeanInterface {
		@Override public String getA() { return "a1"; }
		@Override public String getX1() { return "b1"; }
		@Override public String getX2() { return "c1"; }
		@Override public String getX4() { return ""; }
		@Override public String getX5() { return null; }
		@Override public String getX6() { return "true"; }
		@Override public String getX7() { return "123"; }
	}

	static A01_RemoteResource a01a = MockRemote.build(A01_RemoteResource.class, A.class, null);
	static A01_RemoteResource a01b = MockRestClient.create(A.class, null).partSerializer(UonSerializer.class).build().getRemote(A01_RemoteResource.class);

	@Test
	public void a01a_query_simpleVals_plainText() throws Exception {
		assertEquals("{a:'a1',b:'b1',c:'c1',e:'',g:'true',h:'123'}", a01a.normal(new A01_BeanImpl()));
	}
	@Test
	public void a01b_query_simpleVals_uon() throws Exception {
		assertEquals("{a:'a1',b:'b1',c:'c1',e:'',g:'\\'true\\'',h:'\\'123\\''}", a01b.normal(new A01_BeanImpl()));
	}
	@Test
	public void a01c_query_simpleVals_x() throws Exception {
		assertEquals("{a:'xa1x',b:'xb1x',c:'xc1x',e:'xx',g:'xtruex',h:'x123x'}", a01b.serialized(new A01_BeanImpl()));
	}

	//=================================================================================================================
	// @Query - Maps
	//=================================================================================================================

	@Remote(path="/")
	public static interface A02_RemoteResource {

		@RemoteMethod(method="GET", path="/echoQuery")
		String normal(@Request A02_Bean rb);

		@RemoteMethod(method="GET", path="/echoQuery")
		String serialized(@Request(partSerializer=XSerializer.class) A02_Bean rb);
	}

	public static class A02_Bean {
		@Query
		public Map<String,Object> getA() {
			return new AMap<String,Object>().append("a1","v1").append("a2", 123).append("a3", null).append("a4", "");
		}
		@Query("*")
		public Map<String,Object> getB() {
			return new AMap<String,Object>().append("b1","true").append("b2", "123").append("b3", "null");
		}
		@Query(name="*",allowEmptyValue=true)
		public Map<String,Object> getC() {
			return new AMap<String,Object>().append("c1","v1").append("c2", 123).append("c3", null).append("c4", "");
		}
		@Query("*")
		public Map<String,Object> getD() {
			return null;
		}
	}

	static A02_RemoteResource a02a = MockRemote.build(A02_RemoteResource.class, A.class, null);
	static A02_RemoteResource a02b = MockRestClient.create(A.class, null).partSerializer(UonSerializer.class).build().getRemote(A02_RemoteResource.class);

	@Test
	public void a02a_query_maps_plainText() throws Exception {
		String r = a02a.normal(new A02_Bean());
		assertEquals("{a:'(a1=v1,a2=123,a3=null,a4=\\'\\')',b1:'true',b2:'123',b3:'null',c1:'v1',c2:'123',c4:''}", r);
	}
	@Test
	public void a02b_query_maps_uon() throws Exception {
		String r = a02b.normal(new A02_Bean());
		assertEquals("{a:'(a1=v1,a2=123,a3=null,a4=\\'\\')',b1:'\\'true\\'',b2:'\\'123\\'',b3:'\\'null\\'',c1:'v1',c2:'123',c4:''}", r);
	}
	@Test
	public void a02c_query_maps_x() throws Exception {
		String r = a02b.serialized(new A02_Bean());
		assertEquals("{a:'x{a1=v1, a2=123, a3=null, a4=}x',b1:'xtruex',b2:'x123x',b3:'xnullx',c1:'xv1x',c2:'x123x',c4:'xx'}", r);
	}

	//=================================================================================================================
	// @Query - NameValuePairs
	//=================================================================================================================

	@Remote(path="/")
	public static interface A03_RemoteResource {

		@RemoteMethod(method="GET", path="/echoQuery")
		String normal(@Request A03_Bean rb);

		@RemoteMethod(method="GET", path="/echoQuery")
		String serialized(@Request(partSerializer=XSerializer.class) A03_Bean rb);
	}

	public static class A03_Bean {
		@Query(allowEmptyValue=true)
		public NameValuePairs getA() {
			return new NameValuePairs().append("a1","v1").append("a2", 123).append("a3", null).append("a4", "");
		}
		@Query("*")
		public NameValuePairs getB() {
			return new NameValuePairs().append("b1","true").append("b2", "123").append("b3", "null");
		}
		@Query(name="*",allowEmptyValue=true)
		public NameValuePairs getC() {
			return new NameValuePairs().append("c1","v1").append("c2", 123).append("c3", null).append("c4", "");
		}
		@Query("*")
		public NameValuePairs getD() {
			return null;
		}
	}

	static A03_RemoteResource a03a = MockRemote.build(A03_RemoteResource.class, A.class, null);
	static A03_RemoteResource a03b = MockRestClient.create(A.class, null).partSerializer(UonSerializer.class).build().getRemote(A03_RemoteResource.class);

	@Test
	public void a03a_query_nameValuePairs_plainText() throws Exception {
		String r = a03a.normal(new A03_Bean());
		assertEquals("{a1:'v1',a2:'123',a4:'',b1:'true',b2:'123',b3:'null',c1:'v1',c2:'123',c4:''}", r);
	}
	@Test
	public void a03b_query_nameValuePairs_on() throws Exception {
		String r = a03b.normal(new A03_Bean());
		assertEquals("{a1:'v1',a2:'\\'123\\'',a4:'',b1:'\\'true\\'',b2:'\\'123\\'',b3:'\\'null\\'',c1:'v1',c2:'\\'123\\'',c4:''}", r);
	}
	@Test
	public void a03c_query_nameValuePairs_x() throws Exception {
		String r = a03b.serialized(new A03_Bean());
		assertEquals("{a1:'xv1x',a2:'x123x',a4:'xx',b1:'xtruex',b2:'x123x',b3:'xnullx',c1:'xv1x',c2:'x123x',c4:'xx'}", r);
	}

	//=================================================================================================================
	// @Query - CharSequence
	//=================================================================================================================

	@Remote(path="/")
	public static interface A04_RemoteResource {
		@RemoteMethod(method="GET", path="/echoQuery")
		String normal(@Request A04_Bean rb);
	}

	public static class A04_Bean {
		@Query("*")
		public StringBuilder getA() {
			return new StringBuilder("foo=bar&baz=qux");
		}
	}

	static A04_RemoteResource a04a = MockRemote.build(A04_RemoteResource.class, A.class, null);

	@Test
	public void a04a_query_charSequence() throws Exception {
		String r = a04a.normal(new A04_Bean());
		assertEquals("{baz:'qux',foo:'bar'}", r);
	}

	//=================================================================================================================
	// @Query - Reader
	//=================================================================================================================

	@Remote(path="/")
	public static interface A05_RemoteResource {
		@RemoteMethod(method="GET", path="/echoQuery")
		String normal(@Request A05_Bean rb);
	}

	public static class A05_Bean {
		@Query("*")
		public Reader getA() {
			return new StringReader("foo=bar&baz=qux");
		}
	}

	static A05_RemoteResource a05a = MockRemote.build(A05_RemoteResource.class, A.class, null);

	@Test
	public void a05a_query_reader() throws Exception {
		String r = a05a.normal(new A05_Bean());
		assertEquals("{baz:'qux',foo:'bar'}", r);
	}

	//=================================================================================================================
	// @Query - Collections
	//=================================================================================================================

	@Remote(path="/")
	public static interface A06_RemoteResource {

		@RemoteMethod(method="GET", path="/echoQuery")
		String normal(@Request A06_Bean rb);

		@RemoteMethod(method="GET", path="/echoQuery")
		String serialized(@Request(partSerializer=XSerializer.class) A06_Bean rb);
	}

	public static class A06_Bean {
		@Query
		public List<Object> getA() {
			return new AList<>().append("foo").append("").append("true").append("123").append("null").append(true).append(123).append(null);
		}
		@Query("b")
		public List<Object> getX1() {
			return new AList<>().append("foo").append("").append("true").append("123").append("null").append(true).append(123).append(null);
		}
		@Query(name="c", serializer=ListSerializer.class)
		public List<Object> getX2() {
			return new AList<>().append("foo").append("").append("true").append("123").append("null").append(true).append(123).append(null);
		}
		@Query(name="d",allowEmptyValue=true)
		public List<Object> getX3() {
			return new AList<>();
		}
		@Query("e")
		public List<Object> getX4() {
			return null;
		}
		@Query("f")
		public Object[] getX5() {
			return new Object[]{"foo", "", "true", "123", "null", true, 123, null};
		}
		@Query(name="g", serializer=ListSerializer.class)
		public Object[] getX6() {
			return new Object[]{"foo", "", "true", "123", "null", true, 123, null};
		}
		@Query(name="h",allowEmptyValue=true)
		public Object[] getX7() {
			return new Object[]{};
		}
		@Query("i")
		public Object[] getX8() {
			return null;
		}
	}

	static A06_RemoteResource a06a = MockRemote.build(A06_RemoteResource.class, A.class, null);
	static A06_RemoteResource a06b = MockRestClient.create(A.class, null).partSerializer(UonSerializer.class).build().getRemote(A06_RemoteResource.class);

	@Test
	public void a06a_query_collections_plainText() throws Exception {
		String r = a06a.normal(new A06_Bean());
		assertEquals("{a:'foo,,true,123,null,true,123,null',b:'foo,,true,123,null,true,123,null',c:'foo||true|123|null|true|123|null',d:'',f:'foo,,true,123,null,true,123,null',g:'foo||true|123|null|true|123|null',h:''}", r);
	}
	@Test
	public void a06b_query_collections_uon() throws Exception {
		String r = a06b.normal(new A06_Bean());
		assertEquals("{a:'@(foo,\\'\\',\\'true\\',\\'123\\',\\'null\\',true,123,null)',b:'@(foo,\\'\\',\\'true\\',\\'123\\',\\'null\\',true,123,null)',c:'foo||true|123|null|true|123|null',d:'@()',f:'@(foo,\\'\\',\\'true\\',\\'123\\',\\'null\\',true,123,null)',g:'foo||true|123|null|true|123|null',h:'@()'}", r);
	}
	@Test
	public void a06c_query_collections_x() throws Exception {
		String r = a06b.serialized(new A06_Bean());
		assertEquals("{a:'fooXXtrueX123XnullXtrueX123Xnull',b:'fooXXtrueX123XnullXtrueX123Xnull',c:'foo||true|123|null|true|123|null',d:'',f:'fooXXtrueX123XnullXtrueX123Xnull',g:'foo||true|123|null|true|123|null',h:''}", r);
	}

	//=================================================================================================================
	// @FormData
	//=================================================================================================================

	@Rest(parsers=UrlEncodingParser.class)
	public static class C {
		@RestMethod(name=POST)
		public String echoFormData(RestRequest req) throws Exception {
			return req.getFormData().toString(true);
		}
	}

	//=================================================================================================================
	// @FormData, Simple values
	//=================================================================================================================

	@Remote(path="/")
	public static interface C01_RemoteResource {

		@RemoteMethod(method="POST", path="/echoFormData")
		String normal(@Request C01_Bean rb);

		@RemoteMethod(method="POST", path="/echoFormData")
		String serialized(@Request(partSerializer=XSerializer.class) C01_Bean rb);
	}

	public static class C01_Bean {
		@FormData
		public String getA() {
			return "a1";
		}
		@FormData("b")
		public String getX1() {
			return "b1";
		}
		@FormData(name="c")
		public String getX2() {
			return "c1";
		}
		@FormData(name="e",allowEmptyValue=true)
		public String getX4() {
			return "";
		}
		@FormData("f")
		public String getX5() {
			return null;
		}
		@FormData("g")
		public String getX6() {
			return "true";
		}
		@FormData("h")
		public String getX7() {
			return "123";
		}
	}

	static C01_RemoteResource c01a = MockRemote.build(C01_RemoteResource.class, C.class, null);
	static C01_RemoteResource c01b = MockRestClient.create(C.class, null).partSerializer(UonSerializer.class).build().getRemote(C01_RemoteResource.class);

	@Test
	public void c01a_formData_simpleVals_plainText() throws Exception {
		String r = c01a.normal(new C01_Bean());
		assertEquals("{a:'a1',b:'b1',c:'c1',e:'',g:'true',h:'123'}", r);
	}
	@Test
	public void c01b_formData_simpleVals_uon() throws Exception {
		String r = c01b.normal(new C01_Bean());
		assertEquals("{a:'a1',b:'b1',c:'c1',e:'',g:'\\'true\\'',h:'\\'123\\''}", r);
	}
	@Test
	public void c01c_formData_simpleVals_x() throws Exception {
		String r = c01b.serialized(new C01_Bean());
		assertEquals("{a:'xa1x',b:'xb1x',c:'xc1x',e:'xx',g:'xtruex',h:'x123x'}", r);
	}

	//=================================================================================================================
	// @FormData, Maps
	//=================================================================================================================

	@Remote(path="/")
	public static interface C02_RemoteResource {

		@RemoteMethod(method="POST", path="/echoFormData")
		String normal(@Request C02_Bean rb);

		@RemoteMethod(method="POST", path="/echoFormData")
		String serialized(@Request(partSerializer=XSerializer.class) C02_Bean rb);
	}

	public static class C02_Bean {
		@FormData
		public Map<String,Object> getA() {
			return new AMap<String,Object>().append("a1","v1").append("a2", 123).append("a3", null).append("a4", "");
		}
		@FormData("*")
		public Map<String,Object> getB() {
			return new AMap<String,Object>().append("b1","true").append("b2", "123").append("b3", "null");
		}
		@FormData(name="*",allowEmptyValue=true)
		public Map<String,Object> getC() {
			return new AMap<String,Object>().append("c1","v1").append("c2", 123).append("c3", null).append("c4", "");
		}
		@FormData("*")
		public Map<String,Object> getD() {
			return null;
		}
	}

	static C02_RemoteResource c02a = MockRemote.build(C02_RemoteResource.class, C.class, null);
	static C02_RemoteResource c02b = MockRestClient.create(C.class, null).partSerializer(UonSerializer.class).build().getRemote(C02_RemoteResource.class);

	@Test
	public void c02a_formData_maps_plainText() throws Exception {
		String r = c02a.normal(new C02_Bean());
		assertEquals("{a:'(a1=v1,a2=123,a3=null,a4=\\'\\')',b1:'true',b2:'123',b3:'null',c1:'v1',c2:'123',c4:''}", r);
	}
	@Test
	public void c02b_formData_maps_uon() throws Exception {
		String r = c02b.normal(new C02_Bean());
		assertEquals("{a:'(a1=v1,a2=123,a3=null,a4=\\'\\')',b1:'\\'true\\'',b2:'\\'123\\'',b3:'\\'null\\'',c1:'v1',c2:'123',c4:''}", r);
	}
	@Test
	public void c02c_formData_maps_x() throws Exception {
		String r = c02b.serialized(new C02_Bean());
		assertEquals("{a:'x{a1=v1, a2=123, a3=null, a4=}x',b1:'xtruex',b2:'x123x',b3:'xnullx',c1:'xv1x',c2:'x123x',c4:'xx'}", r);
	}

	//=================================================================================================================
	// @FormData, NameValuePairs
	//=================================================================================================================

	@Remote(path="/")
	public static interface C03_RemoteResource {

		@RemoteMethod(method="POST", path="/echoFormData")
		String normal(@Request C03_Bean rb);

		@RemoteMethod(method="POST", path="/echoFormData")
		String serialized(@Request(partSerializer=XSerializer.class) C03_Bean rb);
	}

	public static class C03_Bean {
		@FormData
		public NameValuePairs getA() {
			return new NameValuePairs().append("a1","v1").append("a2", 123).append("a3", null).append("a4", "");
		}
		@FormData("*")
		public NameValuePairs getB() {
			return new NameValuePairs().append("b1","true").append("b2", "123").append("b3", "null");
		}
		@FormData(name="*")
		public NameValuePairs getC() {
			return new NameValuePairs().append("c1","v1").append("c2", 123).append("c3", null).append("c4", "");
		}
		@FormData("*")
		public NameValuePairs getD() {
			return null;
		}
	}

	static C03_RemoteResource c03a = MockRemote.build(C03_RemoteResource.class, C.class, null);
	static C03_RemoteResource c03b = MockRestClient.create(C.class, null).partSerializer(UonSerializer.class).build().getRemote(C03_RemoteResource.class);

	@Test
	public void c03a_formData_nameValuePairs_plainText() throws Exception {
		String r = c03a.normal(new C03_Bean());
		assertEquals("{a1:'v1',a2:'123',a4:'',b1:'true',b2:'123',b3:'null',c1:'v1',c2:'123',c4:''}", r);
	}
	@Test
	public void c03b_formData_nameValuePairs_uon() throws Exception {
		String r = c03b.normal(new C03_Bean());
		assertEquals("{a1:'v1',a2:'123',a4:'',b1:'true',b2:'123',b3:'null',c1:'v1',c2:'123',c4:''}", r);
	}
	@Test
	public void c03c_formData_nameValuePairs_x() throws Exception {
		String r = c03b.serialized(new C03_Bean());
		assertEquals("{a1:'v1',a2:'123',a4:'',b1:'true',b2:'123',b3:'null',c1:'v1',c2:'123',c4:''}", r);
	}

	//=================================================================================================================
	// @FormData, CharSequence
	//=================================================================================================================

	@Remote(path="/")
	public static interface C04_RemoteResource {
		@RemoteMethod(method="POST", path="/echoFormData")
		String normal(@Request C04_Bean rb);
	}

	public static class C04_Bean {
		@FormData("*")
		public StringBuilder getA() {
			return new StringBuilder("foo=bar&baz=qux");
		}
	}

	static C04_RemoteResource c04a = MockRemote.build(C04_RemoteResource.class, C.class, null);

	@Test
	public void c04a_formDataCharSequence() throws Exception {
		String r = c04a.normal(new C04_Bean());
		assertEquals("{baz:'qux',foo:'bar'}", r);
	}

	//=================================================================================================================
	// @FormData, Reader
	//=================================================================================================================

	@Remote(path="/")
	public static interface C05_RemoteResource {
		@RemoteMethod(method="POST", path="/echoFormData")
		String normal(@Request C05_Bean rb);
	}

	public static class C05_Bean {
		@FormData("*")
		public Reader getA() {
			return new StringReader("foo=bar&baz=qux");
		}
	}

	static C05_RemoteResource c05a = MockRemote.build(C05_RemoteResource.class, C.class, null);

	@Test
	public void c05a_formDataReader() throws Exception {
		String r = c05a.normal(new C05_Bean());
		assertEquals("{baz:'qux',foo:'bar'}", r);
	}

	//=================================================================================================================
	// @FormData, Collections
	//=================================================================================================================

	@Remote(path="/")
	public static interface C06_RemoteResource {

		@RemoteMethod(method="POST", path="/echoFormData")
		String normal(@Request C06_Bean rb);

		@RemoteMethod(method="POST", path="/echoFormData")
		String serialized(@Request(partSerializer=XSerializer.class) C06_Bean rb);
	}

	public static class C06_Bean {
		@FormData
		public List<Object> getA() {
			return new AList<>().append("foo").append("").append("true").append("123").append("null").append(true).append(123).append(null);
		}
		@FormData("b")
		public List<Object> getX1() {
			return new AList<>().append("foo").append("").append("true").append("123").append("null").append(true).append(123).append(null);
		}
		@FormData(name="c", serializer=ListSerializer.class)
		public List<Object> getX2() {
			return new AList<>().append("foo").append("").append("true").append("123").append("null").append(true).append(123).append(null);
		}
		@FormData(name="d",allowEmptyValue=true)
		public List<Object> getX3() {
			return new AList<>();
		}
		@FormData("e")
		public List<Object> getX4() {
			return null;
		}
		@FormData("f")
		public Object[] getX5() {
			return new Object[]{"foo", "", "true", "123", "null", true, 123, null};
		}
		@FormData(name="g", serializer=ListSerializer.class)
		public Object[] getX6() {
			return new Object[]{"foo", "", "true", "123", "null", true, 123, null};
		}
		@FormData(name="h",allowEmptyValue=true)
		public Object[] getX7() {
			return new Object[]{};
		}
		@FormData("i")
		public Object[] getX8() {
			return null;
		}
	}

	static C06_RemoteResource c06a = MockRemote.build(C06_RemoteResource.class, C.class, null);
	static C06_RemoteResource c06b = MockRestClient.create(C.class, null).partSerializer(UonSerializer.class).build().getRemote(C06_RemoteResource.class);

	@Test
	public void c06a_formData_collections_plainText() throws Exception {
		String r = c06a.normal(new C06_Bean());
		assertEquals("{a:'foo,,true,123,null,true,123,null',b:'foo,,true,123,null,true,123,null',c:'foo||true|123|null|true|123|null',d:'',f:'foo,,true,123,null,true,123,null',g:'foo||true|123|null|true|123|null',h:''}", r);
	}
	@Test
	public void c06b_formData_collections_uon() throws Exception {
		String r = c06b.normal(new C06_Bean());
		assertEquals("{a:'@(foo,\\'\\',\\'true\\',\\'123\\',\\'null\\',true,123,null)',b:'@(foo,\\'\\',\\'true\\',\\'123\\',\\'null\\',true,123,null)',c:'foo||true|123|null|true|123|null',d:'@()',f:'@(foo,\\'\\',\\'true\\',\\'123\\',\\'null\\',true,123,null)',g:'foo||true|123|null|true|123|null',h:'@()'}", r);
	}
	@Test
	public void c06c_formData_collections_x() throws Exception {
		String r = c06b.serialized(new C06_Bean());
		assertEquals("{a:'fooXXtrueX123XnullXtrueX123Xnull',b:'fooXXtrueX123XnullXtrueX123Xnull',c:'foo||true|123|null|true|123|null',d:'',f:'fooXXtrueX123XnullXtrueX123Xnull',g:'foo||true|123|null|true|123|null',h:''}", r);
	}


	//=================================================================================================================
	// @Header
	//=================================================================================================================

	@Rest
	public static class E {
		@RestMethod(name=GET)
		public String echoHeaders(RestRequest req) throws Exception {
			return req.getHeaders().subset("a,b,c,d,e,f,g,h,i,a1,a2,a3,a4,b1,b2,b3,b4,c1,c2,c3,c4").toString(true);
		}
	}

	//=================================================================================================================
	// @Header, Simple values
	//=================================================================================================================

	@Remote(path="/")
	public static interface E01_RemoteResource {

		@RemoteMethod(method="GET", path="/echoHeaders")
		String normal(@Request E01_Bean rb);

		@RemoteMethod(method="GET", path="/echoHeaders")
		String serialized(@Request(partSerializer=XSerializer.class) E01_Bean rb);
	}

	public static class E01_Bean {
		@Header
		public String getA() {
			return "a1";
		}
		@Header("b")
		public String getX1() {
			return "b1";
		}
		@Header(name="c")
		public String getX2() {
			return "c1";
		}
		@Header(name="e",allowEmptyValue=true)
		public String getX4() {
			return "";
		}
		@Header("f")
		public String getX5() {
			return null;
		}
		@Header("g")
		public String getX6() {
			return "true";
		}
		@Header("h")
		public String getX7() {
			return "123";
		}
	}

	static E01_RemoteResource e01a = MockRemote.build(E01_RemoteResource.class, E.class, null);
	static E01_RemoteResource e01b = MockRestClient.create(E.class, null).partSerializer(UonSerializer.class).build().getRemote(E01_RemoteResource.class);

	@Test
	public void e01a_headerSimpleValsPlainText() throws Exception {
		String r = e01a.normal(new E01_Bean());
		assertEquals("{a:'a1',b:'b1',c:'c1',e:'',g:'true',h:'123'}", r);
	}
	@Test
	public void e01b_headerSimpleValsUon() throws Exception {
		String r = e01b.normal(new E01_Bean());
		assertEquals("{a:'a1',b:'b1',c:'c1',e:'',g:'\\'true\\'',h:'\\'123\\''}", r);
	}
	@Test
	public void e01c_headerSimpleValsX() throws Exception {
		String r = e01b.serialized(new E01_Bean());
		assertEquals("{a:'xa1x',b:'xb1x',c:'xc1x',e:'xx',g:'xtruex',h:'x123x'}", r);
	}

	//=================================================================================================================
	// @Header, Maps
	//=================================================================================================================

	@Remote(path="/")
	public static interface E02_RemoteResource {

		@RemoteMethod(method="GET", path="/echoHeaders")
		String normal(@Request E02_Bean rb);

		@RemoteMethod(method="GET", path="/echoHeaders")
		String serialized(@Request(partSerializer=XSerializer.class) E02_Bean rb);
	}

	public static class E02_Bean {
		@Header
		public Map<String,Object> getA() {
			return new AMap<String,Object>().append("a1","v1").append("a2", 123).append("a3", null).append("a4", "");
		}
		@Header("*")
		public Map<String,Object> getB() {
			return new AMap<String,Object>().append("b1","true").append("b2", "123").append("b3", "null");
		}
		@Header(name="*",allowEmptyValue=true)
		public Map<String,Object> getC() {
			return new AMap<String,Object>().append("c1","v1").append("c2", 123).append("c3", null).append("c4", "");
		}
		@Header("*")
		public Map<String,Object> getD() {
			return null;
		}
	}

	static E02_RemoteResource e02a = MockRemote.build(E02_RemoteResource.class, E.class, null);
	static E02_RemoteResource e02b = MockRestClient.create(E.class, null).partSerializer(UonSerializer.class).build().getRemote(E02_RemoteResource.class);

	@Test
	public void e02a_header_maps_plainText() throws Exception {
		String r = e02a.normal(new E02_Bean());
		assertEquals("{a:'(a1=v1,a2=123,a3=null,a4=\\'\\')',b1:'true',b2:'123',b3:'null',c1:'v1',c2:'123',c4:''}", r);
	}
	@Test
	public void e02b_header_maps_uon() throws Exception {
		String r = e02b.normal(new E02_Bean());
		assertEquals("{a:'(a1=v1,a2=123,a3=null,a4=\\'\\')',b1:'\\'true\\'',b2:'\\'123\\'',b3:'\\'null\\'',c1:'v1',c2:'123',c4:''}", r);
	}
	@Test
	public void e02c_header_maps_x() throws Exception {
		String r = e02b.serialized(new E02_Bean());
		assertEquals("{a:'x{a1=v1, a2=123, a3=null, a4=}x',b1:'xtruex',b2:'x123x',b3:'xnullx',c1:'xv1x',c2:'x123x',c4:'xx'}", r);
	}

	//=================================================================================================================
	// @Header, NameValuePairs
	//=================================================================================================================

	@Remote(path="/")
	public static interface E03_RemoteResource {

		@RemoteMethod(method="GET", path="/echoHeaders")
		String normal(@Request E03_Bean rb);

		@RemoteMethod(method="GET", path="/echoHeaders")
		String serialized(@Request(partSerializer=XSerializer.class) E03_Bean rb);
	}

	public static class E03_Bean {
		@Header(allowEmptyValue=true)
		public NameValuePairs getA() {
			return new NameValuePairs().append("a1","v1").append("a2", 123).append("a3", null).append("a4", "");
		}
		@Header(value="*",allowEmptyValue=true)
		public NameValuePairs getB() {
			return new NameValuePairs().append("b1","true").append("b2", "123").append("b3", "null");
		}
		@Header(name="*",allowEmptyValue=true)
		public NameValuePairs getC() {
			return new NameValuePairs().append("c1","v1").append("c2", 123).append("c3", null).append("c4", "");
		}
		@Header(value="*",allowEmptyValue=true)
		public NameValuePairs getD() {
			return null;
		}
	}

	static E03_RemoteResource e03a = MockRemote.build(E03_RemoteResource.class, E.class, null);
	static E03_RemoteResource e03b = MockRestClient.create(E.class, null).partSerializer(UonSerializer.class).build().getRemote(E03_RemoteResource.class);

	@Test
	public void e03a_header_nameValuePairs_plainText() throws Exception {
		String r = e03a.normal(new E03_Bean());
		assertEquals("{a1:'v1',a2:'123',a4:'',b1:'true',b2:'123',b3:'null',c1:'v1',c2:'123',c4:''}", r);
	}
	@Test
	public void e03b_header_nameValuePairs_uon() throws Exception {
		String r = e03b.normal(new E03_Bean());
		assertEquals("{a1:'v1',a2:'\\'123\\'',a4:'',b1:'\\'true\\'',b2:'\\'123\\'',b3:'\\'null\\'',c1:'v1',c2:'\\'123\\'',c4:''}", r);
	}
	@Test
	public void e03c_header_nameValuePairs_x() throws Exception {
		String r = e03b.serialized(new E03_Bean());
		assertEquals("{a1:'xv1x',a2:'x123x',a4:'xx',b1:'xtruex',b2:'x123x',b3:'xnullx',c1:'xv1x',c2:'x123x',c4:'xx'}", r);
	}

	//=================================================================================================================
	// @Header, Collections
	//=================================================================================================================

	@Remote(path="/")
	public static interface E04_RemoteResource {

		@RemoteMethod(method="GET", path="/echoHeaders")
		String normal(@Request E04_Bean rb);

		@RemoteMethod(method="GET", path="/echoHeaders")
		String serialized(@Request(partSerializer=XSerializer.class) E04_Bean rb);
	}

	public static class E04_Bean {
		@Header
		public List<Object> getA() {
			return new AList<>().append("foo").append("").append("true").append("123").append("null").append(true).append(123).append(null);
		}
		@Header("b")
		public List<Object> getX1() {
			return new AList<>().append("foo").append("").append("true").append("123").append("null").append(true).append(123).append(null);
		}
		@Header(name="c", serializer=ListSerializer.class)
		public List<Object> getX2() {
			return new AList<>().append("foo").append("").append("true").append("123").append("null").append(true).append(123).append(null);
		}
		@Header(name="d",allowEmptyValue=true)
		public List<Object> getX3() {
			return new AList<>();
		}
		@Header("e")
		public List<Object> getX4() {
			return null;
		}
		@Header("f")
		public Object[] getX5() {
			return new Object[]{"foo", "", "true", "123", "null", true, 123, null};
		}
		@Header(name="g", serializer=ListSerializer.class)
		public Object[] getX6() {
			return new Object[]{"foo", "", "true", "123", "null", true, 123, null};
		}
		@Header(name="h",allowEmptyValue=true)
		public Object[] getX7() {
			return new Object[]{};
		}
		@Header("i")
		public Object[] getX8() {
			return null;
		}
	}

	static E04_RemoteResource e04a = MockRemote.build(E04_RemoteResource.class, E.class, null);
	static E04_RemoteResource e04b = MockRestClient.create(E.class, null).partSerializer(UonSerializer.class).build().getRemote(E04_RemoteResource.class);

	@Test
	public void e04a_header_collections_plainText() throws Exception {
		String r = e04a.normal(new E04_Bean());
		assertEquals("{a:'foo,,true,123,null,true,123,null',b:'foo,,true,123,null,true,123,null',c:'foo||true|123|null|true|123|null',d:'',f:'foo,,true,123,null,true,123,null',g:'foo||true|123|null|true|123|null',h:''}", r);
	}
	@Test
	public void e04b_header_collections_uon() throws Exception {
		String r = e04b.normal(new E04_Bean());
		assertEquals("{a:'@(foo,\\'\\',\\'true\\',\\'123\\',\\'null\\',true,123,null)',b:'@(foo,\\'\\',\\'true\\',\\'123\\',\\'null\\',true,123,null)',c:'foo||true|123|null|true|123|null',d:'@()',f:'@(foo,\\'\\',\\'true\\',\\'123\\',\\'null\\',true,123,null)',g:'foo||true|123|null|true|123|null',h:'@()'}", r);
	}
	@Test
	public void e04c_header_collections_x() throws Exception {
		String r = e04b.serialized(new E04_Bean());
		assertEquals("{a:'fooXXtrueX123XnullXtrueX123Xnull',b:'fooXXtrueX123XnullXtrueX123Xnull',c:'foo||true|123|null|true|123|null',d:'',f:'fooXXtrueX123XnullXtrueX123Xnull',g:'foo||true|123|null|true|123|null',h:''}", r);
	}

	//=================================================================================================================
	// @Path
	//=================================================================================================================

	@Rest
	public static class G  {
		@RestMethod(name=GET,path="/*")
		public String echoPath(RestRequest req) throws Exception {
			return req.getPathMatch().getRemainder();
		}
	}

	//=================================================================================================================
	// @Path, Simple values
	//=================================================================================================================

	@Remote(path="/")
	public static interface G01_RemoteResource {

		@RemoteMethod(method="GET", path="/echoPath/{a}/{b}/{c}/{e}/{g}/{h}")
		String normal(@Request G01_Bean rb);

		@RemoteMethod(method="GET", path="/echoPath/{a}/{b}/{c}/{e}/{g}/{h}")
		String serialized(@Request(partSerializer=XSerializer.class) G01_Bean rb);
	}

	public static class G01_Bean {
		@Path
		public String getA() {
			return "a1";
		}
		@Path("b")
		public String getX1() {
			return "b1";
		}
		@Path(name="c")
		public String getX2() {
			return "c1";
		}
		@Path(name="e",allowEmptyValue=true)
		public String getX4() {
			return "";
		}
		@Path("g")
		public String getX6() {
			return "true";
		}
		@Path("h")
		public String getX7() {
			return "123";
		}
	}

	static G01_RemoteResource g01a = MockRemote.build(G01_RemoteResource.class, G.class, null);
	static G01_RemoteResource g01b = MockRestClient.create(G.class, null).partSerializer(UonSerializer.class).build().getRemote(G01_RemoteResource.class);

	@Test
	public void g01a_pathSimpleValsPlainText() throws Exception {
		String r = g01a.normal(new G01_Bean());
		assertEquals("echoPath/a1/b1/c1//true/123", r);
	}
	@Test
	public void g01b_pathSimpleValsUon() throws Exception {
		String r = g01b.normal(new G01_Bean());
		assertEquals("echoPath/a1/b1/c1//'true'/'123'", r);
	}
	@Test
	public void g01c_pathSimpleValsX() throws Exception {
		String r = g01b.serialized(new G01_Bean());
		assertEquals("echoPath/xa1x/xb1x/xc1x/xx/xtruex/x123x", r);
	}

	//=================================================================================================================
	// @Path, Maps
	//=================================================================================================================

	@Remote(path="/")
	public static interface G02_RemoteResource {

		@RemoteMethod(method="GET", path="/echoPath/{a1}/{a2}/{a3}/{a4}/{b1}/{b2}/{b3}/{c1}/{c2}/{c3}/{c4}")
		String normal(@Request G02_Bean rb);

		@RemoteMethod(method="GET", path="/echoPath/{a1}/{a2}/{a3}/{a4}/{b1}/{b2}/{b3}/{c1}/{c2}/{c3}/{c4}")
		String serialized(@Request(partSerializer=XSerializer.class) G02_Bean rb);
	}

	public static class G02_Bean {
		@Path(name="*",allowEmptyValue=true)
		public Map<String,Object> getA() {
			return new AMap<String,Object>().append("a1","v1").append("a2", 123).append("a3", null).append("a4", "");
		}
		@Path("*")
		public Map<String,Object> getB() {
			return new AMap<String,Object>().append("b1","true").append("b2", "123").append("b3", "null");
		}
		@Path(name="*",allowEmptyValue=true)
		public Map<String,Object> getC() {
			return new AMap<String,Object>().append("c1","v1").append("c2", 123).append("c3", null).append("c4", "");
		}
		@Path("*")
		public Map<String,Object> getD() {
			return null;
		}
	}

	static G02_RemoteResource g02a = MockRemote.build(G02_RemoteResource.class, G.class, null);
	static G02_RemoteResource g02b = MockRestClient.create(G.class, null).partSerializer(UonSerializer.class).build().getRemote(G02_RemoteResource.class);

	@Test
	public void g02a_path_maps_plainText() throws Exception {
		String r = g02a.normal(new G02_Bean());
		assertEquals("echoPath/v1/123/null//true/123/null/v1/123/null/", r);
	}
	@Test
	public void g02b_path_maps_uon() throws Exception {
		String r = g02b.normal(new G02_Bean());
		assertEquals("echoPath/v1/123/null//'true'/'123'/'null'/v1/123/null/", r);
	}
	@Test
	public void g02c_path_maps_x() throws Exception {
		String r = g02b.serialized(new G02_Bean());
		assertEquals("echoPath/xv1x/x123x/NULL/xx/xtruex/x123x/xnullx/xv1x/x123x/NULL/xx", r);
	}

	//=================================================================================================================
	// @Path, NameValuePairs
	//=================================================================================================================

	@Remote(path="/")
	public static interface G03_RemoteResource {

		@RemoteMethod(method="GET", path="/echoPath/{a1}/{a2}/{a3}/{a4}/{b1}/{b2}/{b3}/{c1}/{c2}/{c3}/{c4}")
		String normal(@Request G03_Bean rb);

		@RemoteMethod(method="GET", path="/echoPath/{a1}/{a2}/{a3}/{a4}/{b1}/{b2}/{b3}/{c1}/{c2}/{c3}/{c4}")
		String serialized(@Request(partSerializer=XSerializer.class) G03_Bean rb);
	}

	public static class G03_Bean {
		@Path(name="*",allowEmptyValue=true)
		public NameValuePairs getA() {
			return new NameValuePairs().append("a1","v1").append("a2", 123).append("a3", null).append("a4", "");
		}
		@Path("/*")
		public NameValuePairs getB() {
			return new NameValuePairs().append("b1","true").append("b2", "123").append("b3", "null");
		}
		@Path(name="*",allowEmptyValue=true)
		public NameValuePairs getC() {
			return new NameValuePairs().append("c1","v1").append("c2", 123).append("c3", null).append("c4", "");
		}
		@Path("/*")
		public NameValuePairs getD() {
			return null;
		}
	}

	static G03_RemoteResource g03a = MockRemote.build(G03_RemoteResource.class, G.class, null);
	static G03_RemoteResource g03b = MockRestClient.create(G.class, null).partSerializer(UonSerializer.class).build().getRemote(G03_RemoteResource.class);

	@Test
	public void g03a_path_nameValuePairs_plainText() throws Exception {
		String r = g03a.normal(new G03_Bean());
		assertEquals("echoPath/v1/123/null//true/123/null/v1/123/null/", r);
	}
	@Test
	public void g03b_path_nameValuePairs_uon() throws Exception {
		String r = g03b.normal(new G03_Bean());
		assertEquals("echoPath/v1/'123'/null//'true'/'123'/'null'/v1/'123'/null/", r);
	}
	@Test
	public void g03c_path_nameValuePairs_x() throws Exception {
		String r = g03b.serialized(new G03_Bean());
		assertEquals("echoPath/xv1x/x123x/NULL/xx/xtruex/x123x/xnullx/xv1x/x123x/NULL/xx", r);
	}

	//=================================================================================================================
	// @Path, Collections
	//=================================================================================================================

	@Remote(path="/")
	public static interface G04_RemoteResource {

		@RemoteMethod(method="GET", path="/echoPath/{a}/{b}/{c}/{d}/{f}/{g}/{h}")
		String normal(@Request G04_Bean rb);

		@RemoteMethod(method="GET", path="/echoPath/{a}/{b}/{c}/{d}/{f}/{g}/{h}")
		String serialized(@Request(partSerializer=XSerializer.class) G04_Bean rb);
	}

	public static class G04_Bean {
		@Path
		public List<Object> getA() {
			return new AList<>().append("foo").append("").append("true").append("123").append("null").append(true).append(123).append(null);
		}
		@Path("b")
		public List<Object> getX1() {
			return new AList<>().append("foo").append("").append("true").append("123").append("null").append(true).append(123).append(null);
		}
		@Path(name="c", serializer=ListSerializer.class)
		public List<Object> getX2() {
			return new AList<>().append("foo").append("").append("true").append("123").append("null").append(true).append(123).append(null);
		}
		@Path(name="d",allowEmptyValue=true)
		public List<Object> getX3() {
			return new AList<>();
		}
		@Path("f")
		public Object[] getX5() {
			return new Object[]{"foo", "", "true", "123", "null", true, 123, null};
		}
		@Path(name="g", serializer=ListSerializer.class)
		public Object[] getX6() {
			return new Object[]{"foo", "", "true", "123", "null", true, 123, null};
		}
		@Path(name="h",allowEmptyValue=true)
		public Object[] getX7() {
			return new Object[]{};
		}
	}

	static G04_RemoteResource g04a = MockRemote.build(G04_RemoteResource.class, G.class, null);
	static G04_RemoteResource g04b = MockRestClient.create(G.class, null).partSerializer(UonSerializer.class).build().getRemote(G04_RemoteResource.class);

	@Test
	public void g04a_path_collections_plainText() throws Exception {
		String r = g04a.normal(new G04_Bean());
		assertEquals("echoPath/foo,,true,123,null,true,123,null/foo,,true,123,null,true,123,null/foo||true|123|null|true|123|null//foo,,true,123,null,true,123,null/foo||true|123|null|true|123|null/", r);
	}
	@Test
	public void g04b_path_collections_uon() throws Exception {
		String r = g04b.normal(new G04_Bean());
		assertEquals("echoPath/@(foo,'','true','123','null',true,123,null)/@(foo,'','true','123','null',true,123,null)/foo||true|123|null|true|123|null/@()/@(foo,'','true','123','null',true,123,null)/foo||true|123|null|true|123|null/@()", r);
	}
	@Test
	public void g04c_path_collections_x() throws Exception {
		String r = g04b.serialized(new G04_Bean());
		assertEquals("echoPath/fooXXtrueX123XnullXtrueX123Xnull/fooXXtrueX123XnullXtrueX123Xnull/foo||true|123|null|true|123|null//fooXXtrueX123XnullXtrueX123Xnull/foo||true|123|null|true|123|null/", r);
	}

	//=================================================================================================================
	// Support classes
	//=================================================================================================================

	public static class XSerializer extends BaseHttpPartSerializer {
		@Override
		public HttpPartSerializerSession createPartSession(SerializerSessionArgs args) {
			return new BaseHttpPartSerializerSession() {
				@Override
				public String serialize(HttpPartType partType, HttpPartSchema schema, Object value) throws SerializeException, SchemaValidationException {
					if (value == null)
						return "NULL";
					if (value instanceof Collection)
						return join((Collection<?>)value, "X");
					if (isArray(value))
						return join(toList(value, Object.class), "X");
					return "x" + value + "x";
				}
			};
		}

		@Override
		public String serialize(HttpPartType partType, HttpPartSchema schema, Object value) throws SchemaValidationException, SerializeException {
			return createPartSession().serialize(partType, schema, value);
		}

		@Override
		public String serialize(HttpPartSchema schema, Object value) throws SchemaValidationException, SerializeException {
			return createPartSession().serialize(null, schema, value);
		}
	}

	public static class ListSerializer extends BaseHttpPartSerializer {
		@Override
		public HttpPartSerializerSession createPartSession(SerializerSessionArgs args) {
			return new BaseHttpPartSerializerSession() {
				@Override
				public String serialize(HttpPartType partType, HttpPartSchema schema, Object value) throws SerializeException, SchemaValidationException {
					if (value == null)
						return "NULL";
					if (value instanceof Collection)
						return join((Collection<?>)value, '|');
					if (isArray(value))
						return join(toList(value, Object.class), "|");
					return "?" + value + "?";
				}
			};
		}

		@Override
		public String serialize(HttpPartType partType, HttpPartSchema schema, Object value) throws SchemaValidationException, SerializeException {
			return createPartSession().serialize(partType, schema, value);
		}

		@Override
		public String serialize(HttpPartSchema schema, Object value) throws SchemaValidationException, SerializeException {
			return createPartSession().serialize(null, schema, value);
		}
	}
}
