Add additional encode and decode methods to Coder 10/79310/5
authorJim Hahn <jrh3@att.com>
Wed, 27 Feb 2019 22:42:52 +0000 (17:42 -0500)
committerJim Hahn <jrh3@att.com>
Thu, 28 Feb 2019 03:05:42 +0000 (22:05 -0500)
Also:
Updated some comments and renamed a few parameters.
Removed a "throws" for a RuntimeException.

Short-circuit some calls.

Typo in comment.
Let gson create the JsonWriter.
Renamed a few more parameters.

Change-Id: I22e48c2191820c2a3d0743200edca79bd74353e7
Issue-ID: POLICY-1444
Signed-off-by: Jim Hahn <jrh3@att.com>
utils/src/main/java/org/onap/policy/common/utils/coder/Coder.java
utils/src/main/java/org/onap/policy/common/utils/coder/StandardCoder.java
utils/src/test/java/org/onap/policy/common/utils/coder/StandardCoderTest.java
utils/src/test/resources/org/onap/policy/common/utils/coder/StandardCoder.json [new file with mode: 0644]

index 41db218..66a308f 100644 (file)
 
 package org.onap.policy.common.utils.coder;
 
+import java.io.File;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.Reader;
+import java.io.Writer;
+
 /**
  * JSON encoder and decoder.
  */
@@ -35,7 +41,34 @@ public interface Coder {
     String encode(Object object) throws CoderException;
 
     /**
-     * Decodes a json string into an object.
+     * Encodes an object into json, writing to the given target.
+     *
+     * @param target target to which to write the encoded json
+     * @param object object to be encoded
+     * @throws CoderException if an error occurs
+     */
+    void encode(Writer target, Object object) throws CoderException;
+
+    /**
+     * Encodes an object into json, writing to the given target.
+     *
+     * @param target target to which to write the encoded json
+     * @param object object to be encoded
+     * @throws CoderException if an error occurs
+     */
+    void encode(OutputStream target, Object object) throws CoderException;
+
+    /**
+     * Encodes an object into json, writing to the given target.
+     *
+     * @param target target to which to write the encoded json
+     * @param object object to be encoded
+     * @throws CoderException if an error occurs
+     */
+    void encode(File target, Object object) throws CoderException;
+
+    /**
+     * Decodes json into an object.
      *
      * @param json json string to be decoded
      * @param clazz class of object to be decoded
@@ -43,4 +76,34 @@ public interface Coder {
      * @throws CoderException if an error occurs
      */
     <T> T decode(String json, Class<T> clazz) throws CoderException;
+
+    /**
+     * Decodes json into an object, reading it from the given source.
+     *
+     * @param source source from which to read the json string to be decoded
+     * @param clazz class of object to be decoded
+     * @return the object represented by the given json string
+     * @throws CoderException if an error occurs
+     */
+    <T> T decode(Reader source, Class<T> clazz) throws CoderException;
+
+    /**
+     * Decodes json into an object, reading it from the given source.
+     *
+     * @param source source from which to read the json string to be decoded
+     * @param clazz class of object to be decoded
+     * @return the object represented by the given json string
+     * @throws CoderException if an error occurs
+     */
+    <T> T decode(InputStream source, Class<T> clazz) throws CoderException;
+
+    /**
+     * Decodes json into an object, reading it from the given source.
+     *
+     * @param source source from which to read the json string to be decoded
+     * @param clazz class of object to be decoded
+     * @return the object represented by the given json string
+     * @throws CoderException if an error occurs
+     */
+    <T> T decode(File source, Class<T> clazz) throws CoderException;
 }
index 4c7a55c..389720f 100644 (file)
 package org.onap.policy.common.utils.coder;
 
 import com.google.gson.Gson;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Reader;
