Skip to content

Commit 49a98dc

Browse files
committed
Add support for generating IonSexps
Add writeStartSexp and writeEndSexp methods to the IonGenerator. This requires extending the JsonWriteContext into a new class that recognizes the sexp context.
1 parent f6ec934 commit 49a98dc

File tree

4 files changed

+209
-1
lines changed

4 files changed

+209
-1
lines changed

ion/src/main/java/com/fasterxml/jackson/dataformat/ion/IonGenerator.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,8 @@ public IonGenerator(int jsonFeatures, final int ionFeatures, ObjectCodec codec,
135135
IonWriter ion, boolean ionWriterIsManaged, IOContext ctxt, Closeable dst)
136136
{
137137
super(jsonFeatures, codec);
138+
// Overwrite the writecontext with our own implementation
139+
_writeContext = IonWriteContext.createRootContext(_writeContext.getDupDetector());
138140
_formatFeatures = ionFeatures;
139141
_writer = ion;
140142
_ionWriterIsManaged = ionWriterIsManaged;
@@ -458,12 +460,20 @@ protected void _verifyValueWrite(String msg) throws IOException, JsonGenerationE
458460
case JsonWriteContext.STATUS_OK_AFTER_SPACE:
459461
_cfgPrettyPrinter.writeRootValueSeparator(this);
460462
break;
463+
case IonWriteContext.STATUS_OK_AFTER_SEXP_SEPARATOR:
464+
// Special handling of sexp value separators can be added later. Root value
465+
// separator will be whitespace which is sufficient to separate sexp values
466+
_cfgPrettyPrinter.writeRootValueSeparator(this);
467+
break;
461468
case JsonWriteContext.STATUS_OK_AS_IS:
462469
// First entry, but of which context?
463470
if (_writeContext.inArray()) {
464471
_cfgPrettyPrinter.beforeArrayValues(this);
465472
} else if (_writeContext.inObject()) {
466473
_cfgPrettyPrinter.beforeObjectEntries(this);
474+
} else if(((IonWriteContext) _writeContext).inSexp()) {
475+
// Format sexps like arrays
476+
_cfgPrettyPrinter.beforeArrayValues(this);
467477
}
468478
break;
469479
default:
@@ -484,6 +494,11 @@ public void writeEndObject() throws IOException, JsonGenerationException {
484494
_writer.stepOut();
485495
}
486496

497+
public void writeEndSexp() throws IOException, JsonGenerationException {
498+
_writeContext = _writeContext.getParent();
499+
_writer.stepOut();
500+
}
501+
487502
@Override
488503
public void writeFieldName(String value) throws IOException, JsonGenerationException {
489504
//This call to _writeContext is copied from Jackson's UTF8JsonGenerator.writeFieldName(String)
@@ -515,6 +530,12 @@ public void writeStartObject() throws IOException, JsonGenerationException {
515530
_writer.stepIn(IonType.STRUCT);
516531
}
517532

533+
public void writeStartSexp() throws IOException, JsonGenerationException {
534+
_verifyValueWrite("start a sexp"); // <-- copied from UTF8JsonGenerator
535+
_writeContext = ((IonWriteContext) _writeContext).createChildSexpContext();
536+
_writer.stepIn(IonType.SEXP);
537+
}
538+
518539
/*
519540
/*****************************************************************
520541
/* Support for type ids
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
/*
2+
* Copyright 2012-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License").
5+
* You may not use this file except in compliance with the License.
6+
* A copy of the License is located at:
7+
*
8+
* http://aws.amazon.com/apache2.0/
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS,
11+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific
12+
* language governing permissions and limitations under the License.
13+
*/
14+
15+
package com.fasterxml.jackson.dataformat.ion;
16+
17+
import com.fasterxml.jackson.core.json.DupDetector;
18+
import com.fasterxml.jackson.core.json.JsonWriteContext;
19+
20+
/**
21+
* Extension of JsonWriteContexts that recognizes sexps
22+
* <p>
23+
* The JsonWriteContext is used by the pretty printer for handling of the whitespace between tokens,
24+
* and by the generator for verifying whether it's valid to write a given token. The writeStartSexp
25+
* method in the IonGenerator will enter a "sexp context", so we need a new state in the write
26+
* context to track that. Sexp handling is modeled after arrays.
27+
*/
28+
public class IonWriteContext extends JsonWriteContext {
29+
// Both contstants are in the tens instead of the ones to avoid conflict with the native
30+
// Jackson ones
31+
32+
// Ion-specific contexts
33+
protected final static int TYPE_SEXP = 30;
34+
35+
// Ion-specific statuses
36+
public final static int STATUS_OK_AFTER_SEXP_SEPARATOR = 60;
37+
38+
protected IonWriteContext(int type, IonWriteContext parent, DupDetector dups) {
39+
super(type, parent, dups);
40+
}
41+
42+
public static IonWriteContext createRootContext(DupDetector dd) {
43+
return new IonWriteContext(TYPE_ROOT, null, dd);
44+
}
45+
46+
public IonWriteContext createChildSexpContext() {
47+
IonWriteContext ctxt = (IonWriteContext) _child;
48+
49+
if(ctxt == null) {
50+
// same assignment as in createChildObjectContext, createChildArrayContext
51+
_child = ctxt = new IonWriteContext(TYPE_SEXP, this, (_dups == null) ? null : _dups.child());
52+
}
53+
54+
// reset returns this, OK to cast
55+
return (IonWriteContext) ctxt.reset(TYPE_SEXP);
56+
}
57+
58+
public final boolean inSexp() {
59+
return _type == TYPE_SEXP;
60+
}
61+
62+
// // Overrides
63+
64+
// We have to override the two createChild*Context methods to return a IonWriteContext
65+
// instead of a JsonWriteContext so sexps can be arbitrarily embedded in ion. Otherwise we
66+
// would only be able to create them as top level values.
67+
// Two methods below are copied from JsonWriteContext
68+
69+
@Override
70+
public IonWriteContext createChildArrayContext() {
71+
IonWriteContext ctxt = (IonWriteContext) _child;
72+
73+
if (ctxt == null) {
74+
_child = ctxt = new IonWriteContext(TYPE_ARRAY, this, (_dups == null) ? null : _dups.child());
75+
return ctxt;
76+
}
77+
78+
return (IonWriteContext) ctxt.reset(TYPE_ARRAY);
79+
}
80+
81+
@Override
82+
public IonWriteContext createChildObjectContext() {
83+
IonWriteContext ctxt = (IonWriteContext) _child;
84+
85+
if (ctxt == null) {
86+
_child = ctxt = new IonWriteContext(TYPE_OBJECT, this, (_dups == null) ? null : _dups.child());
87+
return ctxt;
88+
}
89+
return (IonWriteContext) ctxt.reset(TYPE_OBJECT);
90+
}
91+
92+
@Override
93+
public int writeValue() {
94+
// Add special handling for sexp separator
95+
if(_type == TYPE_SEXP) {
96+
int ix = _index;
97+
++_index;
98+
return (ix < 0) ? STATUS_OK_AS_IS : STATUS_OK_AFTER_SEXP_SEPARATOR;
99+
}
100+
101+
return super.writeValue();
102+
}
103+
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
/*
2+
* Copyright 2012-2016 Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License").
5+
* You may not use this file except in compliance with the License.
6+
* A copy of the License is located at:
7+
*
8+
* http://aws.amazon.com/apache2.0/
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed on an "AS IS" BASIS,
11+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific
12+
* language governing permissions and limitations under the License.
13+
*/
14+
15+
package com.fasterxml.jackson.dataformat.ion;
16+
17+
import com.amazon.ion.IonSexp;
18+
import com.amazon.ion.IonSystem;
19+
import com.amazon.ion.system.IonSystemBuilder;
20+
import com.fasterxml.jackson.core.JsonGenerator;
21+
import com.fasterxml.jackson.databind.JsonSerializer;
22+
import com.fasterxml.jackson.databind.SerializerProvider;
23+
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
24+
import java.io.IOException;
25+
import java.util.Arrays;
26+
import java.util.List;
27+
import org.junit.Assert;
28+
import org.junit.Test;
29+
30+
/**
31+
* End to end test verifying we can serialize sexps
32+
*/
33+
public class GenerateSexpTest {
34+
@Test
35+
public void testGenerateSexp() throws IOException {
36+
final IonSystem ionSystem = IonSystemBuilder.standard().build();
37+
final IonObjectMapper mapper = new IonObjectMapper(new IonFactory(null, ionSystem));
38+
39+
Assert.assertEquals(
40+
ionSystem.singleValue("[(foo \"bar\"), (baz \"qux\")]"),
41+
mapper.writeValueAsIonValue(
42+
Arrays.asList(new SomePojo("foo", "bar"), new SomePojo("baz", "qux"))));
43+
}
44+
45+
// Create some pojo that defines a custom serializer that creates an IonSexp
46+
@JsonSerialize(using=SomePojoSerializer.class)
47+
private static class SomePojo {
48+
private String symbolField;
49+
private String stringField;
50+
51+
public SomePojo(String symbolField, String stringField) {
52+
this.symbolField = symbolField;
53+
this.stringField = stringField;
54+
}
55+
56+
public String getSymbolField() {
57+
return symbolField;
58+
}
59+
60+
public String getStringField() {
61+
return stringField;
62+
}
63+
}
64+
65+
private static class SomePojoSerializer extends JsonSerializer<SomePojo> {
66+
@Override
67+
public void serialize(SomePojo value, JsonGenerator jsonGenerator,
68+
SerializerProvider provider) throws IOException {
69+
final IonGenerator joiGenerator = (IonGenerator) jsonGenerator;
70+
71+
joiGenerator.writeStartSexp();
72+
joiGenerator.writeSymbol(value.getSymbolField());
73+
joiGenerator.writeString(value.getStringField());
74+
joiGenerator.writeEndSexp();
75+
}
76+
}
77+
}

ion/src/test/java/com/fasterxml/jackson/dataformat/ion/IonGeneratorTest.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
import org.junit.Test;
1818
import org.junit.Before;
19+
import com.fasterxml.jackson.core.JsonGenerationException;
1920
import com.fasterxml.jackson.databind.JsonNode;
2021

2122
import com.amazon.ion.IonDatagram;
@@ -58,7 +59,7 @@ public class IonGeneratorTest {
5859

5960
@Before
6061
public void setUp() throws Exception {
61-
final IonFactory factory = new IonFactory();
62+
final IonFactory factory = new IonFactory();
6263

6364
this.joiObjectMapper = IonObjectMapper.builder(factory).build();
6465
this.ionSystem = IonSystemBuilder.standard().build();
@@ -113,4 +114,10 @@ public void testTreeWriteVerifiesOnce() throws Exception {
113114
final IonStruct struct = (IonStruct) output.get(0);
114115
assertThat(struct.get(FIELD), is(testObjectIon));
115116
}
117+
118+
@Test(expected = JsonGenerationException.class)
119+
public void testWriteFieldNameFailsInSexp() throws Exception {
120+
joiGenerator.writeStartSexp();
121+
joiGenerator.writeFieldName("foo");
122+
}
116123
}

0 commit comments

Comments
 (0)