Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add support for generating IonSexps (#242)
Add writeStartSexp and writeEndSexp methods to the IonGenerator. This requires extending the JsonWriteContext into a new class that recognizes the sexp context.
- Loading branch information
Showing
4 changed files
with
298 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
103 changes: 103 additions & 0 deletions
103
ion/src/main/java/com/fasterxml/jackson/dataformat/ion/IonWriteContext.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
/* | ||
* Copyright 2012-2016 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.fasterxml.jackson.dataformat.ion; | ||
|
||
import com.fasterxml.jackson.core.json.DupDetector; | ||
import com.fasterxml.jackson.core.json.JsonWriteContext; | ||
|
||
/** | ||
* Extension of JsonWriteContexts that recognizes sexps | ||
* <p> | ||
* The JsonWriteContext is used by the pretty printer for handling of the whitespace between tokens, | ||
* and by the generator for verifying whether it's valid to write a given token. The writeStartSexp | ||
* method in the IonGenerator will enter a "sexp context", so we need a new state in the write | ||
* context to track that. Sexp handling is modeled after arrays. | ||
*/ | ||
public class IonWriteContext extends JsonWriteContext { | ||
// Both contstants are in the tens instead of the ones to avoid conflict with the native | ||
// Jackson ones | ||
|
||
// Ion-specific contexts | ||
protected final static int TYPE_SEXP = 30; | ||
|
||
// Ion-specific statuses | ||
public final static int STATUS_OK_AFTER_SEXP_SEPARATOR = 60; | ||
|
||
protected IonWriteContext(int type, IonWriteContext parent, DupDetector dups) { | ||
super(type, parent, dups); | ||
} | ||
|
||
public static IonWriteContext createRootContext(DupDetector dd) { | ||
return new IonWriteContext(TYPE_ROOT, null, dd); | ||
} | ||
|
||
public IonWriteContext createChildSexpContext() { | ||
IonWriteContext ctxt = (IonWriteContext) _child; | ||
|
||
if(ctxt == null) { | ||
// same assignment as in createChildObjectContext, createChildArrayContext | ||
_child = ctxt = new IonWriteContext(TYPE_SEXP, this, (_dups == null) ? null : _dups.child()); | ||
} | ||
|
||
// reset returns this, OK to cast | ||
return (IonWriteContext) ctxt.reset(TYPE_SEXP); | ||
} | ||
|
||
public final boolean inSexp() { | ||
return _type == TYPE_SEXP; | ||
} | ||
|
||
// // Overrides | ||
|
||
// We have to override the two createChild*Context methods to return a IonWriteContext | ||
// instead of a JsonWriteContext so sexps can be arbitrarily embedded in ion. Otherwise we | ||
// would only be able to create them as top level values. | ||
// Two methods below are copied from JsonWriteContext | ||
|
||
@Override | ||
public IonWriteContext createChildArrayContext() { | ||
IonWriteContext ctxt = (IonWriteContext) _child; | ||
|
||
if (ctxt == null) { | ||
_child = ctxt = new IonWriteContext(TYPE_ARRAY, this, (_dups == null) ? null : _dups.child()); | ||
return ctxt; | ||
} | ||
|
||
return (IonWriteContext) ctxt.reset(TYPE_ARRAY); | ||
} | ||
|
||
@Override | ||
public IonWriteContext createChildObjectContext() { | ||
IonWriteContext ctxt = (IonWriteContext) _child; | ||
|
||
if (ctxt == null) { | ||
_child = ctxt = new IonWriteContext(TYPE_OBJECT, this, (_dups == null) ? null : _dups.child()); | ||
return ctxt; | ||
} | ||
return (IonWriteContext) ctxt.reset(TYPE_OBJECT); | ||
} | ||
|
||
@Override | ||
public int writeValue() { | ||
// Add special handling for sexp separator | ||
if(_type == TYPE_SEXP) { | ||
int ix = _index; | ||
++_index; | ||
return (ix < 0) ? STATUS_OK_AS_IS : STATUS_OK_AFTER_SEXP_SEPARATOR; | ||
} | ||
|
||
return super.writeValue(); | ||
} | ||
} |
151 changes: 151 additions & 0 deletions
151
ion/src/test/java/com/fasterxml/jackson/dataformat/ion/GenerateSexpTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,151 @@ | ||
/* | ||
* Copyright 2012-2016 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.fasterxml.jackson.dataformat.ion; | ||
|
||
import com.amazon.ion.IonSexp; | ||
import com.amazon.ion.IonSystem; | ||
import com.amazon.ion.IonWriter; | ||
import com.amazon.ion.system.IonSystemBuilder; | ||
import com.fasterxml.jackson.core.JsonGenerator; | ||
import com.fasterxml.jackson.databind.JsonSerializer; | ||
import com.fasterxml.jackson.databind.SerializerProvider; | ||
import com.fasterxml.jackson.databind.annotation.JsonSerialize; | ||
import java.io.ByteArrayOutputStream; | ||
import java.io.IOException; | ||
import java.util.Arrays; | ||
import java.util.List; | ||
import org.junit.Assert; | ||
import org.junit.Before; | ||
import org.junit.Test; | ||
|
||
/** | ||
* End to end test verifying we can serialize sexps | ||
*/ | ||
public class GenerateSexpTest { | ||
|
||
private IonSystem ionSystem; | ||
private IonObjectMapper mapper; | ||
|
||
@Before | ||
public void setup() { | ||
this.ionSystem = IonSystemBuilder.standard().build(); | ||
this.mapper = new IonObjectMapper(new IonFactory(null, ionSystem)); | ||
} | ||
|
||
@Test | ||
public void topLevel() throws IOException { | ||
Assert.assertEquals( | ||
ionSystem.singleValue("(foo \"bar\")"), | ||
mapper.writeValueAsIonValue(new SexpObject("foo", "bar"))); | ||
} | ||
|
||
@Test | ||
public void inList() throws IOException { | ||
Assert.assertEquals( | ||
ionSystem.singleValue("[(foo \"bar\"), (baz \"qux\")]"), | ||
mapper.writeValueAsIonValue( | ||
Arrays.asList(new SexpObject("foo", "bar"), new SexpObject("baz", "qux")))); | ||
} | ||
|
||
@Test | ||
public void inObject() throws IOException { | ||
Assert.assertEquals( | ||
ionSystem.singleValue("{sexpField:(foo \"bar\")}"), | ||
mapper.writeValueAsIonValue(new SexpObjectContainer(new SexpObject("foo", "bar")))); | ||
} | ||
|
||
@Test | ||
public void inOtherSexp() throws IOException { | ||
Assert.assertEquals( | ||
ionSystem.singleValue("(foo (bar \"baz\"))"), | ||
mapper.writeValueAsIonValue(new SexpObject("foo", new SexpObject("bar", "baz")))); | ||
} | ||
|
||
@Test | ||
public void generatorUsedInStreamingWriteText() throws IOException { | ||
Assert.assertArrayEquals("(foo 0)".getBytes(), toBytes(new SexpObject("foo", 0), mapper)); | ||
} | ||
|
||
@Test | ||
public void generatorUsedInStreamingWriteBinary() throws IOException { | ||
byte[] expectedBytes = null; | ||
|
||
try (ByteArrayOutputStream baos = new ByteArrayOutputStream(); | ||
IonWriter writer = ionSystem.newBinaryWriter(baos)) { | ||
ionSystem.singleValue("(foo 0)").writeTo(writer); | ||
writer.finish(); | ||
expectedBytes = baos.toByteArray(); | ||
} | ||
|
||
mapper.setCreateBinaryWriters(true); | ||
Assert.assertArrayEquals(expectedBytes, toBytes(new SexpObject("foo", 0), mapper)); | ||
} | ||
|
||
private byte[] toBytes(Object object, IonObjectMapper mapper) throws IOException { | ||
byte[] bytes = null; | ||
|
||
try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) { | ||
mapper.writeValue(baos, object); | ||
bytes = baos.toByteArray(); | ||
} | ||
|
||
return bytes; | ||
} | ||
|
||
private static class SexpObjectContainer { | ||
private SexpObject sexpField; | ||
|
||
SexpObjectContainer(SexpObject sexpField) { | ||
this.sexpField = sexpField; | ||
} | ||
|
||
public SexpObject getSexpField() { | ||
return sexpField; | ||
} | ||
} | ||
|
||
// Create some pojo that defines a custom serializer that creates an IonSexp | ||
@JsonSerialize(using=SexpObjectSerializer.class) | ||
private static class SexpObject { | ||
private String symbolField; | ||
private Object objectField; | ||
|
||
SexpObject(String symbolField, Object objectField) { | ||
this.symbolField = symbolField; | ||
this.objectField = objectField; | ||
} | ||
|
||
public String getSymbolField() { | ||
return symbolField; | ||
} | ||
|
||
public Object getObjectField() { | ||
return objectField; | ||
} | ||
} | ||
|
||
private static class SexpObjectSerializer extends JsonSerializer<SexpObject> { | ||
@Override | ||
public void serialize(SexpObject value, JsonGenerator jsonGenerator, | ||
SerializerProvider provider) throws IOException { | ||
final IonGenerator ionGenerator = (IonGenerator) jsonGenerator; | ||
|
||
ionGenerator.writeStartSexp(); | ||
ionGenerator.writeSymbol(value.getSymbolField()); | ||
ionGenerator.writeObject(value.getObjectField()); | ||
ionGenerator.writeEndSexp(); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters