/*
* Copyright 2011-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License"). You may not
* use this file except in compliance with the License. A copy of the License is
* located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file 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 com.amazonaws.protocol.json;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import com.amazonaws.util.StringUtils;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.Parameterized.Parameters;
import com.amazonaws.AmazonClientException;
import com.fasterxml.jackson.core.JsonToken;
import software.amazon.ion.IonReader;
import software.amazon.ion.IonSystem;
import software.amazon.ion.IonWriter;
import software.amazon.ion.system.IonBinaryWriterBuilder;
import software.amazon.ion.system.IonSystemBuilder;
import software.amazon.ion.system.IonTextWriterBuilder;
/**
* Tests the {@link IonParser} for conformity with the {@link JsonParser} API.
* Also tests that the IonParser correctly converts Ion-only value types to
* the correct {@link JsonToken}s. For testing of additional value types and
* roundtrip testing with the {@link SdkIonGenerator}, see {@link IonRoundtripTest}.
*/
@RunWith(Parameterized.class)
public class IonParserTest {
private enum WriteFormat {
TEXT {
@Override
public byte[] write(String data) throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
write(data, IonTextWriterBuilder.standard().build(out));
return out.toByteArray();
}
},
BINARY {
@Override
public byte[] write(String data) throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
write(data, IonBinaryWriterBuilder.standard().build(out));
return out.toByteArray();
}
};
public static void write(String data, IonWriter writer) throws IOException {
IonReader reader = SYSTEM.newReader(data);
writer.writeValues(reader);
writer.close();
}
public abstract byte[] write(String data) throws IOException;
}
@Parameters
public static Collection<Object[]> data() {
List<Object[]> parameters = new ArrayList<Object[]>();
for (WriteFormat format : WriteFormat.values()) {
parameters.add(new Object[]{ format });
}
return parameters;
}
private static IonSystem SYSTEM = IonSystemBuilder.standard().build();
private WriteFormat format;
public IonParserTest(WriteFormat format) {
this.format = format;
}
private IonParser parse(String data) throws IOException {
byte[] ion = format.write(data);
return new IonParser(SYSTEM.newReader(ion), false);
}
@Test
public void testEmptySexp() throws IOException {
IonParser parser = parse("()");
assertEquals(JsonToken.START_ARRAY, parser.nextToken());
assertEquals(JsonToken.END_ARRAY, parser.nextToken());
assertNull(parser.nextToken());
}
@Test
public void testSexp() throws IOException {
IonParser parser = parse("(a+b)");
assertEquals(JsonToken.START_ARRAY, parser.nextToken());
assertEquals(JsonToken.VALUE_STRING, parser.nextToken());
assertEquals("a", parser.getText());
assertEquals(JsonToken.VALUE_STRING, parser.nextToken());
assertEquals("+", parser.getText());
assertEquals(JsonToken.VALUE_STRING, parser.nextToken());
assertEquals("b", parser.getText());
assertEquals(JsonToken.END_ARRAY, parser.nextToken());
assertNull(parser.nextToken());
}
@Test
public void testNestedSexp() throws IOException {
IonParser parser = parse("((a)+(b))");
assertEquals(JsonToken.START_ARRAY, parser.nextToken());
assertEquals(JsonToken.START_ARRAY, parser.nextToken());
assertEquals(JsonToken.VALUE_STRING, parser.nextToken());
assertEquals("a", parser.getText());
assertEquals(JsonToken.END_ARRAY, parser.nextToken());
assertEquals(JsonToken.VALUE_STRING, parser.nextToken());
assertEquals("+", parser.getText());
assertEquals(JsonToken.START_ARRAY, parser.nextToken());
assertEquals(JsonToken.VALUE_STRING, parser.nextToken());
assertEquals("b", parser.getText());
assertEquals(JsonToken.END_ARRAY, parser.nextToken());
assertEquals(JsonToken.END_ARRAY, parser.nextToken());
assertNull(parser.nextToken());
}
@Test
public void testSexpSkip() throws IOException {
IonParser parser = parse("(a+b)");
assertEquals(JsonToken.START_ARRAY, parser.nextToken());
parser.skipChildren();
assertEquals(JsonToken.END_ARRAY, parser.getCurrentToken());
assertNull(parser.nextToken());
}
@Test
public void testNestedSexpSkip() throws IOException {
IonParser parser = parse("((a)+(b))");
assertEquals(JsonToken.START_ARRAY, parser.nextToken());
assertEquals(JsonToken.START_ARRAY, parser.nextToken());
parser.skipChildren();
assertEquals(JsonToken.END_ARRAY, parser.getCurrentToken());
assertEquals(JsonToken.VALUE_STRING, parser.nextToken());
assertEquals("+", parser.getText());
assertEquals(JsonToken.START_ARRAY, parser.nextToken());
parser.skipChildren();
assertEquals(JsonToken.END_ARRAY, parser.getCurrentToken());
assertEquals(JsonToken.END_ARRAY, parser.nextToken());
assertNull(parser.nextToken());
}
@Test
public void testEmptyClob() throws IOException {
IonParser parser = parse("{{}}");
assertEquals(JsonToken.VALUE_EMBEDDED_OBJECT, parser.nextToken());
assertEquals(ByteBuffer.wrap(new byte[0]), parser.getEmbeddedObject());
assertNull(parser.nextToken());
}
@Test
public void testClob() throws IOException {
IonParser parser = parse("{{\"abc123\"}}");
assertEquals(JsonToken.VALUE_EMBEDDED_OBJECT, parser.nextToken());
assertEquals(ByteBuffer.wrap("abc123".getBytes(StringUtils.UTF8)), parser.getEmbeddedObject());
assertNull(parser.nextToken());
}
@Test
public void testSymbolValue() throws IOException {
IonParser parser = parse("a1 _1 $foo '123' 'sp ace'");
assertEquals(JsonToken.VALUE_STRING, parser.nextToken());
assertEquals("a1", parser.getText());
assertEquals(JsonToken.VALUE_STRING, parser.nextToken());
assertEquals("_1", parser.getText());
assertEquals(JsonToken.VALUE_STRING, parser.nextToken());
assertEquals("$foo", parser.getText());
assertEquals(JsonToken.VALUE_STRING, parser.nextToken());
assertEquals("123", parser.getText());
assertEquals(JsonToken.VALUE_STRING, parser.nextToken());
assertEquals("sp ace", parser.getText());
assertNull(parser.nextToken());
}
@Test
public void testSkipChildrenNotAtContainerStartDoesNothing() throws IOException {
IonParser parser = parse("123 (a+b)");
assertEquals(JsonToken.VALUE_NUMBER_INT, parser.nextToken());
parser.skipChildren(); // should do nothing
assertEquals(JsonToken.VALUE_NUMBER_INT, parser.getCurrentToken());
assertEquals(123, parser.getIntValue());
assertEquals(JsonToken.START_ARRAY, parser.nextToken());
assertEquals(JsonToken.VALUE_STRING, parser.nextToken());
parser.skipChildren(); // should do nothing
assertEquals(JsonToken.VALUE_STRING, parser.getCurrentToken());
assertEquals("a", parser.getText());
}
@Test
public void testGetEmbeddedObjectOnBasicValueReturnsNull() throws IOException {
IonParser parser = parse("123 (a+b) abc");
assertEquals(JsonToken.VALUE_NUMBER_INT, parser.nextToken());
assertNull(parser.getEmbeddedObject());
assertEquals(123, parser.getIntValue());
assertEquals(JsonToken.START_ARRAY, parser.nextToken());
assertNull(parser.getEmbeddedObject());
parser.skipChildren();
assertEquals(JsonToken.VALUE_STRING, parser.nextToken());
assertNull(parser.getEmbeddedObject());
assertEquals("abc", parser.getText());
assertNull(parser.nextToken());
}
@Test
public void testNulls() throws IOException {
IonParser parser = parse( "null "
+ "null.null "
+ "null.bool "
+ "null.int "
+ "null.float "
+ "null.decimal "
+ "null.timestamp "
+ "null.string "
+ "null.symbol "
+ "null.blob "
+ "null.clob "
+ "null.struct "
+ "null.list "
+ "null.sexp"
);
JsonToken token = null;
int count = 0;
while ((token = parser.nextToken()) != null) {
assertEquals(JsonToken.VALUE_NULL, token);
count++;
}
assertEquals(14, count);
}
@Test
public void testNextValue() throws IOException {
IonParser parser = parse("{foo:{bar:\"abc\"}, baz:123} 42.0");
assertEquals(JsonToken.START_OBJECT, parser.nextValue());
assertEquals(JsonToken.START_OBJECT, parser.nextValue());
assertEquals("foo", parser.getCurrentName());
assertEquals(JsonToken.VALUE_STRING, parser.nextValue());
assertEquals("abc", parser.getText());
assertEquals("bar", parser.getCurrentName());
assertEquals(JsonToken.END_OBJECT, parser.nextValue());
assertEquals(JsonToken.VALUE_NUMBER_INT, parser.nextValue());
assertEquals(123, parser.getIntValue());
assertEquals("baz", parser.getCurrentName());
assertEquals(JsonToken.END_OBJECT, parser.nextValue());
assertEquals(JsonToken.VALUE_NUMBER_FLOAT, parser.nextValue());
assertEquals(42.0, parser.getFloatValue(), 1e-9);
assertNull(parser.nextValue());
}
@Test
public void testGetCurrentNameNotAtFieldReturnsNull() throws IOException {
IonParser parser = parse("{foo:\"abc\"} [a, b] {{}} \"bar\"");
assertEquals(JsonToken.START_OBJECT, parser.nextToken());
assertNull(parser.getCurrentName());
assertEquals(JsonToken.VALUE_STRING, parser.nextValue());
assertEquals(JsonToken.END_OBJECT, parser.nextToken());
assertNull(parser.getCurrentName());
assertEquals(JsonToken.START_ARRAY, parser.nextToken());
assertEquals(JsonToken.VALUE_STRING, parser.nextToken());
assertNull(parser.getCurrentName());
assertEquals(JsonToken.VALUE_STRING, parser.nextToken());
assertNull(parser.getCurrentName());
assertEquals(JsonToken.END_ARRAY, parser.nextToken());
assertNull(parser.getCurrentName());
assertEquals(JsonToken.VALUE_EMBEDDED_OBJECT, parser.nextToken());
assertNull(parser.getCurrentName());
assertEquals(JsonToken.VALUE_STRING, parser.nextToken());
assertNull(parser.getCurrentName());
assertNull(parser.nextToken());
assertNull(parser.getCurrentName());
}
@Test
public void testClearCurrentToken() throws IOException {
IonParser parser = parse("{}");
assertEquals(JsonToken.START_OBJECT, parser.nextToken());
parser.clearCurrentToken();
assertNull(parser.getCurrentToken());
assertFalse(parser.hasCurrentToken());
assertEquals(JsonToken.START_OBJECT, parser.getLastClearedToken());
}
@Test
public void testGetText() throws IOException {
String defaultText = "default";
String integer = String.valueOf(123);
String flt = String.valueOf(42.0);
IonParser parser = parse("{foo:" + integer + ", bar:" + flt + "} {{\"abc\"}} null true false");
assertNull(parser.getText());
assertEquals(defaultText, parser.getValueAsString(defaultText));
assertEquals(JsonToken.START_OBJECT, parser.nextToken());
assertEquals(JsonToken.START_OBJECT.asString(), parser.getText()); // "{"
assertEquals(defaultText, parser.getValueAsString(defaultText));
assertEquals(JsonToken.FIELD_NAME, parser.nextToken());
assertEquals("foo", parser.getText());
assertEquals(defaultText, parser.getValueAsString(defaultText));
assertEquals("foo", parser.getCurrentName());
assertEquals(JsonToken.VALUE_NUMBER_INT, parser.nextToken());
assertEquals(integer, parser.getText());
assertEquals(integer, parser.getValueAsString(defaultText));
assertEquals(123, parser.getIntValue());
assertEquals(JsonToken.VALUE_NUMBER_FLOAT, parser.nextValue());
assertEquals(flt, parser.getText());
assertEquals(flt, parser.getValueAsString(defaultText));
assertEquals(42.0, parser.getFloatValue(), 1e-9);
assertEquals("bar", parser.getCurrentName());
assertEquals(JsonToken.END_OBJECT, parser.nextToken());
assertEquals(JsonToken.END_OBJECT.asString(), parser.getText()); // "}"
assertEquals(defaultText, parser.getValueAsString(defaultText));
assertEquals(JsonToken.VALUE_EMBEDDED_OBJECT, parser.nextToken());
assertNull(parser.getText()); // embedded objects have undefined text
assertEquals(JsonToken.VALUE_NULL, parser.nextToken());
assertEquals(JsonToken.VALUE_NULL.asString(), parser.getText()); // "null"
assertEquals(defaultText, parser.getValueAsString(defaultText));
assertEquals(JsonToken.VALUE_TRUE, parser.nextToken());
assertEquals(JsonToken.VALUE_TRUE.asString(), parser.getText()); // "true"
assertEquals(JsonToken.VALUE_TRUE.asString(), parser.getValueAsString(defaultText));
assertEquals(JsonToken.VALUE_FALSE, parser.nextToken());
assertEquals(JsonToken.VALUE_FALSE.asString(), parser.getText()); // "false"
assertEquals(JsonToken.VALUE_FALSE.asString(), parser.getValueAsString(defaultText));
assertNull(parser.nextToken());
assertNull(parser.getText());
assertEquals(defaultText, parser.getValueAsString(defaultText));
}
@Test
public void testGetNumberValue() throws IOException {
String integer = String.valueOf(Integer.MAX_VALUE);
String lng = String.valueOf(Long.MAX_VALUE);
String bigInteger = BigInteger.valueOf(Long.MAX_VALUE).add(BigInteger.ONE).toString();
String flt = String.valueOf(Float.MAX_VALUE);
String dbl = String.valueOf(Double.MAX_VALUE);
String inf = "1.7976931348623157E309"; // Double.MAX_VALUE * 10;
String bigDecimal = new BigDecimal(inf).toString();
IonParser parser = parse( integer + " "
+ lng + " "
+ bigInteger + " "
+ flt + " "
+ dbl + " "
+ inf + " "
+ bigDecimal.toLowerCase().replace("e", "D")
);
assertEquals(JsonToken.VALUE_NUMBER_INT, parser.nextToken());
assertEquals(integer, parser.getNumberValue().toString());
assertEquals(JsonToken.VALUE_NUMBER_INT, parser.nextToken());
assertEquals(lng, parser.getNumberValue().toString());
assertEquals(JsonToken.VALUE_NUMBER_INT, parser.nextToken());
assertEquals(bigInteger, parser.getNumberValue().toString());
assertEquals(JsonToken.VALUE_NUMBER_FLOAT, parser.nextToken());
assertEquals(flt, parser.getNumberValue().toString());
assertEquals(JsonToken.VALUE_NUMBER_FLOAT, parser.nextToken());
assertEquals(dbl, parser.getNumberValue().toString());
assertEquals(JsonToken.VALUE_NUMBER_FLOAT, parser.nextToken());
assertTrue(Double.isInfinite(parser.getDoubleValue()));
assertTrue(Double.isInfinite(parser.getFloatValue()));
assertEquals(JsonToken.VALUE_NUMBER_FLOAT, parser.nextToken());
assertEquals(bigDecimal, parser.getNumberValue().toString());
}
@Rule
public ExpectedException thrown = ExpectedException.none();
@Test
public void testGetNumberValueNotOnNumberFails() throws IOException {
IonParser parser = parse("foo {{}} {abc:123}");
assertEquals(JsonToken.VALUE_STRING, parser.nextToken());
assertNull(parser.getNumberType());
thrown.expect(AmazonClientException.class);
parser.getNumberValue();
}
@Test
public void testSpecialFloatValues() throws IOException {
IonParser parser = parse( "1.7976931348623157E309 " // Double.MAX_VALUE * 10
+ "-1.7976931348623157E309 "
+ "+inf "
+ "-inf "
+ "nan"
);
assertEquals(JsonToken.VALUE_NUMBER_FLOAT, parser.nextToken());
assertTrue(Double.isInfinite(parser.getDoubleValue()));
assertTrue(Double.isInfinite(parser.getFloatValue()));
assertEquals(JsonToken.VALUE_NUMBER_FLOAT, parser.nextToken());
assertTrue(Double.isInfinite(parser.getDoubleValue()));
assertTrue(Double.isInfinite(parser.getFloatValue()));
assertEquals(JsonToken.VALUE_NUMBER_FLOAT, parser.nextToken());
assertTrue(Double.isInfinite(parser.getDoubleValue()));
assertTrue(Double.isInfinite(parser.getFloatValue()));
assertEquals(JsonToken.VALUE_NUMBER_FLOAT, parser.nextToken());
assertTrue(Double.isInfinite(parser.getDoubleValue()));
assertTrue(Double.isInfinite(parser.getFloatValue()));
assertEquals(JsonToken.VALUE_NUMBER_FLOAT, parser.nextToken());
assertTrue(Double.isNaN(parser.getDoubleValue()));
assertTrue(Double.isNaN(parser.getFloatValue()));
}
}