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

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

import java.io.*;

import org.apache.juneau.*;
import org.apache.juneau.parser.*;
import org.apache.juneau.serializer.*;
import org.junit.*;

@SuppressWarnings({})
public class JsonParserTest {

	private static final JsonParser p = JsonParser.DEFAULT;
	private static final JsonParser sp = JsonParser.DEFAULT_STRICT;


	//====================================================================================================
	// Test invalid input
	//====================================================================================================
	@Test
	public void testInvalidJson() {
		try {
			p.parse("{\na:1,\nb:xxx\n}", Object.class);
			fail("Exception expected.");
		} catch (ParseException e) {}
	}

	@Test
	public void testNonExistentAttribute() throws Exception {
		String json = "{foo:,bar:}";
		ObjectMap m = p.parse(json, ObjectMap.class);
		assertEquals("{foo:null,bar:null}", m.toString());
	}

	@Test
	public void testNonStringAsString() throws Exception {
		String json = "123";
		String s;

		// Strict mode does not allow unquoted values.
		try {
			sp.parse(json, String.class);
			fail("Exception expected");
		} catch (Exception e) {
			assertTrue(e.getMessage().contains("Did not find quote character"));
		}

		s = p.parse(json, String.class);
		assertEquals("123", s);

		json = " 123 ";
		// Strict mode does not allow unquoted values.
		try {
			sp.parse(json, String.class);
			fail("Exception expected");
		} catch (Exception e) {
			assertTrue(e.getMessage().contains("Did not find quote character"));
		}

		s = p.parse(json, String.class);
		assertEquals("123", s);

		json = "{\"fa\":123}";
		try {
			sp.parse(json, A.class);
			fail("Exception expected");
		} catch (Exception e) {
			assertTrue(e.getMessage().contains("Did not find quote character"));
		}

		A a = p.parse(json, A.class);
		assertEquals("123", a.fa);

		json = " { \"fa\" : 123 } ";
		try {
			sp.parse(json, A.class);
			fail("Exception expected");
		} catch (Exception e) {
			assertTrue(e.getMessage().contains("Did not find quote character"));
		}

		a = p.parse(json, A.class);
		assertEquals("123", a.fa);

		json = "'123'";
		try {
			sp.parse(json, String.class);
			fail("Exception expected");
		} catch (Exception e) {
			assertTrue(e.getMessage().contains("Invalid quote character"));
		}
	}

	public static class A {
		public String fa;
	}

	@Test
	public void testStrictMode() throws Exception {
		JsonParser p = sp;

		// Missing attribute values.
		String json = "{\"foo\":,\"bar\":}";
		try {
			p.parse(json, ObjectMap.class);
			fail("Exception expected");
		} catch (ParseException e) {
			assertTrue(e.getRootCause().getMessage().contains("Missing value detected."));
		}

		// Single quoted values.
		json = "{\"foo\":'bar'}";
		try {
			p.parse(json, ObjectMap.class);
			fail("Exception expected");
		} catch (ParseException e) {
			assertTrue(e.getRootCause().getMessage().contains("Invalid quote character"));
		}

		// Single quoted attribute name.
		json = "{'foo':\"bar\"}";
		try {
			p.parse(json, ObjectMap.class);
			fail("Exception expected");
		} catch (ParseException e) {
			assertTrue(e.getRootCause().getMessage().contains("Invalid quote character"));
		}

		// Unquoted attribute name.
		json = "{foo:\"bar\"}";
		try {
			p.parse(json, ObjectMap.class);
			fail("Exception expected");
		} catch (ParseException e) {
			assertTrue(e.getRootCause().getMessage().contains("Unquoted attribute detected."));
		}

		// Concatenated string
		json = "{\"foo\":\"bar\"+\"baz\"}";
		try {
			p.parse(json, ObjectMap.class);
			fail("Exception expected");
		} catch (ParseException e) {
			assertTrue(e.getRootCause().getMessage().contains("String concatenation detected."));
		}

		// Concatenated string 2
		json = "{\"foo\":\"bar\" + \"baz\"}";
		try {
			p.parse(json, ObjectMap.class);
			fail("Exception expected");
		} catch (ParseException e) {
			assertTrue(e.getRootCause().getMessage().contains("String concatenation detected."));
		}

		json = "{\"foo\":/*comment*/\"bar\"}";
		try {
			p.parse(json, ObjectMap.class);
			fail("Exception expected");
		} catch (ParseException e) {
			assertTrue(e.getRootCause().getMessage().contains("Javascript comment detected."));
		}
	}

	/**
	 * JSON numbers and booleans should be representable as strings and converted accordingly.
	 */
	@Test
	public void testPrimitivesAsStrings() throws Exception {
		String json;
		ReaderParser p = JsonParser.DEFAULT;
		WriterSerializer s = SimpleJsonSerializer.DEFAULT;

		json = "{f01:'1',f02:'1',f03:'true',f04:'true',f05:'1',f06:'1',f07:'1',f08:'1',f09:'1',f10:'1'}";
		B b = p.parse(json, B.class);
		assertEquals("{f01:1,f02:1,f03:true,f04:true,f05:1.0,f06:1.0,f07:1,f08:1,f09:1,f10:1}", s.toString(b));

		json = "{f01:'',f02:'',f03:'',f04:'',f05:'',f06:'',f07:'',f08:'',f09:'',f10:''}";
		b = p.parse(json, B.class);
		assertEquals("{f01:0,f02:0,f03:false,f04:false,f05:0.0,f06:0.0,f07:0,f08:0,f09:0,f10:0}", s.toString(b));
	}

