Add convert() to Coder 47/101847/1
authorJim Hahn <jrh3@att.com>
Mon, 17 Feb 2020 18:21:08 +0000 (13:21 -0500)
committerJim Hahn <jrh3@att.com>
Mon, 17 Feb 2020 18:46:43 +0000 (13:46 -0500)
This addresses Liam's review comment about moving the "translate"
method from the actor Util class into policy-common.
Added a method to Coder to convert from one object type to
another (e.g., from a Map to a POJO, or vice versa).

Issue-ID: POLICY-2363
Signed-off-by: Jim Hahn <jrh3@att.com>
Change-Id: I2a0b5ab4ce4b0eeda216a57cbe23a8bb64f64940

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/CoderTest.java [new file with mode: 0644]
utils/src/test/java/org/onap/policy/common/utils/coder/StandardCoderTest.java

index ec0e5e4..3049a5c 100644 (file)
@@ -31,6 +31,37 @@ import java.io.Writer;
  */
 public interface Coder {
 
+    /**
+     * Converts an object/POJO to an object of the given type.
+     *
+     * @param <T> desired type
+     * @param source source object
+     * @param clazz class of the desired object type
+     * @return the converted object
+     * @throws CoderException if an error occurs
+     */
+    default <S, T> T convert(S source, Class<T> clazz) throws CoderException {
+        if (source == null) {
+            return null;
+
+        } else if (clazz == source.getClass()) {
+            // same class - just cast it
+            return clazz.cast(source);
+
+        } else if (clazz == String.class) {
+            // target is a string - just encode the source
+            return (clazz.cast(encode(source)));
+
+        } else if (source.getClass() == String.class) {
+            // source is a string - just decode it
+            return decode(source.toString(), clazz);
+
+        } else {
+            // do it the long way: encode to a string and then decode the string
+            return decode(encode(source), clazz);
+        }
+    }
+
     /**
      * Encodes an object into json.
      *
@@ -44,7 +75,8 @@ public interface Coder {
      * Encodes an object into json, optionally making it "pretty".
      *
      * @param object object to be encoded
-     * @param pretty {@code true} if it should be encoded as "pretty" json, {@code false} otherwise
+     * @param pretty {@code true} if it should be encoded as "pretty" json, {@code false}
+     *        otherwise
      * @return a json string representing the object
      * @throws CoderException if an error occurs
      */
index 13973f1..9d444ca 100644 (file)
@@ -77,6 +77,39 @@ public class StandardCoder implements Coder {
         super();
     }
 
+    @Override
+    public <S, T> T convert(S source, Class<T> clazz) throws CoderException {
+        if (source == null) {
+            return null;
+
+        } else if (clazz == source.getClass()) {
+            // same class - just cast it
+            return clazz.cast(source);
+
+        } else if (clazz == String.class) {
+            // target is a string - just encode the source
+            return (clazz.cast(encode(source)));
+
+        } else if (source.getClass() == String.class) {
+            // source is a string - just decode it
+            return decode(source.toString(), clazz);
+
+        } else {
+            /*
+             * Do it the long way: encode to a tree and then decode the tree. This entire
+             * method could have been left out and the default Coder.convert() used
+             * instead, but this should perform slightly better as it only uses a
+             * JsonElement as the intermediate data structure, while Coder.convert() goes
+             * all the way to a String as the intermediate data structure.
+             */
+            try {
+                return fromJson(toJsonTree(source), clazz);
+            } catch (RuntimeException e) {
+                throw new CoderException(e);
+            }
+        }
+    }
+
     @Override
     public String encode(Object object) throws CoderException {
         return encode(object, false);
diff --git a/utils/src/test/java/org/onap/policy/common/utils/coder/CoderTest.java b/utils/src/test/java/org/onap/policy/common/utils/coder/CoderTest.java
new file mode 100644 (file)
index 0000000..0182150
--- /dev/null
@@ -0,0 +1,129 @@
+/*-
+ * ============LICENSE_START=======================================================
+ * ONAP
+ * ================================================================================
+ * Copyright (C) 2020 AT&T Intellectual Property. 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.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License 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.
+ * ============LICENSE_END=========================================================
+ */
+
+package org.onap.policy.common.utils.coder;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+import java.io.File;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.Reader;
+import java.io.Writer;
+import org.junit.Before;
+import org.junit.Test;
+
+public class CoderTest {
+    private static final Long LONG = 10L;
+    private static final Integer INTEGER = 10;
+    private static final String INT_TEXT = INTEGER.toString();
+    private static final String TEXT = "some text";
+    private static final String ENCODED = "encoded value";
+    private static final String DECODED = "decoded value";
+
+    private MyCoder coder;
+
+    @Before
+    public void setUp() {
+        coder = new MyCoder();
+    }
+
+    @Test
+    public void testConvert() throws CoderException {
+        assertNull(coder.convert(null, String.class));
+
+        // same class of object
+        assertEquals(TEXT, coder.convert(TEXT, String.class));
+        assertEquals(INTEGER, coder.convert(INTEGER, Integer.class));
+
+        // source is a string
+        assertEquals(INTEGER, coder.convert(TEXT, Integer.class));
+
+        // target is a string
+        assertEquals(INT_TEXT, coder.convert(INTEGER, String.class));
+
+        // source and target are different types, neither is a string
+        assertEquals(INTEGER, coder.convert(LONG, Integer.class));
+    }
+
+    private static class MyCoder implements Coder {
+        @Override
+        public String encode(Object object) throws CoderException {
+            return (object.getClass() == String.class ? ENCODED : INT_TEXT);
+        }
+
+        @Override
+        public String encode(Object object, boolean pretty) throws CoderException {
+            // unused
+            return null;
+        }
+
+        @Override
+        public void encode(Writer target, Object object) throws CoderException {
+            // unused
+        }
+
+        @Override
+        public void encode(OutputStream target, Object object) throws CoderException {
+            // unused
+        }
+
+        @Override
+        public void encode(File target, Object object) throws CoderException {
+            // unused
+        }
+
+        @Override
+        public <T> T decode(String json, Class<T> clazz) throws CoderException {
+            return (clazz == String.class ? clazz.cast(DECODED) : clazz.cast(INTEGER));
+        }
+
+        @Override
+        public <T> T decode(Reader source, Class<T> clazz) throws CoderException {
+            // unused
+            return null;
+        }
+
+        @Override
+        public <T> T decode(InputStream source, Class<T> clazz) throws CoderException {
+            // unused
+            return null;
+        }
+
+        @Override
+        public <T> T decode(File source, Class<T> clazz) throws CoderException {
+            // unused
+            return null;
+        }
+
+        @Override
+        public StandardCoderObject toStandard(Object object) throws CoderException {
+            // unused
+            return null;
+        }
+
+        @Override
+        public <T> T fromStandard(StandardCoderObject sco, Class<T> clazz) throws CoderException {
+            // unused
+            return null;
+        }
+    }
+}
index d5cde55..ad4382c 100644 (file)
@@ -23,6 +23,8 @@ package org.onap.policy.common.utils.coder;
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
 import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.when;
@@ -65,6 +67,35 @@ public class StandardCoderTest {
         coder = new StandardCoder();
     }
 
+    @Test
+    public void testConvert() throws CoderException {
+        // null source
+        assertNull(coder.convert(null, StandardCoderObject.class));
+
+        // same class of object
+        StandardCoderObject sco = new StandardCoderObject();
+        assertSame(sco, coder.convert(sco, StandardCoderObject.class));
+
+        // source is a string
+        assertEquals(Integer.valueOf(10), coder.convert("10", Integer.class));
+
+        // target is a string
+        assertEquals("10", coder.convert(10, String.class));
+
+        // source and target are different types, neither is a string
+        sco = coder.convert(Map.of("hello", "world"), StandardCoderObject.class);
+        assertEquals("world", sco.getString("hello"));
+
+        // throw an exeception
+        coder = new StandardCoder() {
+            @Override
+            protected <T> T fromJson(JsonElement json, Class<T> clazz) {
+                throw jpe;
+            }
+        };
+        assertThatThrownBy(() -> coder.convert(10, Long.class)).isInstanceOf(CoderException.class).hasCause(jpe);
+    }
+
     @Test
     public void testEncodeObject() throws Exception {
         List<Integer> arr = Arrays.asList(1100, 1110);
@@ -312,7 +343,7 @@ public class StandardCoderTest {
 
         // test when decoding into a map
         @SuppressWarnings("unchecked")
-        Map<String,Object> map2 = coder.decode("{'intValue':10, 'dblVal':20.1}", TreeMap.class);
+        Map<String, Object> map2 = coder.decode("{'intValue':10, 'dblVal':20.1}", TreeMap.class);
         assertEquals("{dblVal=20.1, intValue=10}", map2.toString());
     }