diff --git a/src/main/java/com/amihaiemil/eojsonp/RtJsonGenerator.java b/src/main/java/com/amihaiemil/eojsonp/RtJsonGenerator.java index 4b0bd7a..3b19b62 100644 --- a/src/main/java/com/amihaiemil/eojsonp/RtJsonGenerator.java +++ b/src/main/java/com/amihaiemil/eojsonp/RtJsonGenerator.java @@ -25,14 +25,21 @@ */ package com.amihaiemil.eojsonp; +import java.io.IOException; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.Writer; +import javax.json.JsonException; import javax.json.JsonValue; import javax.json.stream.JsonGenerator; /** * Base JsonGenerator implementation. Rt stands for "runtime". + * + * This JsonGenerator works as a finite automata composed of different + * JsonGenerator implementations (each node is a JsonGenerator which knows + * exactly what operations are permitted in that moment). + * * @author Mihai Andronache (amihaiemil@gmail.com) * @version $Id$ * @since 0.0.1 @@ -62,54 +69,71 @@ final class RtJsonGenerator extends ConvenientJsonGenerator { @Override public JsonGenerator writeStartObject() { - return new StartObject(); + try { + this.writer.write("{"); + } catch (final IOException ex) { + throw new IllegalStateException( + "IOException when trying to start writing JsonObject", ex + ); + } + return new StartObject(this); } @Override public JsonGenerator writeStartObject(final String name) { - throw new IllegalStateException( - "Cannot write named JsonArray, base Json structure is not " + throw new JsonException( + "Cannot write a named JsonArray, base Json structure is not " + "started yet. Use #writeStartObject() or #writeStartObject()." ); } @Override public JsonGenerator writeKey(final String name) { - return new WriteJsonValue(); + throw new JsonException( + "Cannot write a named JsonValue, base Json structure is not " + + "started yet. Use #writeStartObject() or #writeStartObject()." + ); } @Override public JsonGenerator writeStartArray() { - return new StartArray(); + try { + this.writer.write("["); + } catch (final IOException ex) { + throw new IllegalStateException( + "IOException when trying to start writing JsonArray", ex + ); + } + return new StartArray(this); } @Override public JsonGenerator writeStartArray(final String name) { - throw new IllegalStateException( - "Cannot write named JsonArray, base Json structure is not " + throw new JsonException( + "Cannot write a named JsonArray, base Json structure is not " + "started yet. Use #writeStartObject() or #writeStartObject()." ); } @Override public JsonGenerator write(final String name, final JsonValue value) { - throw new IllegalStateException( - "Cannot write named JsonValue, base Json structure is not " + throw new JsonException( + "Cannot write a named JsonValue, base Json structure is not " + "started yet. Use #writeStartObject() or #writeStartObject()." ); } @Override public JsonGenerator writeEnd() { - throw new IllegalStateException( - "Cannot end the Json structure, base Json structure is not " + throw new JsonException( + "Cannot end the Json structure, since it is not " + "started yet. Use #writeStartObject() or #writeStartObject()." ); } @Override public JsonGenerator write(final JsonValue value) { - throw new IllegalStateException( + throw new JsonException( "Cannot write JsonValue, base Json structure is not " + "started yet. Use #writeStartObject() or #writeStartObject()." ); @@ -117,19 +141,44 @@ public JsonGenerator write(final JsonValue value) { @Override public void close() { - throw new UnsupportedOperationException("Not supported yet."); + try { + this.writer.close(); + } catch (final IOException ex) { + throw new IllegalStateException( + "IOException when trying to close the Writer.", ex + ); + } } @Override public void flush() { - throw new UnsupportedOperationException("Not supported yet."); + try { + this.writer.flush(); + } catch (final IOException ex) { + throw new IllegalStateException( + "IOException when trying to flush the Writer.", ex + ); + } } /** * A JsonGenerator for started JsonObjects. */ - private final class StartObject extends ConvenientJsonGenerator { - + final class StartObject extends ConvenientJsonGenerator { + + /** + * Parent JsonGenerator which started this object generator. + */ + private final JsonGenerator parent; + + /** + * Ctor. + * @param parent Parent generator. + */ + StartObject(final JsonGenerator parent) { + this.parent = parent; + } + @Override public JsonGenerator writeStartObject() { throw new IllegalStateException("JsonObject is already started!"); @@ -137,12 +186,34 @@ public JsonGenerator writeStartObject() { @Override public JsonGenerator writeStartObject(final String name) { - return new StartObject(); + try { + RtJsonGenerator.this.writer.write( + new RtJsonString(name).toString() + ":{" + ); + } catch (final IOException ex) { + throw new IllegalStateException( + "IOException when trying to start JsonObject with key\"" + + name + "\".", + ex + ); + } + return new StartObject(this); } @Override public JsonGenerator writeKey(final String name) { - throw new UnsupportedOperationException("Not supported yet."); + try { + RtJsonGenerator.this.writer.write( + new RtJsonString(name).toString() + ":" + ); + } catch (final IOException ex) { + throw new IllegalStateException( + "IOException when trying to start JsonValue with key \"" + + name + "\".", + ex + ); + } + return new ExpectJsonValue(this); } @Override @@ -156,12 +227,35 @@ public JsonGenerator writeStartArray() { @Override public JsonGenerator writeStartArray(final String name) { - return new StartArray(); + try { + RtJsonGenerator.this.writer.write( + new RtJsonString(name).toString() + ":[" + ); + } catch (final IOException ex) { + throw new IllegalStateException( + "IOException when trying to start JsonArray with key \"" + + name + "\".", + ex + ); + } + return new StartArray(this); } @Override public JsonGenerator write(final String name, final JsonValue value) { - throw new UnsupportedOperationException("Not supported yet."); + try { + RtJsonGenerator.this.writer.write( + new RtJsonString(name).toString() + ":" + value.toString() + + "," + ); + } catch (final IOException ex) { + throw new IllegalStateException( + "IOException when trying to write key \"" + + name + "\" and value \"" + value.toString() + "\".", + ex + ); + } + return this; } @Override @@ -175,17 +269,24 @@ public JsonGenerator write(final JsonValue value) { @Override public JsonGenerator writeEnd() { - throw new UnsupportedOperationException("Not supported yet."); + try { + RtJsonGenerator.this.writer.write("},"); + } catch (final IOException ex) { + throw new IllegalStateException( + "IOException when trying to end the JsonObject", ex + ); + } + return this.parent; } @Override public void close() { - throw new UnsupportedOperationException("Not supported yet."); + this.parent.close(); } @Override public void flush() { - throw new UnsupportedOperationException("Not supported yet."); + this.parent.flush(); } } @@ -193,11 +294,31 @@ public void flush() { /** * A JsonGenerator for started JsonArrays. */ - private final class StartArray extends ConvenientJsonGenerator { - + final class StartArray extends ConvenientJsonGenerator { + + /** + * Parent JsonGenerator which started this array generator. + */ + private final JsonGenerator parent; + + /** + * Ctor. + * @param parent Parent generator. + */ + StartArray(final JsonGenerator parent) { + this.parent = parent; + } + @Override public JsonGenerator writeStartObject() { - return new StartObject(); + try { + RtJsonGenerator.this.writer.write("{"); + } catch (final IOException ex) { + throw new IllegalStateException( + "IOException when trying to start JsonObject", ex + ); + } + return new StartObject(this); } @Override @@ -218,7 +339,14 @@ public JsonGenerator writeKey(final String name) { @Override public JsonGenerator writeStartArray() { - return new StartArray(); + try { + RtJsonGenerator.this.writer.write("["); + } catch (final IOException ex) { + throw new IllegalStateException( + "IOException when trying to start JsonArray", ex + ); + } + return new StartArray(this); } @Override @@ -239,22 +367,38 @@ public JsonGenerator write(final String name, final JsonValue value) { @Override public JsonGenerator write(final JsonValue value) { - throw new IllegalStateException("Not supported yet."); + try { + RtJsonGenerator.this.writer.write(value.toString() + ","); + } catch (final IOException ex) { + throw new IllegalStateException( + "IOException when trying to write JsonValue \"" + + value.toString() + "\".", + ex + ); + } + return new StartArray(this); } @Override public JsonGenerator writeEnd() { - throw new UnsupportedOperationException("Not supported yet."); + try { + RtJsonGenerator.this.writer.write("],"); + } catch (final IOException ex) { + throw new IllegalStateException( + "IOException when trying to end the JsonObject", ex + ); + } + return this.parent; } @Override public void close() { - throw new UnsupportedOperationException("Not supported yet."); + this.parent.close(); } @Override public void flush() { - throw new UnsupportedOperationException("Not supported yet."); + this.parent.flush(); } } @@ -263,11 +407,32 @@ public void flush() { * A JsonGenerator which mandates the writing of a JsonValue after a key * is previously added. */ - private final class WriteJsonValue extends ConvenientJsonGenerator { - + final class ExpectJsonValue extends ConvenientJsonGenerator { + + /** + * Parent JsonGenerator which wrote the key for the JsonValue generated + * here. + */ + private final JsonGenerator parent; + + /** + * Ctor. + * @param parent Parent generator. + */ + ExpectJsonValue(final JsonGenerator parent) { + this.parent = parent; + } + @Override public JsonGenerator writeStartObject() { - return new StartObject(); + try { + RtJsonGenerator.this.writer.write("{"); + } catch (final IOException ex) { + throw new IllegalStateException( + "IOException when trying to start JsonObject", ex + ); + } + return new StartObject(this.parent); } @Override @@ -287,7 +452,14 @@ public JsonGenerator writeKey(final String name) { @Override public JsonGenerator writeStartArray() { - return new StartArray(); + try { + RtJsonGenerator.this.writer.write("["); + } catch (final IOException ex) { + throw new IllegalStateException( + "IOException when trying to start JsonArray", ex + ); + } + return new StartArray(this.parent); } @Override @@ -308,24 +480,33 @@ public JsonGenerator write(final String name, final JsonValue value) { @Override public JsonGenerator write(final JsonValue value) { - throw new IllegalStateException("Not supported yet."); + try { + RtJsonGenerator.this.writer.write(value.toString()); + } catch (final IOException ex) { + throw new IllegalStateException( + "IOException when trying to write JsonValue \"" + + value.toString() + "\".", + ex + ); + } + return this.parent; } @Override public JsonGenerator writeEnd() { throw new IllegalStateException( - "Cannot and the Json structure here. Need a JsonValue!" + "Cannot end the Json structure here. Need a JsonValue!" ); } @Override public void close() { - throw new UnsupportedOperationException("Not supported yet."); + this.parent.close(); } @Override public void flush() { - throw new UnsupportedOperationException("Not supported yet."); + this.parent.flush(); } } diff --git a/src/test/java/com/amihaiemil/eojsonp/RtJsonGeneratorTestCase.java b/src/test/java/com/amihaiemil/eojsonp/RtJsonGeneratorTestCase.java new file mode 100644 index 0000000..54b39ad --- /dev/null +++ b/src/test/java/com/amihaiemil/eojsonp/RtJsonGeneratorTestCase.java @@ -0,0 +1,234 @@ +/** + * Copyright (c) 2018, Mihai Emil Andronache + * All rights reserved. + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * 1)Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * 2)Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * 3)Neither the name of eo-jsonp-impl nor the names of its + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +package com.amihaiemil.eojsonp; + +import java.io.IOException; +import java.io.StringWriter; +import java.io.Writer; +import javax.json.JsonException; +import javax.json.stream.JsonGenerator; +import org.hamcrest.MatcherAssert; +import org.hamcrest.Matchers; +import org.junit.Assert; +import org.junit.Test; +import org.mockito.Mockito; + +/** + * Unit tests for {@link RtJsonGenerator}. + * @author Mihai Andronache (amihaiemil@gmail.com) + * @version $Id$ + * @since 0.0.1 + */ +public final class RtJsonGeneratorTestCase { + + /** + * RtJsonGenerator can start writing a JsonObject. + */ + @Test + public void startsJsonObject() { + final Writer written = new StringWriter(); + final JsonGenerator gen = new RtJsonGenerator(written); + MatcherAssert.assertThat( + gen.writeStartObject(), + Matchers.allOf( + Matchers.notNullValue(), + Matchers.instanceOf(RtJsonGenerator.StartObject.class) + ) + ); + MatcherAssert.assertThat(written.toString(), Matchers.equalTo("{")); + } + + /** + * RtJsonGenerator can start writing a JsonArray. + */ + @Test + public void startsJsonArray() { + final Writer written = new StringWriter(); + final JsonGenerator gen = new RtJsonGenerator(written); + MatcherAssert.assertThat( + gen.writeStartArray(), + Matchers.allOf( + Matchers.notNullValue(), + Matchers.instanceOf(RtJsonGenerator.StartArray.class) + ) + ); + MatcherAssert.assertThat(written.toString(), Matchers.equalTo("[")); + } + + /** + * RtJsonGenerator should only start generating the base Json parent, + * so it shouldn't be able to create named JsonObjects. + */ + @Test(expected = JsonException.class) + public void cannotStartNamedObject() { + new RtJsonGenerator(new StringWriter()).writeStartObject("test"); + } + + /** + * RtJsonGenerator should only start generating the base Json parent, + * so it shouldn't be able to create named JsonArrays. + */ + @Test(expected = JsonException.class) + public void cannotStartNamedArray() { + new RtJsonGenerator(new StringWriter()).writeStartArray("test"); + } + + /** + * RtJsonGenerator should only start generating the base Json parent, + * so it shouldn't be able to write any key/value pairs. + */ + @Test(expected = JsonException.class) + public void cannotWriteKeyValue() { + new RtJsonGenerator(new StringWriter()).write( + "key", new RtJsonNumber("1") + ); + } + + /** + * RtJsonGenerator should only start generating the base Json parent, + * so it shouldn't be able to write any JsonValue. + */ + @Test(expected = JsonException.class) + public void cannotWriteJsonValue() { + new RtJsonGenerator(new StringWriter()).write(new RtJsonString("test")); + } + + /** + * RtJsonGenerator should only start generating the base Json parent, + * so it shouldn't be able write the end of the it. + */ + @Test(expected = JsonException.class) + public void cannotWriteJsonEnd() { + new RtJsonGenerator(new StringWriter()).writeEnd(); + } + + /** + * RtJsonGenerator can flush ok. + * @throws Exception If something goes worng. + */ + @Test + public void flushesOk() throws Exception { + final Writer writer = Mockito.mock(Writer.class); + Mockito.doNothing().when(writer).flush(); + final JsonGenerator gen = new RtJsonGenerator(writer); + gen.flush(); + Mockito.verify(writer, Mockito.times(1)).flush(); + } + + /** + * RtJsonGenerator catches and rethrows the IOException from writer.flush(). + * @throws Exception If something goes worng. + */ + @Test + public void flusheThrowsException() throws Exception { + final Writer writer = Mockito.mock(Writer.class); + Mockito.doThrow(new IOException("#flush()")).when(writer).flush(); + final JsonGenerator gen = new RtJsonGenerator(writer); + try { + gen.flush(); + } catch (final IllegalStateException ex) { + Mockito.verify(writer, Mockito.times(1)).flush(); + return; + } + Assert.fail("#flush() should have thrown IllegalStateException!"); + } + + /** + * RtJsonGenerator can close ok. + * @throws Exception If something goes worng. + */ + @Test + public void closesOk() throws Exception { + final Writer writer = Mockito.mock(Writer.class); + Mockito.doNothing().when(writer).close(); + final JsonGenerator gen = new RtJsonGenerator(writer); + gen.close(); + Mockito.verify(writer, Mockito.times(1)).close(); + } + + /** + * RtJsonGenerator catches and rethrows the IOException from writer.flush(). + * @throws Exception If something goes worng. + */ + @Test + public void closeThrowsException() throws Exception { + final Writer writer = Mockito.mock(Writer.class); + Mockito.doThrow(new IOException("#close()")).when(writer).close(); + final JsonGenerator gen = new RtJsonGenerator(writer); + try { + gen.close(); + } catch (final IllegalStateException ex) { + Mockito.verify(writer, Mockito.times(1)).close(); + return; + } + Assert.fail("#close() should have thrown IllegalStateException!"); + } + + /** + * RtJsonGenerator catches and rethrows the IOException when trying to start + * a JsonArray. + * @throws Exception If something goes worng. + */ + @Test + public void startArrayThrowsIoException() throws Exception { + final Writer writer = Mockito.mock(Writer.class); + Mockito.doThrow(new IOException("#startArray()")) + .when(writer).write("["); + final JsonGenerator gen = new RtJsonGenerator(writer); + try { + gen.writeStartArray(); + } catch (final IllegalStateException ex) { + Mockito.verify(writer, Mockito.times(1)).write("["); + return; + } + Assert.fail( + "#writeStartArray() should have thrown IllegalStateException!" + ); + } + + /** + * RtJsonGenerator catches and rethrows the IOException when trying to start + * a JsonArray. + * @throws Exception If something goes worng. + */ + @Test + public void startObjectThrowsIoException() throws Exception { + final Writer writer = Mockito.mock(Writer.class); + Mockito.doThrow(new IOException("#startObject()")) + .when(writer).write("{"); + final JsonGenerator gen = new RtJsonGenerator(writer); + try { + gen.writeStartObject(); + } catch (final IllegalStateException ex) { + Mockito.verify(writer, Mockito.times(1)).write("{"); + return; + } + Assert.fail( + "#writeStartArray() should have thrown IllegalStateException!" + ); + } + +}