+import java.io.Writer;
+import java.nio.charset.StandardCharsets;
 
 /**
  * JSON encoder and decoder using the "standard" mechanism, which is currently gson.
@@ -49,6 +61,42 @@ public class StandardCoder implements Coder {
         }
     }
 
+    @Override
+    public void encode(Writer target, Object object) throws CoderException {
+        try {
+            toJson(target, object);
+
+        } catch (RuntimeException | IOException e) {
+            throw new CoderException(e);
+        }
+    }
+
+    @Override
+    public void encode(OutputStream target, Object object) throws CoderException {
+        try {
+            Writer wtr = makeWriter(target);
+            toJson(wtr, object);
+
+            // flush, but don't close
+            wtr.flush();
+
+        } catch (RuntimeException | IOException e) {
+            throw new CoderException(e);
+        }
+    }
+
+    @Override
+    public void encode(File target, Object object) throws CoderException {
+        try (Writer wtr = makeWriter(target)) {
+            toJson(wtr, object);
+
+            // no need to flush or close here
+
+        } catch (RuntimeException | IOException e) {
+            throw new CoderException(e);
+        }
+    }
+
     @Override
     public <T> T decode(String json, Class<T> clazz) throws CoderException {
         try {
@@ -59,8 +107,80 @@ public class StandardCoder implements Coder {
         }
     }
 
+    @Override
+    public <T> T decode(Reader source, Class<T> clazz) throws CoderException {
+        try {
+            return fromJson(source, clazz);
+
+        } catch (RuntimeException e) {
+            throw new CoderException(e);
+        }
+    }
+
+    @Override
+    public <T> T decode(InputStream source, Class<T> clazz) throws CoderException {
+        try {
+            return fromJson(makeReader(source), clazz);
+
+        } catch (RuntimeException e) {
+            throw new CoderException(e);
+        }
+    }
+
+    @Override
+    public <T> T decode(File source, Class<T> clazz) throws CoderException {
+        try (Reader input = makeReader(source)) {
+            return fromJson(input, clazz);
+
+        } catch (RuntimeException | IOException e) {
+            throw new CoderException(e);
+        }
+    }
+
     // the remaining methods are wrappers that can be overridden by junit tests
 
+    /**
+     * Makes a writer for the given file.
+     *
+     * @param target file of interest
+     * @return a writer for the file
+     * @throws FileNotFoundException if the file cannot be created
+     */
+    protected Writer makeWriter(File target) throws FileNotFoundException {
+        return makeWriter(new FileOutputStream(target));
+    }
+
+    /**
+     * Makes a writer for the given stream.
+     *
+     * @param target stream of interest
+     * @return a writer for the stream
+     */
+    protected Writer makeWriter(OutputStream target) {
+        return new OutputStreamWriter(target, StandardCharsets.UTF_8);
+    }
+
+    /**
+     * Makes a reader for the given file.
+     *
+     * @param source file of interest
+     * @return a reader for the file
+     * @throws FileNotFoundException if the file does not exist
+     */
+    protected Reader makeReader(File source) throws FileNotFoundException {
+        return makeReader(new FileInputStream(source));
+    }
+
+    /**
+     * Makes a reader for the given stream.
+     *
+     * @param source stream of interest
+     * @return a reader for the stream
+     */
+    protected Reader makeReader(InputStream source) {
+        return new InputStreamReader(source, StandardCharsets.UTF_8);
+    }
+
     /**
      * Encodes an object into json, without catching exceptions.
      *
@@ -71,6 +191,17 @@ public class StandardCoder implements Coder {
         return GSON.toJson(object);
     }
 
+    /**
+     * Encodes an object into json, without catching exceptions.
+     *
+     * @param target target to which to write the encoded json
+     * @param object object to be encoded
+     * @throws IOException if an I/O error occurs
+     */
+    protected void toJson(Writer target, Object object) throws IOException {
+        GSON.toJson(object, object.getClass(), target);
+    }
+
     /**
      * Decodes a json string into an object, without catching exceptions.
      *
@@ -81,4 +212,15 @@ public class StandardCoder implements Coder {
     protected <T> T fromJson(String json, Class<T> clazz) {
         return GSON.fromJson(json, clazz);
     }
+
+    /**
+     * Decodes a json string into an object, without catching exceptions.
+     *
+     * @param source source from which to read the json string to be decoded
+     * @param clazz class of object to be decoded
+     * @return the object represented by the given json string
+     */
+    protected <T> T fromJson(Reader source, Class<T> clazz) {
+        return GSON.fromJson(source, clazz);
+    }
 }
index 80157d0..25cce74 100644 (file)
@@ -22,17 +22,35 @@ package org.onap.policy.common.utils.coder;
 
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
 import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.when;
 
 import com.google.gson.JsonElement;
 import com.google.gson.JsonParseException;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileReader;
