blob: e5f0e9241736162cd2e4201c0be4fd139c924e4b [file] [log] [blame]
package org.codehaus.jackson.smile;
import java.io.*;
import java.util.*;
import org.codehaus.jackson.*;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.sym.BytesToNameCanonicalizer;
/**
* Unit tests for verifying that symbol handling works as planned, including
* efficient reuse of names encountered during parsing.
*/
public class TestSmileParserSymbolHandling
extends SmileTestBase
{
/*
/**********************************************************
/* Helper types, constants
/**********************************************************
*/
private final static String[] SHARED_SYMBOLS = new String[] {
"g", "J", "v", "B", "S", "JAVA",
"h", "J", "LARGE",
"JAVA", "J", "SMALL"
};
static class MediaItem
{
public Content content;
public Image[] images;
}
public enum Size { SMALL, LARGE; }
public enum Player { JAVA, FLASH; }
static class Image
{
public String uri;
public String title;
public int width;
public int height;
public Size size;
public Image() { }
public Image(String uri, String title, int w, int h, Size s)
{
this.uri = uri;
this.title = title;
width = w;
height = h;
size = s;
}
}
static class Content
{
public Player player;
public String uri;
public String title;
public int width;
public int height;
public String format;
public long duration;
public long size;
public int bitrate;
public String[] persons;
public String copyright;
}
/*
/**********************************************************
/* Unit tests
/**********************************************************
*/
public void testSimple() throws IOException
{
final String STR1 = "a";
byte[] data = _smileDoc("{ "+quote(STR1)+":1, \"foobar\":2, \"longername\":3 }");
SmileFactory f = new SmileFactory();
SmileParser p = _smileParser(f, data);
final BytesToNameCanonicalizer symbols1 = p._symbols;
assertEquals(0, symbols1.size());
assertEquals(JsonToken.START_OBJECT, p.nextToken());
assertEquals(JsonToken.FIELD_NAME, p.nextToken());
// field names are interned:
assertSame(STR1, p.getCurrentName());
assertEquals(1, symbols1.size());
assertEquals(JsonToken.VALUE_NUMBER_INT, p.nextToken());
assertEquals(JsonToken.FIELD_NAME, p.nextToken());
assertSame("foobar", p.getCurrentName());
assertEquals(2, symbols1.size());
assertEquals(JsonToken.VALUE_NUMBER_INT, p.nextToken());
assertEquals(JsonToken.FIELD_NAME, p.nextToken());
assertSame("longername", p.getCurrentName());
assertEquals(3, symbols1.size());
assertEquals(JsonToken.VALUE_NUMBER_INT, p.nextToken());
assertEquals(JsonToken.END_OBJECT, p.nextToken());
assertNull(p.nextToken());
assertEquals(3, symbols1.size());
p.close();
// but let's verify that symbol table gets reused properly
p = _smileParser(f, data);
BytesToNameCanonicalizer symbols2 = p._symbols;
// symbol tables are not reused, but contents are:
assertNotSame(symbols1, symbols2);
assertEquals(3, symbols2.size());
assertEquals(JsonToken.START_OBJECT, p.nextToken());
assertEquals(JsonToken.FIELD_NAME, p.nextToken());
// field names are interned:
assertSame(STR1, p.getCurrentName());
assertEquals(3, symbols2.size());
assertEquals(JsonToken.VALUE_NUMBER_INT, p.nextToken());
assertEquals(JsonToken.FIELD_NAME, p.nextToken());
assertSame("foobar", p.getCurrentName());
assertEquals(3, symbols2.size());
assertEquals(JsonToken.VALUE_NUMBER_INT, p.nextToken());
assertEquals(JsonToken.FIELD_NAME, p.nextToken());
assertSame("longername", p.getCurrentName());
assertEquals(3, symbols2.size());
assertEquals(JsonToken.VALUE_NUMBER_INT, p.nextToken());
assertEquals(JsonToken.END_OBJECT, p.nextToken());
assertNull(p.nextToken());
assertEquals(3, symbols2.size());
p.close();
assertEquals(3, symbols2.size());
p.close();
}
public void testSharedNames() throws IOException
{
final int COUNT = 19000;
SmileFactory f = new SmileFactory();
f.configure(SmileGenerator.Feature.WRITE_HEADER, false);
f.configure(SmileGenerator.Feature.CHECK_SHARED_NAMES, true);
ByteArrayOutputStream out = new ByteArrayOutputStream(4000);
JsonGenerator gen = f.createJsonGenerator(out);
gen.writeStartArray();
Random rnd = new Random(COUNT);
for (int i = 0; i < COUNT; ++i) {
gen.writeStartObject();
int nr = rnd.nextInt() % 1200;
gen.writeNumberField("f"+nr, nr);
gen.writeEndObject();
}
gen.writeEndArray();
gen.close();
byte[] json = out.toByteArray();
// And verify
f.configure(SmileParser.Feature.REQUIRE_HEADER, false);
JsonParser jp = f.createJsonParser(json);
assertToken(JsonToken.START_ARRAY, jp.nextToken());
rnd = new Random(COUNT);
for (int i = 0; i < COUNT; ++i) {
assertToken(JsonToken.START_OBJECT, jp.nextToken());
int nr = rnd.nextInt() % 1200;
String name = "f"+nr;
assertToken(JsonToken.FIELD_NAME, jp.nextToken());
assertEquals(name, jp.getCurrentName());
assertToken(JsonToken.VALUE_NUMBER_INT, jp.nextToken());
assertEquals(nr, jp.getIntValue());
assertToken(JsonToken.END_OBJECT, jp.nextToken());
}
assertToken(JsonToken.END_ARRAY, jp.nextToken());
}
public void testSharedStrings() throws IOException
{
final int count = 19000;
byte[] baseline = writeStringValues(false, count);
assertEquals(763589, baseline.length);
verifyStringValues(baseline, count);
// and then shared; should be much smaller
byte[] shared = writeStringValues(true, count);
if (shared.length >= baseline.length) {
fail("Expected shared String length < "+baseline.length+", was "+shared.length);
}
verifyStringValues(shared, count);
}
public void testSharedStringsInArrays() throws IOException
{
SmileFactory f = new SmileFactory();
f.configure(SmileGenerator.Feature.CHECK_SHARED_STRING_VALUES, true);
ByteArrayOutputStream out = new ByteArrayOutputStream(4000);
JsonGenerator gen = f.createJsonGenerator(out);
gen.writeStartArray();
for (String value : SHARED_SYMBOLS) {
gen.writeString(value);
}
gen.writeEndArray();
gen.close();
byte[] smile = out.toByteArray();
JsonParser jp = f.createJsonParser(smile);
assertToken(JsonToken.START_ARRAY, jp.nextToken());
for (String value : SHARED_SYMBOLS) {
assertToken(JsonToken.VALUE_STRING, jp.nextToken());
assertEquals(value, jp.getText());
}
assertToken(JsonToken.END_ARRAY, jp.nextToken());
}
public void testSharedStringsInObject() throws IOException
{
SmileFactory f = new SmileFactory();
f.configure(SmileGenerator.Feature.CHECK_SHARED_STRING_VALUES, true);
ByteArrayOutputStream out = new ByteArrayOutputStream(4000);
JsonGenerator gen = f.createJsonGenerator(out);
gen.writeStartObject();
for (int i = 0; i < SHARED_SYMBOLS.length; ++i) {
gen.writeFieldName("a"+i);
gen.writeString(SHARED_SYMBOLS[i]);
}
gen.writeEndObject();
gen.close();
byte[] smile = out.toByteArray();
JsonParser jp = f.createJsonParser(smile);
assertToken(JsonToken.START_OBJECT, jp.nextToken());
for (int i = 0; i < SHARED_SYMBOLS.length; ++i) {
assertToken(JsonToken.FIELD_NAME, jp.nextToken());
assertEquals("a"+i, jp.getCurrentName());
assertToken(JsonToken.VALUE_STRING, jp.nextToken());
assertEquals(SHARED_SYMBOLS[i], jp.getText());
}
assertToken(JsonToken.END_OBJECT, jp.nextToken());
}
public void testSharedStringsMixed() throws IOException
{
SmileFactory f = new SmileFactory();
f.configure(SmileGenerator.Feature.CHECK_SHARED_STRING_VALUES, true);
ByteArrayOutputStream out = new ByteArrayOutputStream(4000);
JsonGenerator gen = f.createJsonGenerator(out);
gen.writeStartObject();
gen.writeFieldName("media");
gen.writeStartObject();
gen.writeStringField("uri", "g");
gen.writeStringField("title", "J");
gen.writeNumberField("width", 640);
gen.writeStringField("format", "v");
gen.writeFieldName("persons");
gen.writeStartArray();
gen.writeString("B");
gen.writeString("S");
gen.writeEndArray();
gen.writeStringField("player", "JAVA");
gen.writeStringField("copyright", "NONE");
gen.writeEndObject(); // media
gen.writeFieldName("images");
gen.writeStartArray();
// 3 instances of identical entries
for (int i = 0; i < 3; ++i) {
gen.writeStartObject();
gen.writeStringField("uri", "h");
gen.writeStringField("title", "J");
gen.writeNumberField("width", 1024);
gen.writeNumberField("height", 768);
gen.writeEndObject();
}
gen.writeEndArray();
gen.writeEndObject();
gen.close();
byte[] smile = out.toByteArray();
JsonParser jp = f.createJsonParser(smile);
assertToken(JsonToken.START_OBJECT, jp.nextToken());
assertToken(JsonToken.FIELD_NAME, jp.nextToken());
assertEquals("media", jp.getCurrentName());
assertToken(JsonToken.START_OBJECT, jp.nextToken());
assertToken(JsonToken.FIELD_NAME, jp.nextToken());
assertEquals("uri", jp.getCurrentName());
assertToken(JsonToken.VALUE_STRING, jp.nextToken());
assertEquals("g", jp.getText());
assertToken(JsonToken.FIELD_NAME, jp.nextToken());
assertEquals("title", jp.getCurrentName());
assertToken(JsonToken.VALUE_STRING, jp.nextToken());
assertEquals("J", jp.getText());
assertToken(JsonToken.FIELD_NAME, jp.nextToken());
assertEquals("width", jp.getCurrentName());
assertToken(JsonToken.VALUE_NUMBER_INT, jp.nextToken());
assertEquals(640, jp.getIntValue());
assertToken(JsonToken.FIELD_NAME, jp.nextToken());
assertEquals("format", jp.getCurrentName());
assertToken(JsonToken.VALUE_STRING, jp.nextToken());
assertEquals("v", jp.getText());
assertToken(JsonToken.FIELD_NAME, jp.nextToken());
assertEquals("persons", jp.getCurrentName());
assertToken(JsonToken.START_ARRAY, jp.nextToken());
assertToken(JsonToken.VALUE_STRING, jp.nextToken());
assertEquals("B", jp.getText());
assertToken(JsonToken.VALUE_STRING, jp.nextToken());
assertEquals("S", jp.getText());
assertToken(JsonToken.END_ARRAY, jp.nextToken());
assertToken(JsonToken.FIELD_NAME, jp.nextToken());
assertEquals("player", jp.getCurrentName());
assertToken(JsonToken.VALUE_STRING, jp.nextToken());
assertEquals("JAVA", jp.getText());
assertToken(JsonToken.FIELD_NAME, jp.nextToken());
assertEquals("copyright", jp.getCurrentName());
assertToken(JsonToken.VALUE_STRING, jp.nextToken());
assertEquals("NONE", jp.getText());
assertToken(JsonToken.END_OBJECT, jp.nextToken()); // media
assertToken(JsonToken.FIELD_NAME, jp.nextToken());
assertEquals("images", jp.getCurrentName());
assertToken(JsonToken.START_ARRAY, jp.nextToken());
// 3 instances of identical entries:
for (int i = 0; i < 3; ++i) {
assertToken(JsonToken.START_OBJECT, jp.nextToken());
assertToken(JsonToken.FIELD_NAME, jp.nextToken());
assertEquals("uri", jp.getCurrentName());
assertToken(JsonToken.VALUE_STRING, jp.nextToken());
assertEquals("h", jp.getText());
assertToken(JsonToken.FIELD_NAME, jp.nextToken());
assertEquals("title", jp.getCurrentName());
assertToken(JsonToken.VALUE_STRING, jp.nextToken());
assertEquals("J", jp.getText());
assertToken(JsonToken.FIELD_NAME, jp.nextToken());
assertEquals("width", jp.getCurrentName());
assertToken(JsonToken.VALUE_NUMBER_INT, jp.nextToken());
assertEquals(1024, jp.getIntValue());
assertToken(JsonToken.FIELD_NAME, jp.nextToken());
assertEquals("height", jp.getCurrentName());
assertToken(JsonToken.VALUE_NUMBER_INT, jp.nextToken());
assertEquals(768, jp.getIntValue());
assertToken(JsonToken.END_OBJECT, jp.nextToken());
}
assertToken(JsonToken.END_ARRAY, jp.nextToken()); // images
assertToken(JsonToken.END_OBJECT, jp.nextToken());
}
public void testDataBindingAndShared() throws IOException
{
SmileFactory f = new SmileFactory();
f.configure(SmileGenerator.Feature.CHECK_SHARED_STRING_VALUES, true);
MediaItem item = new MediaItem();
Content c = new Content();
c.uri = "g";
c.title = "J";
c.width = 640;
c.height = 480;
c.format = "v";
c.duration = 18000000L;
c.size = 58982400L;
c.bitrate = 262144;
c.persons = new String[] { "B", "S" };
c.player = Player.JAVA;
c.copyright = "NONE";
item.content = c;
item.images = new Image[] {
new Image("h", "J", 1024, 768, Size.LARGE),
new Image("h", "J", 320, 240, Size.LARGE)
};
// Ok: let's just do quick comparison (yes/no)...
ObjectMapper plain = new ObjectMapper();
ObjectMapper smiley = new ObjectMapper(f);
String exp = plain.writeValueAsString(item);
byte[] smile = smiley.writeValueAsBytes(item);
MediaItem result = smiley.readValue(smile, 0, smile.length, MediaItem.class);
String actual = plain.writeValueAsString(result);
assertEquals(exp, actual);
}
/**
* Reproducing [JACKSON-561] (and [JACKSON-562])
*/
public void testIssue562() throws IOException
{
JsonFactory factory = new SmileFactory();
ByteArrayOutputStream bos = new ByteArrayOutputStream();
JsonGenerator gen = factory.createJsonGenerator(bos);
gen.writeStartObject();
gen.writeFieldName("z_aaaabbbbccccddddee");
gen.writeString("end");
gen.writeFieldName("a_aaaabbbbccccddddee");
gen.writeString("start");
gen.writeEndObject();
gen.close();
JsonParser parser = factory.createJsonParser(bos.toByteArray());
assertToken(JsonToken.START_OBJECT, parser.nextToken());
assertToken(JsonToken.FIELD_NAME, parser.nextToken());
assertEquals("z_aaaabbbbccccddddee", parser.getCurrentName());
assertToken(JsonToken.VALUE_STRING, parser.nextToken());
assertEquals("end", parser.getText());
// This one fails...
assertToken(JsonToken.FIELD_NAME, parser.nextToken());
assertEquals("a_aaaabbbbccccddddee", parser.getCurrentName());
assertToken(JsonToken.VALUE_STRING, parser.nextToken());
assertEquals("start", parser.getText());
assertToken(JsonToken.END_OBJECT, parser.nextToken());
}
/**
* Verification that [JACKSON-564] was fixed.
*/
public void testIssue564() throws Exception
{
JsonFactory factory = new SmileFactory();
ByteArrayOutputStream bos1 = new ByteArrayOutputStream();
JsonGenerator generator = factory.createJsonGenerator(bos1);
generator.writeStartObject();
generator.writeFieldName("query");
generator.writeStartObject();
generator.writeFieldName("term");
generator.writeStartObject();
generator.writeStringField("doc.payload.test_record_main.string_not_analyzed__s", "foo");
generator.writeEndObject();
generator.writeEndObject();
generator.writeEndObject();
generator.close();
JsonParser parser = factory.createJsonParser(bos1.toByteArray());
JsonToken token = parser.nextToken();
assertToken(JsonToken.START_OBJECT, token);
token = parser.nextToken();
assertToken(JsonToken.FIELD_NAME, token);
assertEquals("query", parser.getCurrentName());
token = parser.nextToken();
assertToken(JsonToken.START_OBJECT, token);
token = parser.nextToken();
assertToken(JsonToken.FIELD_NAME, token);
assertEquals("term", parser.getCurrentName());
token = parser.nextToken();
assertToken(JsonToken.START_OBJECT, token);
token = parser.nextToken();
assertToken(JsonToken.FIELD_NAME, token);
assertEquals("doc.payload.test_record_main.string_not_analyzed__s", parser.getCurrentName());
token = parser.nextToken();
assertToken(JsonToken.VALUE_STRING, token);
assertEquals("foo", parser.getText());
parser.close();
ByteArrayOutputStream bos2 = new ByteArrayOutputStream();
generator = factory.createJsonGenerator(bos2);
generator.writeStartObject();
generator.writeFieldName("query");
generator.writeStartObject();
generator.writeFieldName("term");
generator.writeStartObject();
// note the difference here, teh field is analyzed2 and not analyzed as in the first doc, as well
// as having a different value, though don't think it matters
generator.writeStringField("doc.payload.test_record_main.string_not_analyzed2__s", "bar");
generator.writeEndObject();
generator.writeEndObject();
generator.writeEndObject();
generator.close();
parser = factory.createJsonParser(bos2.toByteArray());
token = parser.nextToken();
assertToken(JsonToken.START_OBJECT, token);
token = parser.nextToken();
assertToken(JsonToken.FIELD_NAME, token);
assertEquals("query", parser.getCurrentName());
token = parser.nextToken();
assertToken(JsonToken.START_OBJECT, token);
token = parser.nextToken();
assertToken(JsonToken.FIELD_NAME, token);
assertEquals("term", parser.getCurrentName());
token = parser.nextToken();
assertToken(JsonToken.START_OBJECT, token);
token = parser.nextToken();
assertToken(JsonToken.FIELD_NAME, token);
// here we fail..., seems to be a problem with field caching factory level???
// since we get the field name of the previous (bos1) document field value (withou the 2)
assertEquals("doc.payload.test_record_main.string_not_analyzed2__s", parser.getCurrentName());
token = parser.nextToken();
assertToken(JsonToken.VALUE_STRING, token);
assertEquals("bar", parser.getText());
parser.close();
}
/*
/**********************************************************
/* Helper methods
/**********************************************************
*/
private final String CHARS_40 = "0123456789012345678901234567890123456789";
private byte[] writeStringValues(boolean enableSharing, int COUNT) throws IOException
{
SmileFactory f = new SmileFactory();
f.configure(SmileGenerator.Feature.WRITE_HEADER, true);
f.configure(SmileGenerator.Feature.CHECK_SHARED_STRING_VALUES, enableSharing);
ByteArrayOutputStream out = new ByteArrayOutputStream(4000);
JsonGenerator gen = f.createJsonGenerator(out);
gen.writeStartArray();
Random rnd = new Random(COUNT);
for (int i = 0; i < COUNT; ++i) {
gen.writeString(generateString(rnd.nextInt()));
}
gen.writeEndArray();
gen.close();
return out.toByteArray();
}
private void verifyStringValues(byte[] json, int COUNT) throws IOException
{
SmileFactory f = new SmileFactory();
JsonParser jp = f.createJsonParser(json);
assertToken(JsonToken.START_ARRAY, jp.nextToken());
Random rnd = new Random(COUNT);
for (int i = 0; i < COUNT; ++i) {
String str = generateString(rnd.nextInt());
assertToken(JsonToken.VALUE_STRING, jp.nextToken());
assertEquals(str, jp.getText());
}
assertToken(JsonToken.END_ARRAY, jp.nextToken());
}
private String generateString(int rawNr)
{
int nr = rawNr % 1100;
// Actually, let's try longer ones too
String str = "some kind of String value we use"+nr;
if (nr > 900) {
str += CHARS_40;
}
return str;
}
}