// ***************************************************************************************************************************
// * 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.annotation2;

import static org.apache.juneau.http.HttpMethodName.*;
import static org.apache.juneau.rest.testutils.TestUtils.*;
import static org.junit.Assert.*;

import java.util.*;

import org.apache.juneau.*;
import org.apache.juneau.dto.swagger.*;
import org.apache.juneau.http.annotation.*;
import org.apache.juneau.json.*;
import org.apache.juneau.marshall.*;
import org.apache.juneau.rest.*;
import org.apache.juneau.rest.annotation.*;
import org.apache.juneau.rest.mock2.*;
import org.apache.juneau.rest.testutils.*;
import org.junit.*;
import org.junit.runners.*;

/**
 * Tests related to @Path annotation.
 */
@SuppressWarnings({})
@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class PathAnnotationTest {

	//=================================================================================================================
	// Basic tests
	//=================================================================================================================

	@Rest
	public static class A  {
		@RestMethod(name=GET, path="/")
		public void noPath(RestResponse res) {
			res.setOutput(GET);
		}
		@RestMethod(name=GET, path="/a")
		public String simplePath() {
			return "GET /a";
		}
		@RestMethod(name=GET, path="/a/{foo}")
		public String simplePathOneVar(RestResponse res, @Path("foo") String foo) {
			return "GET /a " + foo;
		}
		@RestMethod(name=GET, path="/a/{foo}/{bar}")
		public String simplePathTwoVars(RestResponse res, @Path("foo") String foo, @Path("bar") String bar) {
			return "GET /a " + foo + "," + bar;
		}
		@RestMethod(name=GET, path="/a/{foo}/{bar}/*")
		public String simplePathWithRemainder(@Path("foo") String foo, @Path("bar") int bar, @Path("/*") String remainder) {
			return "GET /a "+foo+","+bar+",r="+remainder;
		}
	}
	static MockRest a = MockRest.build(A.class, null);

	@Test
	public void a00_nonExistentPath() throws Exception {
		a.get("/bad?noTrace=true").execute().assertStatus(404);
	}
	@Test
	public void a01_noPath() throws Exception {
		a.get(null).execute().assertBody("GET");
		a.get().execute().assertBody("GET");
	}
	@Test
	public void a02_simplePath() throws Exception {
		a.get("/a").execute().assertBody("GET /a");
	}
	@Test
	public void a03_simplePathOneVar() throws Exception {
		a.get("/a/foo").execute().assertBody("GET /a foo");
	}
	@Test
	public void a04_simplePathTwoVars() throws Exception {
		a.get("/a/foo/bar").execute().assertBody("GET /a foo,bar");
	}
	@Test
	public void a05_simplePathWithRemainder() throws Exception {
		a.get("/a/foo/123/baz").execute().assertBody("GET /a foo,123,r=baz");
	}
	@Test
	public void a06_urlEncodedPathPart() throws Exception {
		// URL-encoded part should not get decoded before finding method to invoke.
		// This should match /get1/{foo} and not /get1/{foo}/{bar}
		// NOTE:  When testing on Tomcat, must specify the following system property:
		// -Dorg.apache.tomcat.util.buf.UDecoder.ALLOW_ENCODED_SLASH=true
		a.get("/a/x%2Fy").execute().assertBody("GET /a x/y");
		a.get("/a/x%2Fy/x%2Fy").execute().assertBody("GET /a x/y,x/y");
	}

	//=================================================================================================================
	// Primitives
	//=================================================================================================================

	@Rest
	public static class B  {
		@RestMethod(name=GET, path="/int/{x}/foo")
		public String b01(@Path(name="x") int x) {
			return String.valueOf(x);
		}
		@RestMethod(name=GET, path="/short/{x}/foo")
		public String b02(@Path(name="x") short x) {
			return String.valueOf(x);
		}
		@RestMethod(name=GET, path="/long/{x}/foo")
		public String b03(@Path(name="x") long x) {
			return String.valueOf(x);
		}
		@RestMethod(name=GET, path="/char/{x}/foo")
		public String b04(@Path(name="x") char x) {
			return String.valueOf(x);
		}
		@RestMethod(name=GET, path="/float/{x}/foo")
		public String b05(@Path(name="x") float x) {
			return String.valueOf(x);
		}
		@RestMethod(name=GET, path="/double/{x}/foo")
		public String b06(@Path(name="x") double x) {
			return String.valueOf(x);
		}
		@RestMethod(name=GET, path="/byte/{x}/foo")
		public String b07(@Path(name="x") byte x) {
			return String.valueOf(x);
		}
		@RestMethod(name=GET, path="/boolean/{x}/foo")
		public String b08(@Path(name="x") boolean x) {
			return String.valueOf(x);
		}
	}
	static MockRest b = MockRest.build(B.class, null);

	@Test
	public void b01_int() throws Exception {
		b.get("/int/123/foo").execute().assertBody("123");
		b.get("/int/bad/foo?noTrace=true").execute().assertStatus(400);
	}
	@Test
	public void b02_short() throws Exception {
		b.get("/short/123/foo").execute().assertBody("123");
		b.get("/short/bad/foo?noTrace=true").execute().assertStatus(400);
	}
	@Test
	public void b03_long() throws Exception {
		b.get("/long/123/foo").execute().assertBody("123");
		b.get("/long/bad/foo?noTrace=true").execute().assertStatus(400);
	}
	@Test
	public void b04_char() throws Exception {
		b.get("/char/c/foo").execute().assertBody("c");
		b.get("/char/bad/foo?noTrace=true").execute().assertStatus(400);
	}
	@Test
	public void b05_float() throws Exception {
		b.get("/float/1.23/foo").execute().assertBody("1.23");
		b.get("/float/bad/foo?noTrace=true").execute().assertStatus(400);
	}
	@Test
	public void b06_double() throws Exception {
		b.get("/double/1.23/foo").execute().assertBody("1.23");
		b.get("/double/bad/foo?noTrace=true").execute().assertStatus(400);
	}
	@Test
	public void b07_byte() throws Exception {
		b.get("/byte/123/foo").execute().assertBody("123");
		b.get("/byte/bad/foo?noTrace=true").execute().assertStatus(400);
	}
	@Test
	public void b08_boolean() throws Exception {
		b.get("/boolean/true/foo").execute().assertBody("true");
		b.get("/boolean/bad/foo?noTrace=true").execute().assertStatus(400);
	}

	//=================================================================================================================
	// Primitive objects
	//=================================================================================================================

	@Rest
	public static class C  {
		@RestMethod(name=GET, path="/Integer/{x}/foo")
		public String c01(@Path(name="x") Integer x) {
			return String.valueOf(x);
		}
		@RestMethod(name=GET, path="/Short/{x}/foo")
		public String c02(@Path(name="x") Short x) {
			return String.valueOf(x);
		}
		@RestMethod(name=GET, path="/Long/{x}/foo")
		public String c03(@Path(name="x") Long x) {
			return String.valueOf(x);
		}
		@RestMethod(name=GET, path="/Character/{x}/foo")
		public String c04(@Path(name="x") Character x) {
			return String.valueOf(x);
		}
		@RestMethod(name=GET, path="/Float/{x}/foo")
		public String c05(@Path(name="x") Float x) {
			return String.valueOf(x);
		}
		@RestMethod(name=GET, path="/Double/{x}/foo")
		public String c06(@Path(name="x") Double x) {
			return String.valueOf(x);
		}
		@RestMethod(name=GET, path="/Byte/{x}/foo")
		public String c07(@Path(name="x") Byte x) {
			return String.valueOf(x);
		}
		@RestMethod(name=GET, path="/Boolean/{x}/foo")
		public String c08(@Path(name="x") Boolean x) {
			return String.valueOf(x);
		}
	}
	static MockRest c = MockRest.build(C.class, null);

	@Test
	public void c01_Integer() throws Exception {
		c.get("/Integer/123/foo").execute().assertBody("123");
		c.get("/Integer/bad/foo?noTrace=true").execute().assertStatus(400);
	}
	@Test
	public void c02_Short() throws Exception {
		c.get("/Short/123/foo").execute().assertBody("123");
		c.get("/Short/bad/foo?noTrace=true").execute().assertStatus(400);
	}
	@Test
	public void c03_Long() throws Exception {
		c.get("/Long/123/foo").execute().assertBody("123");
		c.get("/Long/bad/foo?noTrace=true").execute().assertStatus(400);
	}
	@Test
	public void c04_Char() throws Exception {
		c.get("/Character/c/foo").execute().assertBody("c");
		c.get("/Character/bad/foo?noTrace=true").execute().assertStatus(400);
	}
	@Test
	public void c05_Float() throws Exception {
		c.get("/Float/1.23/foo").execute().assertBody("1.23");
		c.get("/Float/bad/foo?noTrace=true").execute().assertStatus(400);
	}
	@Test
	public void c06_Double() throws Exception {
		c.get("/Double/1.23/foo").execute().assertBody("1.23");
		c.get("/Double/bad/foo?noTrace=true").execute().assertStatus(400);
	}
	@Test
	public void c07_Byte() throws Exception {
		c.get("/Byte/123/foo").execute().assertBody("123");
		c.get("/Byte/bad/foo?noTrace=true").execute().assertStatus(400);
	}
	@Test
	public void c08_Boolean() throws Exception {
		c.get("/Boolean/true/foo").execute().assertBody("true");
		c.get("/Boolean/bad/foo?noTrace=true").execute().assertStatus(400);
	}

	//=================================================================================================================
	// POJOs convertible from strings
	//=================================================================================================================

	@Rest
	public static class D {
		// Object with forString(String) method
		@RestMethod(name=GET, path="/uuid/{uuid}")
		public UUID uuid(RestResponse res, @Path(name="uuid") UUID uuid) {
			return uuid;
		}
	}
	static MockRest d = MockRest.build(D.class, null);

	@Test
	public void d01_uuid() throws Exception {
		UUID uuid = UUID.randomUUID();
		d.get("/uuid/" + uuid).execute().assertBody(uuid.toString());
	}

	//=================================================================================================================
	// @Path annotation without name.
	//=================================================================================================================

	@Rest
	public static class E  {
		@RestMethod(name=GET, path="/x/{foo}/{bar}")
		public Object normal1(@Path String foo, @Path String bar) {
			return new ObjectMap().append("m","normal1").append("foo", foo).append("bar", bar);
		}
		@RestMethod(name=GET, path="/x/{foo}/x/{bar}/x")
		public Object normal2(@Path String foo, @Path String bar) {
			return new ObjectMap().append("m","normal2").append("foo", foo).append("bar", bar);
		}
		@RestMethod(name=GET, path="/y/{0}/{1}")
		public Object numbers1(@Path String foo, @Path String bar) {
			return new ObjectMap().append("m","numbers1").append("0", foo).append("1", bar);
		}
		@RestMethod(name=GET, path="/y/{0}/y/{1}/y")
		public Object numbers2(@Path String foo, @Path String bar) {
			return new ObjectMap().append("m","numbers2").append("0", foo).append("1", bar);
		}
		@RestMethod(name=GET, path="/z/{1}/z/{0}/z")
		public Object numbers3(@Path String foo, @Path String bar) {
			return new ObjectMap().append("m","numbers3").append("0", foo).append("1", bar);
		}
	}
	static MockRest e = MockRest.build(E.class, null);

	@Test
	public void e01_normal1() throws Exception {
		e.get("/x/x1/x2").execute().assertBody("{m:'normal1',foo:'x1',bar:'x2'}");
	}
	@Test
	public void e02_normal2() throws Exception {
		e.get("/x/x1/x/x2/x").execute().assertBody("{m:'normal2',foo:'x1',bar:'x2'}");
	}
	@Test
	public void e03_numbers1() throws Exception {
		e.get("/y/y1/y2").execute().assertBody("{m:'numbers1','0':'y1','1':'y2'}");
	}
	@Test
	public void e04_numbers2() throws Exception {
		e.get("/y/y1/y/y2/y").execute().assertBody("{m:'numbers2','0':'y1','1':'y2'}");
	}
	@Test
	public void e05_numbers3() throws Exception {
		e.get("/z/z1/z/z2/z").execute().assertBody("{m:'numbers3','0':'z2','1':'z1'}");
	}

	//=================================================================================================================
	// Path variables on class.
	//=================================================================================================================

	@Rest(path="/f/{a}/{b}")
	public static class F  {
		@RestMethod(name=GET, path="/")
		public String noPath(RequestPath path) {
			return format("noPath: {0}", path);
		}
		@RestMethod(name=GET, path="/*")
		public String noPath2(RequestPath path) {
			return format("noPath2: {0}", path);
		}
		@RestMethod(name=GET, path="/a")
		public String noVars(RequestPath path) {
			return format("noVars: {0}", path);
		}
		@RestMethod(name=GET, path="/b/{c}/{d}")
		public String twoVars(RequestPath path) {
			return format("twoVars: {0}", path);
		}
		@RestMethod(name=GET, path="/c/{a}/{b}")
		public String twoVarsOverlapping(RequestPath path) {
			return format("twoVarsOverlapping: {0}", path);
		}
		@RestMethod(name=GET, path="/d/{c}/{d}/*")
		public String withRemainder(RequestPath path) {
			return format("withRemainder: {0}", path);
		}
		private String format(String msg, Object...args) {
			return SimpleJson.DEFAULT.format(msg, args);
		}
	}
	static MockRest f = MockRest.create(F.class).servletPath("/f").build();

	@Test
	public void f01a_noPath() throws Exception {
		f.get("/f/x1/x2").execute().assertBody("noPath: {a:'x1',b:'x2'}");
	}

	@Test
	public void f01b_incompletePath() throws Exception {
		f.get("/f/x1").execute().assertStatus(404);
		f.get("/f").execute().assertStatus(404);
	}

	@Test
	public void f01c_noPath_blanks() throws Exception {
		f.get("/f//").execute().assertStatus(404);
		f.get("/f/x/").execute().assertStatus(404);
		f.get("/f//x").execute().assertStatus(404);
	}

	@Test
	public void f02a_noPath2() throws Exception {
		f.get("/f/x1/x2/foo").execute().assertBody("noPath2: {'/*':'foo','/**':'foo',a:'x1',b:'x2'}");
	}

	@Test
	public void f02b_noPath2_blanks() throws Exception {
		f.get("/f///foo").execute().assertStatus(404);
		f.get("/f/x1//foo").execute().assertStatus(404);
		f.get("/f//x2/foo").execute().assertStatus(404);
	}

	@Test
	public void f03a_noVars() throws Exception {
		f.get("/f/x1/x2/a").execute().assertBody("noVars: {a:'x1',b:'x2'}");
	}

	@Test
	public void f03b_noVars_blanks() throws Exception {
		f.get("/f///a").execute().assertStatus(404);
		f.get("/f/x1//a").execute().assertStatus(404);
		f.get("/f//x2/a").execute().assertStatus(404);
	}

	@Test
	public void f04a_twoVars() throws Exception {
		f.get("/f/x1/x2/b/x3/x4").execute().assertBody("twoVars: {a:'x1',b:'x2',c:'x3',d:'x4'}");
	}

	@Test
	public void f04b_twoVars_blanks() throws Exception {
		f.get("/f//x2/b/x3/x4").execute().assertStatus(404);
		f.get("/f/x1//b/x3/x4").execute().assertStatus(404);
		f.get("/f/x1/x2/b//x4").execute().assertStatus(200);
		f.get("/f/x1/x2/b/x3/").execute().assertStatus(200);
		f.get("/f///b//").execute().assertStatus(404);
	}

	@Test
	public void f05_twoVarsOverlapping() throws Exception {
		f.get("/f/x1/x2/c/x3/x4").execute().assertBody("twoVarsOverlapping: {a:'x3',b:'x4'}");
	}

	@Test
	public void f06a_withRemainder() throws Exception {
		f.get("/f/x1/x2/d/x3/x4").execute().assertBody("withRemainder: {a:'x1',b:'x2',c:'x3',d:'x4'}");
	}

	@Test
	public void f06b_withRemainder_lank() throws Exception {
		f.get("/f/x1/x2/d/x3/x4/").execute().assertBody("withRemainder: {'/*':'','/**':'',a:'x1',b:'x2',c:'x3',d:'x4'}");
	}

	@Test
	public void f06c_withRemainderWithStuff() throws Exception {
		f.get("/f/x1/x2/d/x3/x4/foo/bar").execute().assertBody("withRemainder: {'/*':'foo/bar','/**':'foo/bar',a:'x1',b:'x2',c:'x3',d:'x4'}");
	}

	//=================================================================================================================
	// Path variables on child class.
	//=================================================================================================================

	@Rest(children={F.class})
	public static class G {}

	static MockRest g = MockRest.create(G.class).build();

	@Test
	public void g01a_noPath() throws Exception {
		g.get("/f/x1/x2").execute().assertBody("noPath: {a:'x1',b:'x2'}");
	}

	@Test
	public void g01b_incompletePath() throws Exception {
		g.get("/f/x1").execute().assertStatus(404);
		g.get("/f").execute().assertStatus(404);
	}

	@Test
	public void g01c_noPath_blanks() throws Exception {
		g.get("/f//").execute().assertStatus(404);
		g.get("/f/x1/").execute().assertStatus(404);
		g.get("/f//x2").execute().assertStatus(404);
	}

	@Test
	public void g02a_noVars() throws Exception {
		g.get("/f/x1/x2/a").execute().assertBody("noVars: {a:'x1',b:'x2'}");
	}

	@Test
	public void g02b_noVars_blanks() throws Exception {
		g.get("/f///a").execute().assertStatus(404);
		g.get("/f/x1//a").execute().assertStatus(404);
		g.get("/f//x2/a").execute().assertStatus(404);
	}

	@Test
	public void g03a_twoVars() throws Exception {
		g.get("/f/x1/x2/b/x3/x4").execute().assertBody("twoVars: {a:'x1',b:'x2',c:'x3',d:'x4'}");
	}

	@Test
	public void g03b_twoVars_blanks() throws Exception {
		g.get("/f//x2/b/x3/x4").execute().assertStatus(404);
		g.get("/f/x1//b/x3/x4").execute().assertStatus(404);
		g.get("/f/x1/x2/b//x4").execute().assertStatus(200);
		g.get("/f/x1/x2/b/x3/").execute().assertStatus(200);
		g.get("/f///b//").execute().assertStatus(404);
	}

	@Test
	public void g04_twoVarsOverlapping() throws Exception {
		g.get("/f/x1/x2/c/x3/x4").execute().assertBody("twoVarsOverlapping: {a:'x3',b:'x4'}");
	}

	@Test
	public void g05a_withRemainder() throws Exception {
		g.get("/f/x1/x2/d/x3/x4").execute().assertBody("withRemainder: {a:'x1',b:'x2',c:'x3',d:'x4'}");
	}

	@Test
	public void g05b_withRemainderBlank() throws Exception {
		g.get("/f/x1/x2/d/x3/x4/").execute().assertBody("withRemainder: {'/*':'','/**':'',a:'x1',b:'x2',c:'x3',d:'x4'}");
	}

	@Test
	public void g05c_withRemainderWithStuff() throws Exception {
		g.get("/f/x1/x2/d/x3/x4/foo/bar").execute().assertBody("withRemainder: {'/*':'foo/bar','/**':'foo/bar',a:'x1',b:'x2',c:'x3',d:'x4'}");
	}

	//=================================================================================================================
	// Path variables on parent and child class.
	//=================================================================================================================

	@Rest(path="/h/{ha}/{hb}", children={F.class})
	public static class H {}

	static MockRest h = MockRest.create(H.class).servletPath("/h").build();

	@Test
	public void h01a_noPath() throws Exception {
		h.get("/h/ha1/hb1/f/x1/x2").execute().assertBody("noPath: {a:'x1',b:'x2',ha:'ha1',hb:'hb1'}");
	}

	@Test
	public void h01b_incompletePath() throws Exception {
		// These are 405 instead of 404 because when children don't match, we try to find a matching Java method.
		h.get("/h/ha1/hb1/f/x1").execute().assertStatus(404);
		h.get("/h/ha1/hb1/f").execute().assertStatus(404);
		h.get("/h/ha1/hb1").execute().assertStatus(404);
		h.get("/h/ha1").execute().assertStatus(404);
		h.get("/h").execute().assertStatus(404);
	}

	@Test
	public void h01c_noPath_blanks() throws Exception {
		h.get("/h//hb1/f/x1/x2").execute().assertStatus(404);
		h.get("/h/ha1//f/x1/x2").execute().assertStatus(404);
		h.get("/h/ha1/hb1/f//x2").execute().assertStatus(404);
		h.get("/h/ha1/hb1/f/x1/").execute().assertStatus(404);
		h.get("/h///f//").execute().assertStatus(404);
	}

	@Test
	public void h02a_noPath2() throws Exception {
		h.get("/h/ha1/hb1/f/x1/x2/foo").execute().assertBody("noPath2: {'/*':'foo','/**':'foo',a:'x1',b:'x2',ha:'ha1',hb:'hb1'}");
	}

	@Test
	public void h02b_noPath2_blanks() throws Exception {
		h.get("/h//hb1/f/x1/x2/foo").execute().assertStatus(404);
		h.get("/h/ha1//f/x1/x2/foo").execute().assertStatus(404);
		h.get("/h/ha1/hb1/f//x2/foo").execute().assertStatus(404);
		h.get("/h/ha1/hb1/f/x1//foo").execute().assertStatus(404);
		h.get("/h///f///foo").execute().assertStatus(404);
	}

	@Test
	public void h03a_noVars() throws Exception {
		h.get("/h/ha1/hb1/f/x1/x2/a").execute().assertBody("noVars: {a:'x1',b:'x2',ha:'ha1',hb:'hb1'}");
	}

	@Test
	public void h03b_noVars_blanks() throws Exception {
		h.get("/h//hb1/f/x1/x2/a").execute().assertStatus(404);
		h.get("/h/ha1//f/x1/x2/a").execute().assertStatus(404);
		h.get("/h/ha1/hb1/f//x2/a").execute().assertStatus(404);
		h.get("/h/ha1/hb1/f/x1//a").execute().assertStatus(404);
		h.get("/h///f///a").execute().assertStatus(404);
	}

	@Test
	public void h04_twoVars() throws Exception {
		h.get("/h/ha1/hb1/f/x1/x2/b/x3/x4").execute().assertBody("twoVars: {a:'x1',b:'x2',c:'x3',d:'x4',ha:'ha1',hb:'hb1'}");
	}

	@Test
	public void h05_twoVarsOverlapping() throws Exception {
		h.get("/h/ha1/hb1/f/x1/x2/c/x3/x4").execute().assertBody("twoVarsOverlapping: {a:'x3',b:'x4',ha:'ha1',hb:'hb1'}");
	}

	@Test
	public void h06a_withRemainder() throws Exception {
		h.get("/h/ha1/hb1/f/x1/x2/d/x3/x4").execute().assertBody("withRemainder: {a:'x1',b:'x2',c:'x3',d:'x4',ha:'ha1',hb:'hb1'}");
	}

	@Test
	public void h06b_withRemainderBlank() throws Exception {
		h.get("/h/ha1/hb1/f/x1/x2/d/x3/x4/").execute().assertBody("withRemainder: {'/*':'','/**':'',a:'x1',b:'x2',c:'x3',d:'x4',ha:'ha1',hb:'hb1'}");
	}

	@Test
	public void h06c_withRemainderWithStuff() throws Exception {
		h.get("/h/ha1/hb1/f/x1/x2/d/x3/x4/foo/bar").execute().assertBody("withRemainder: {'/*':'foo/bar','/**':'foo/bar',a:'x1',b:'x2',c:'x3',d:'x4',ha:'ha1',hb:'hb1'}");
	}

	//=================================================================================================================
	// Path variables on parents and child class.
	//=================================================================================================================

	@Rest(path="/i/{ia}/{ib}", children={H.class})
	public static class I {}

	static MockRest i = MockRest.create(I.class).servletPath("/i").build();

	@Test
	public void i01a_noPath() throws Exception {
		i.get("/i/ia1/ib1/h/ha1/hb1/f/x1/x2").execute().assertBody("noPath: {a:'x1',b:'x2',ha:'ha1',hb:'hb1',ia:'ia1',ib:'ib1'}");
	}

	@Test
	public void i01b_incompletePath() throws Exception {
		i.get("/i/ia1/ib1/h/ha1/hb1/f/x1").execute().assertStatus(404);
		i.get("/i/ia1/ib1/h/ha1/hb1/f").execute().assertStatus(404);
		i.get("/i/ia1/ib1/h/ha1/hb1").execute().assertStatus(404);
		i.get("/i/ia1/ib1/h/ha1").execute().assertStatus(404);
		i.get("/i/ia1/ib1/h").execute().assertStatus(404);
		i.get("/i/ia1/ib1").execute().assertStatus(404);
		i.get("/i/ia1").execute().assertStatus(404);
		i.get("/i").execute().assertStatus(404);
	}

	@Test
	public void i01c_noPath_blanks() throws Exception {
		i.get("/i//ib1/h/ha1/hb1/f/x1/x2").execute().assertStatus(404);
		i.get("/i/ia1//h/ha1/hb1/f/x1/x2").execute().assertStatus(404);
		i.get("/i/ia1/ib1/h//hb1/f/x1/x2").execute().assertStatus(404);
		i.get("/i/ia1/ib1/h/ha1//f/x1/x2").execute().assertStatus(404);
		i.get("/i/ia1/ib1/h/ha1/hb1/f//x2").execute().assertStatus(404);
		i.get("/i/ia1/ib1/h/ha1/hb1/f/x1/").execute().assertStatus(404);
		i.get("/i///h///f//").execute().assertStatus(404);
	}

	@Test
	public void i02_noPath2() throws Exception {
		i.get("/i/ia1/ib1/h/ha1/hb1/f/x1/x2/foo").execute().assertBody("noPath2: {'/*':'foo','/**':'foo',a:'x1',b:'x2',ha:'ha1',hb:'hb1',ia:'ia1',ib:'ib1'}");
	}

	@Test
	public void i03_noVars() throws Exception {
		i.get("/i/ia1/ib1/h/ha1/hb1/f/x1/x2/a").execute().assertBody("noVars: {a:'x1',b:'x2',ha:'ha1',hb:'hb1',ia:'ia1',ib:'ib1'}");
	}

	@Test
	public void i04_twoVars() throws Exception {
		i.get("/i/ia1/ib1/h/ha1/hb1/f/x1/x2/b/x3/x4").execute().assertBody("twoVars: {a:'x1',b:'x2',c:'x3',d:'x4',ha:'ha1',hb:'hb1',ia:'ia1',ib:'ib1'}");
	}

	@Test
	public void i05_twoVarsOverlapping() throws Exception {
		i.get("/i/ia1/ib1/h/ha1/hb1/f/x1/x2/c/x3/x4").execute().assertBody("twoVarsOverlapping: {a:'x3',b:'x4',ha:'ha1',hb:'hb1',ia:'ia1',ib:'ib1'}");
	}

	@Test
	public void i06a_withRemainder() throws Exception {
		i.get("/i/ia1/ib1/h/ha1/hb1/f/x1/x2/d/x3/x4").execute().assertBody("withRemainder: {a:'x1',b:'x2',c:'x3',d:'x4',ha:'ha1',hb:'hb1',ia:'ia1',ib:'ib1'}");
	}

	@Test
	public void i06b_withRemainderBlank() throws Exception {
		i.get("/i/ia1/ib1/h/ha1/hb1/f/x1/x2/d/x3/x4/").execute().assertBody("withRemainder: {'/*':'','/**':'',a:'x1',b:'x2',c:'x3',d:'x4',ha:'ha1',hb:'hb1',ia:'ia1',ib:'ib1'}");
	}

	@Test
	public void i06c_withRemainderWithStuff() throws Exception {
		i.get("/i/ia1/ib1/h/ha1/hb1/f/x1/x2/d/x3/x4/foo/bar").execute().assertBody("withRemainder: {'/*':'foo/bar','/**':'foo/bar',a:'x1',b:'x2',c:'x3',d:'x4',ha:'ha1',hb:'hb1',ia:'ia1',ib:'ib1'}");
	}

	//=================================================================================================================
	// Optional path parameter.
	//=================================================================================================================

	@Rest(serializers=SimpleJsonSerializer.class)
	public static class J {
		@RestMethod(name=GET,path="/a/{f1}")
		public Object a(@Path("f1") Optional<Integer> f1) throws Exception {
			assertNotNull(f1);
			return f1;
		}
		@RestMethod(name=GET,path="/b/{f1}")
		public Object b(@Path("f1") Optional<ABean> f1) throws Exception {
			assertNotNull(f1);
			return f1;
		}
		@RestMethod(name=GET,path="/c/{f1}")
		public Object c(@Path("f1") Optional<List<ABean>> f1) throws Exception {
			assertNotNull(f1);
			return f1;
		}
		@RestMethod(name=GET,path="/d/{f1}")
		public Object d(@Path("f1") List<Optional<ABean>> f1) throws Exception {
			return f1;
		}
	}
	static MockRest j = MockRest.create(J.class).json().build();

	@Test
	public void j01_optionalParam_integer() throws Exception {
		j.get("/a/123").execute().assertStatus(200).assertBody("123");
	}

	@Test
	public void j02_optionalParam_bean() throws Exception {
		j.get("/b/(a=1,b=foo)").execute().assertStatus(200).assertBody("{a:1,b:'foo'}");
	}

	@Test
	public void j03_optionalParam_listOfBeans() throws Exception {
		j.get("/c/@((a=1,b=foo))").execute().assertStatus(200).assertBody("[{a:1,b:'foo'}]");
	}

	@Test
	public void j04_optionalParam_listOfOptionals() throws Exception {
		j.get("/d/@((a=1,b=foo))").execute().assertStatus(200).assertBody("[{a:1,b:'foo'}]");
	}


	//=================================================================================================================
	// @Path on POJO
	//=================================================================================================================

	//-----------------------------------------------------------------------------------------------------------------
	// Basic tests
	//-----------------------------------------------------------------------------------------------------------------

	@Rest
	public static class SA {

		@Path(
			name="P",
			description={"a","b"},
			type="string",
			_enum="a,b",
			example="a"
		)
		public static class SA01 {
			public SA01(String x) {}
			@Override
			public String toString() {
				return "sa01";
			}
		}
		@RestMethod(name=GET,path="/basic/{P}")
		public void sa01(SA01 f) {}

		@Path(
			name="P",
			api={
				"description:'a\nb',",
				"type:'string',",
				"enum:['a','b'],",
				"x-example:'a'"
			}
		)
		public static class SA02 {
			public SA02(String x) {}
			@Override
			public String toString() {
				return "sa02";
			}
		}
		@RestMethod(name=GET,path="/api/{P}")
		public void sa02(SA02 f) {}

		@Path(
			name="P",
			api={
				"description:'b\nc',",
				"type:'string',",
				"enum:['b','c'],",
				"x-example:'b'"
			},
			description={"a","b"},
			type="string",
			_enum="a,b",
			example="a"
		)
		public static class SA03 {
			public SA03(String x) {}
			@Override
			public String toString() {
				return "sa03";
			}
		}
		@RestMethod(name=GET,path="/mixed/{P}")
		public void sa03(SA03 f) {}


		@Path("P")
		public static class SA04 {
			@Override
			public String toString() {
				return "sa04";
			}
		}
		@RestMethod(name=GET,path="/value/{P}")
		public void sa04(SA04 f) {}

		@Path(name="P", _enum={" ['a','b'] "})
		public static class SA19 {
			@Override
			public String toString() {
				return "sa19";
			}
		}
		@RestMethod(name=GET,path="/enum/{P}")
		public void sa19(SA19 f) {}
	}

	static Swagger sa = getSwagger(SA.class);

	@Test
	public void sa01_Path_onPojo_basic() throws Exception {
		ParameterInfo x = sa.getParameterInfo("/basic/{P}","get","path","P");
		assertEquals("P", x.getName());
		assertEquals("a\nb", x.getDescription());
		assertEquals("string", x.getType());
		assertObjectEquals("['a','b']", x.getEnum());
		assertEquals("a", x.getExample());
		assertObjectEquals("{'in':'path',name:'P',type:'string',description:'a\\nb',required:true,'enum':['a','b'],'x-example':'a','x-examples':{example:'/basic/a'}}", x);
	}
	@Test
	public void sa02_Path_onPojo_api() throws Exception {
		ParameterInfo x = sa.getParameterInfo("/api/{P}","get","path","P");
		assertEquals("P", x.getName());
		assertEquals("a\nb", x.getDescription());
		assertEquals("string", x.getType());
		assertObjectEquals("['a','b']", x.getEnum());
		assertEquals("a", x.getExample());
		assertObjectEquals("{'in':'path',name:'P',type:'string',description:'a\\nb',required:true,'enum':['a','b'],'x-example':'a','x-examples':{example:'/api/a'}}", x);
	}
	@Test
	public void sa03_Path_onPojo_mixed() throws Exception {
		ParameterInfo x = sa.getParameterInfo("/mixed/{P}","get","path","P");
		assertEquals("P", x.getName());
		assertEquals("a\nb", x.getDescription());
		assertEquals("string", x.getType());
		assertObjectEquals("['a','b']", x.getEnum());
		assertEquals("a", x.getExample());
		assertObjectEquals("{'in':'path',name:'P',type:'string',description:'a\\nb',required:true,'enum':['a','b'],'x-example':'a','x-examples':{example:'/mixed/a'}}", x);
	}
	@Test
	public void sa04_Path_onPojo_value() throws Exception {
		ParameterInfo x = sa.getParameterInfo("/value/{P}","get","path","P");
		assertEquals("P", x.getName());
		assertObjectEquals("{'in':'path',name:'P',type:'string',required:true}", x);
	}
	@Test
	public void sa05_Path_onPojo_enum() throws Exception {
		ParameterInfo x = sa.getParameterInfo("/enum/{P}","get","path","P");
		assertObjectEquals("['a','b']", x.getEnum());
		assertObjectEquals("{'in':'path',name:'P',type:'string',required:true,'enum':['a','b']}", x);
	}

	//-----------------------------------------------------------------------------------------------------------------
	// Schema
	//-----------------------------------------------------------------------------------------------------------------

	@Rest
	public static class SB {

		@Path(name="P")
		public static class SB01 {}
		@RestMethod(name=GET,path="/schemaValue/{P}")
		public void sb01(SB01 f) {}

		@Path("P")
		public static class SB02 {
			public String f1;
		}
		@RestMethod(name=GET,path="/autoDetectBean/{P}")
		public void sb02(SB02 b) {}

		@Path("P")
		public static class SB03 extends LinkedList<String> {
			private static final long serialVersionUID = 1L;
		}
		@RestMethod(name=GET,path="/autoDetectList/{P}")
		public void sb03(SB03 b) {}

		@Path("P")
		public static class SB04 {}
		@RestMethod(name=GET,path="/autoDetectStringObject/{P}")
		public void sb04(SB04 b) {}
	}

	static Swagger sb = getSwagger(SB.class);

	@Test
	public void sb01_Path_onPojo_schemaValue() throws Exception {
		ParameterInfo x = sb.getParameterInfo("/schemaValue/{P}","get","path","P");
		assertObjectEquals("{'in':'path',name:'P',type:'string',required:true}", x);
	}
	@Test
	public void sb02_Path_onPojo_autoDetectBean() throws Exception {
		ParameterInfo x = sb.getParameterInfo("/autoDetectBean/{P}","get","path","P");
		assertObjectEquals("{'in':'path',name:'P',type:'object',required:true,schema:{properties:{f1:{type:'string'}}}}", x);
	}
	@Test
	public void sb03_Path_onPojo_autoDetectList() throws Exception {
		ParameterInfo x = sb.getParameterInfo("/autoDetectList/{P}","get","path","P");
		assertObjectEquals("{'in':'path',name:'P',type:'array',required:true,items:{type:'string'}}", x);
	}
	@Test
	public void sb04_Path_onPojo_autoDetectStringObject() throws Exception {
		ParameterInfo x = sb.getParameterInfo("/autoDetectStringObject/{P}","get","path","P");
		assertObjectEquals("{'in':'path',name:'P',type:'string',required:true}", x);
	}

	//-----------------------------------------------------------------------------------------------------------------
	// Examples
	//-----------------------------------------------------------------------------------------------------------------

	@Rest
	public static class SC {

		@Path(name="P", example={" {f1:'a'} "})
		public static class SC01 {
			public String f1;
		}
		@RestMethod(name=GET,path="/example/{P}")
		public void sc01(SC01 f) {}
	}


	static Swagger sc = getSwagger(SC.class);

	@Test
	public void sc01_Path_onPojo_example() throws Exception {
		ParameterInfo x = sc.getParameterInfo("/example/{P}","get","path","P");
		assertEquals("{f1:'a'}", x.getExample());
	}

	//=================================================================================================================
	// @Path on parameter
	//=================================================================================================================

	//-----------------------------------------------------------------------------------------------------------------
	// Basic tests
	//-----------------------------------------------------------------------------------------------------------------

	@Rest
	public static class TA {

		@RestMethod(name=GET,path="/basic/{P}")
		public void ta01(@Path(
			name="P",
			description="a",
			type="string"
		) String h) {}

		@RestMethod(name=GET,path="/api/{P}")
		public void ta02(@Path(
			name="P",
			api={
				"description:'a',",
				"type:'string'"
			}
		) String h) {}

		@RestMethod(name=GET,path="/mixed/{P}")
		public void ta03(@Path(
			name="P",
			api={
				"description:'b',",
				"type:'string'"
			},
			description="a",
			type="string"
		) String h) {}

		@RestMethod(name=GET,path="/value/{P}")
		public void ta04(@Path("P") String h) {}

		@RestMethod(name=GET,path="/enum/{P}")
		public void ta05(@Path(name="P", _enum={" ['a','b'] "}) String h) {}
	}

	static Swagger ta = getSwagger(TA.class);

	@Test
	public void ta01_Path_onParameter_basic() throws Exception {
		ParameterInfo x = ta.getParameterInfo("/basic/{P}","get","path","P");
		assertEquals("a", x.getDescription());
		assertEquals("string", x.getType());
	}
	@Test
	public void ta02_Path_onParameter_api() throws Exception {
		ParameterInfo x = ta.getParameterInfo("/api/{P}","get","path","P");
		assertEquals("a", x.getDescription());
		assertEquals("string", x.getType());
	}
	@Test
	public void ta03_Path_onParameter_mixed() throws Exception {
		ParameterInfo x = ta.getParameterInfo("/mixed/{P}","get","path","P");
		assertEquals("a", x.getDescription());
		assertEquals("string", x.getType());
	}
	@Test
	public void ta04_Path_onParameter_value() throws Exception {
		ParameterInfo x = ta.getParameterInfo("/value/{P}","get","path","P");
		assertEquals("P", x.getName());
	}
	@Test
	public void ta05_Path_onParameter_enum() throws Exception {
		ParameterInfo x = ta.getParameterInfo("/enum/{P}","get","path","P");
		assertObjectEquals("['a','b']", x.getEnum());
	}

	//-----------------------------------------------------------------------------------------------------------------
	// Schema
	//-----------------------------------------------------------------------------------------------------------------

	@Rest
	public static class TB {

		@RestMethod(name=GET,path="/schemaValue/{P}")
		public void tb01(@Path(name="P") String h) {}

		public static class TB02 {
			public String f1;
		}
		@RestMethod(name=GET,path="/autoDetectBean/{P}")
		public void tb02(@Path("P") TB02 b) {}

		public static class TB03 extends LinkedList<String> {
			private static final long serialVersionUID = 1L;
		}
		@RestMethod(name=GET,path="/autoDetectList/{P}")
		public void tb03(@Path("P") TB03 b) {}

		public static class TB04 {}
		@RestMethod(name=GET,path="/autoDetectStringObject/{P}")
		public void tb04(@Path("P") TB04 b) {}

		@RestMethod(name=GET,path="/autoDetectInteger/{P}")
		public void tb05(@Path("P") Integer b) {}

		@RestMethod(name=GET,path="/autoDetectBoolean/{P}")
		public void tb06(@Path("P") Boolean b) {}
	}

	static Swagger tb = getSwagger(TB.class);

	@Test
	public void tb01_Path_onParameter_schemaValue() throws Exception {
		ParameterInfo x = tb.getParameterInfo("/schemaValue/{P}","get","path","P");
		assertObjectEquals("{'in':'path',name:'P',type:'string',required:true}", x);
	}
	@Test
	public void tb02_Path_onParameter_autoDetectBean() throws Exception {
		ParameterInfo x = tb.getParameterInfo("/autoDetectBean/{P}","get","path","P");
		assertObjectEquals("{'in':'path',name:'P',type:'object',required:true,schema:{properties:{f1:{type:'string'}}}}", x);
	}
	@Test
	public void tb03_Path_onParameter_autoDetectList() throws Exception {
		ParameterInfo x = tb.getParameterInfo("/autoDetectList/{P}","get","path","P");
		assertObjectEquals("{'in':'path',name:'P',type:'array',required:true,items:{type:'string'}}", x);
	}
	@Test
	public void tb04_Path_onParameter_autoDetectStringObject() throws Exception {
		ParameterInfo x = tb.getParameterInfo("/autoDetectStringObject/{P}","get","path","P");
		assertObjectEquals("{'in':'path',name:'P',type:'string',required:true}", x);
	}
	@Test
	public void tb05_Path_onParameter_autoDetectInteger() throws Exception {
		ParameterInfo x = tb.getParameterInfo("/autoDetectInteger/{P}","get","path","P");
		assertObjectEquals("{'in':'path',name:'P',type:'integer',required:true,format:'int32'}", x);
	}
	@Test
	public void tb06_Path_onParameter_autoDetectBoolean() throws Exception {
		ParameterInfo x = tb.getParameterInfo("/autoDetectBoolean/{P}","get","path","P");
		assertObjectEquals("{'in':'path',name:'P',type:'boolean',required:true}", x);
	}

	//-----------------------------------------------------------------------------------------------------------------
	// Examples
	//-----------------------------------------------------------------------------------------------------------------

	@Rest
	public static class TC {

		@RestMethod(name=GET,path="/example/{P}")
		public void ta21(@Path(name="P", example="{f1:'b'}") String h) {}
	}

	static Swagger tc = getSwagger(TC.class);

	@Test
	public void tc01_Path_onParameter_example2() throws Exception {
		ParameterInfo x = tc.getParameterInfo("/example/{P}","get","path","P");
		assertEquals("{f1:'b'}", x.getExample());
	}
}