+import java.io.IOException;
+import java.io.OutputStreamWriter;
+import java.io.Reader;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.io.Writer;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
 import java.util.Arrays;
 import java.util.List;
 import org.junit.Before;
 import org.junit.Test;
 
 public class StandardCoderTest {
+    private static final String EXPECTED_EXCEPTION = "expected exception";
+
+    private static final JsonParseException jpe = new JsonParseException(EXPECTED_EXCEPTION);
+    private static final IOException ioe = new IOException(EXPECTED_EXCEPTION);
 
     private StandardCoder coder;
 
@@ -42,32 +60,140 @@ public class StandardCoderTest {
     }
 
     @Test
-    public void testEncode() throws Exception {
-        List<Integer> arr = Arrays.asList(100, 110);
-        assertEquals("[100,110]", coder.encode(arr));
+    public void testEncodeObject() throws Exception {
+        List<Integer> arr = Arrays.asList(1100, 1110);
+        assertEquals("[1100,1110]", coder.encode(arr));
 
         // test exception case
-        JsonParseException jpe = new JsonParseException("expected exception");
-
-        coder = spy(coder);
+        coder = spy(new StandardCoder());
         when(coder.toJson(arr)).thenThrow(jpe);
-
         assertThatThrownBy(() -> coder.encode(arr)).isInstanceOf(CoderException.class).hasCause(jpe);
     }
 
     @Test
-    public void testDecode() throws Exception {
-        String text = "[200,210]";
-        assertEquals(text, coder.decode(text, JsonElement.class).toString());
+    public void testEncodeWriterObject() throws Exception {
+        List<Integer> arr = Arrays.asList(1200, 1210);
+        StringWriter wtr = new StringWriter();
+        coder.encode(wtr, arr);
+        assertEquals("[1200,1210]", wtr.toString());
 
-        // test exception case
-        JsonParseException jpe = new JsonParseException("expected exception");
+        // test json exception
+        coder = spy(new StandardCoder());
+        doThrow(jpe).when(coder).toJson(wtr, arr);
+        assertThatThrownBy(() -> coder.encode(wtr, arr)).isInstanceOf(CoderException.class).hasCause(jpe);
+    }
 
-        coder = spy(coder);
-        when(coder.fromJson(text, JsonElement.class)).thenThrow(jpe);
+    @Test
+    public void testEncodeOutputStreamObject() throws Exception {
+        List<Integer> arr = Arrays.asList(1300, 1310);
+        ByteArrayOutputStream stream = new ByteArrayOutputStream();
+        coder.encode(stream, arr);
+        assertEquals("[1300,1310]", stream.toString("UTF-8"));
+
+        // test json exception
+        Writer wtr = new StringWriter();
+        coder = spy(new StandardCoder());
+        when(coder.makeWriter(stream)).thenReturn(wtr);
+        doThrow(jpe).when(coder).toJson(wtr, arr);
+        assertThatThrownBy(() -> coder.encode(stream, arr)).isInstanceOf(CoderException.class).hasCause(jpe);
 
+        // test exception when flushed
+        wtr = spy(new OutputStreamWriter(stream));
+        doThrow(ioe).when(wtr).flush();
+        coder = spy(new StandardCoder());
+        when(coder.makeWriter(stream)).thenReturn(wtr);
+        assertThatThrownBy(() -> coder.encode(stream, arr)).isInstanceOf(CoderException.class).hasCause(ioe);
+    }
+
+    @Test
+    public void testEncodeFileObject() throws Exception {
+        File file = new File(getClass().getResource(StandardCoder.class.getSimpleName() + ".json").getFile() + "X");
+        file.deleteOnExit();
+        List<Integer> arr = Arrays.asList(1400, 1410);
+        coder.encode(file, arr);
+        assertEquals("[1400,1410]", new String(Files.readAllBytes(file.toPath()), StandardCharsets.UTF_8));
+
+        // test json exception
+        StringWriter wtr = new StringWriter();
+        coder = spy(new StandardCoder());
+        when(coder.makeWriter(file)).thenReturn(wtr);
+        doThrow(jpe).when(coder).toJson(wtr, arr);
+        assertThatThrownBy(() -> coder.encode(file, arr)).isInstanceOf(CoderException.class).hasCause(jpe);
+
+        // test exception when closed
+        coder = spy(new StandardCoder());
+        wtr = spy(new StringWriter());
+        doThrow(ioe).when(wtr).close();
+        coder = spy(new StandardCoder());
+        when(coder.makeWriter(file)).thenReturn(wtr);
+        assertThatThrownBy(() -> coder.encode(file, arr)).isInstanceOf(CoderException.class).hasCause(ioe);
+    }
+
+    @Test
+    public void testDecodeStringClass() throws Exception {
+        String text = "[2200,2210]";
+        assertEquals(text, coder.decode(text, JsonElement.class).toString());
+
+        // test json exception
+        coder = spy(new StandardCoder());
+        when(coder.fromJson(text, JsonElement.class)).thenThrow(jpe);
         assertThatThrownBy(() -> coder.decode(text, JsonElement.class)).isInstanceOf(CoderException.class)
                         .hasCause(jpe);
     }
 
+    @Test
+    public void testDecodeReaderClass() throws Exception {
+        String text = "[2300,2310]";
+        assertEquals(text, coder.decode(new StringReader(text), JsonElement.class).toString());
+
+        // test json exception
+        coder = spy(new StandardCoder());
+        StringReader rdr = new StringReader(text);
+        when(coder.fromJson(rdr, JsonElement.class)).thenThrow(jpe);
+        assertThatThrownBy(() -> coder.decode(rdr, JsonElement.class)).isInstanceOf(CoderException.class).hasCause(jpe);
+    }
+
+    @Test
+    public void testDecodeInputStreamClass() throws Exception {
+        String text = "[2400,2410]";
+        assertEquals(text,
+                        coder.decode(new ByteArrayInputStream(text.getBytes(StandardCharsets.UTF_8)), JsonElement.class)
+                                        .toString());
+
+        // test json exception
+        coder = spy(new StandardCoder());
+        ByteArrayInputStream stream = new ByteArrayInputStream(text.getBytes(StandardCharsets.UTF_8));
+        StringReader rdr = new StringReader(text);
+        when(coder.makeReader(stream)).thenReturn(rdr);
+        when(coder.fromJson(rdr, JsonElement.class)).thenThrow(jpe);
+        assertThatThrownBy(() -> coder.decode(stream, JsonElement.class)).isInstanceOf(CoderException.class)
+                        .hasCause(jpe);
+    }
+
+    @Test
+    public void testDecodeFileClass() throws Exception {
+        File file = new File(getClass().getResource(StandardCoder.class.getSimpleName() + ".json").getFile());
+        String text = new String(Files.readAllBytes(file.toPath()), StandardCharsets.UTF_8);
+        assertEquals(text, coder.decode(file, JsonElement.class).toString());
+
+        // test FileNotFoundException case
+        assertThatThrownBy(() -> coder.decode(new File("unknown-file"), JsonElement.class))
+                        .isInstanceOf(CoderException.class).hasCauseInstanceOf(FileNotFoundException.class);
+
+        // test json exception
+        Reader rdr = new StringReader(text);
+        coder = spy(new StandardCoder());
+        when(coder.makeReader(file)).thenReturn(rdr);
+        when(coder.fromJson(rdr, JsonElement.class)).thenThrow(jpe);
+        assertThatThrownBy(() -> coder.decode(file, JsonElement.class)).isInstanceOf(CoderException.class)
+                        .hasCause(jpe);
+
+        // test IOException case
+        rdr = spy(new FileReader(file));
+        doThrow(ioe).when(rdr).close();
+        coder = spy(new StandardCoder());
+        when(coder.makeReader(file)).thenReturn(rdr);
+        assertThatThrownBy(() -> coder.decode(file, JsonElement.class)).isInstanceOf(CoderException.class)
+                        .hasCause(ioe);
+    }
 }
diff --git a/utils/src/test/resources/org/onap/policy/common/utils/coder/StandardCoder.json b/utils/src/test/resources/org/onap/policy/common/utils/coder/StandardCoder.json
new file mode 100644 (file)
index 0000000..b50b53b
--- /dev/null
@@ -0,0 +1 @@
+[3000,3010]
\ No newline at end of file