	public static class B {
		public int f01;
		public Integer f02;
		public boolean f03;
		public Boolean f04;
		public float f05;
		public Float f06;
		public long f07;
		public Long f08;
		public byte f09;
		public Byte f10;
	}

	//====================================================================================================
	// testInvalidJsonNumbers
	// Lax parser allows octal and hexadecimal numbers.  Strict parser does not.
	//====================================================================================================
	@Test
	public void testInvalidJsonNumbers() throws Exception {
		JsonParser p1 = JsonParser.DEFAULT;
		JsonParser p2 = JsonParser.DEFAULT_STRICT;
		Number r;

		// Lax allows blank strings interpreted as 0, strict does not.
		String s = "\"\"";
		r = p1.parse(s, Number.class);
		assertEquals(0, r.intValue());
		assertTrue(r instanceof Integer);
		try {
			r = p2.parse(s, Number.class);
			fail("Exception expected");
		} catch (ParseException e) {
			assertTrue(e.getMessage().contains("Invalid JSON number"));
		}

		// Either should allow 0 or -0.
		s = "0";
		r = p1.parse(s, Number.class);
		assertEquals(0, r.intValue());
		assertTrue(r instanceof Integer);
		r = p2.parse(s, Number.class);
		assertEquals(0, r.intValue());
		assertTrue(r instanceof Integer);

		s = "-0";
		r = p1.parse(s, Number.class);
		assertEquals(0, r.intValue());
		assertTrue(r instanceof Integer);
		r = p2.parse(s, Number.class);
		assertEquals(0, r.intValue());
		assertTrue(r instanceof Integer);

		// Lax allows 0123 and -0123, strict does not.
		s = "0123";
		r = p1.parse(s, Number.class);
		assertEquals(0123, r.intValue());
		assertTrue(r instanceof Integer);
		try {
			r = p2.parse(s, Number.class);
			fail("Exception expected");
		} catch (ParseException e) {
			assertTrue(e.getMessage().contains("Invalid JSON number"));
		}
		s = "-0123";
		r = p1.parse(s, Number.class);
		assertEquals(-0123, r.intValue());
		assertTrue(r instanceof Integer);
		try {
			r = p2.parse(s, Number.class);
			fail("Exception expected");
		} catch (ParseException e) {
			assertTrue(e.getMessage().contains("Invalid JSON number"));
		}

		// Lax allows 0x123 and -0x123, strict does not.
		s = "0x123";
		r = p1.parse(s, Number.class);
		assertEquals(0x123, r.intValue());
		assertTrue(r instanceof Integer);
		try {
			r = p2.parse(s, Number.class);
			fail("Exception expected");
		} catch (ParseException e) {
			assertTrue(e.getMessage().contains("Invalid JSON number"));
		}
		s = "-0x123";
		r = p1.parse(s, Number.class);
		assertEquals(-0x123, r.intValue());
		assertTrue(r instanceof Integer);
		try {
			r = p2.parse(s, Number.class);
			fail("Exception expected");
		} catch (ParseException e) {
			assertTrue(e.getMessage().contains("Invalid JSON number"));
		}
	}

	//====================================================================================================
	// testUnquotedStrings
	// Lax parser allows unquoted strings if POJO can be converted from a string.
	//====================================================================================================
	@Test
	public void testUnquotedStrings() throws Exception {
		JsonParser p1 = JsonParser.DEFAULT;
		JsonParser p2 = JsonParser.DEFAULT_STRICT;

		String s = "foobar";
		C c = p1.parse(s, C.class);
		assertEquals("f=foobar", c.toString());

		try {
			p2.parse(s, C.class);
			fail("Exception expected");
		} catch (ParseException e) {
			// OK
		}
	}

	public static class C {
		String f;
		public static C valueOf(String s) {
			C c = new C();
			c.f = s;
			return c;
		}
		@Override /* Object */
		public String toString() {
			return "f="+f;
		}
	}

	//====================================================================================================
	// testStreamsAutoClose
	// Validates PARSER_autoCloseStreams.
	//====================================================================================================
	@Test
	public void testStreamsAutoClose() throws Exception {
		ReaderParser p = JsonParser.DEFAULT.builder().autoCloseStreams().build();
		Object x;
		Reader r;

		r = reader("{foo:'bar'}{baz:'qux'}");
		x = p.parse(r, ObjectMap.class);
		assertObjectEquals("{foo:'bar'}", x);
		try {
			x = p.parse(r, ObjectMap.class);
			fail("Exception expected");
		} catch (Exception e) {
			assertTrue(e.getMessage().contains("Reader is closed"));
		}
	}

	//====================================================================================================
	// testMultipleObjectsInStream
	// Validates that readers are not closed so that we can read streams of POJOs.
	//====================================================================================================
	@Test
	public void testMultipleObjectsInStream() throws Exception {
		ReaderParser p = JsonParser.create().unbuffered().build();
		Object x;
		Reader r;

		r = reader("{foo:'bar'}{baz:'qux'}");
		x = p.parse(r, ObjectMap.class);
		assertObjectEquals("{foo:'bar'}", x);
		x = p.parse(r, ObjectMap.class);
		assertObjectEquals("{baz:'qux'}", x);

		r = reader("[123][456]");
		x = p.parse(r, ObjectList.class);
		assertObjectEquals("[123]", x);
		x = p.parse(r, ObjectList.class);
		assertObjectEquals("[456]", x);
	}

	private Reader reader(String in) {
		return new CloseableStringReader(in);
	}
}
