Add JSONValue and JSONCodec.unparseValue() 11/111211/4
authorRobert Varga <robert.varga@pantheon.tech>
Tue, 2 Apr 2024 17:54:08 +0000 (19:54 +0200)
committerRobert Varga <robert.varga@pantheon.tech>
Wed, 3 Apr 2024 22:48:59 +0000 (00:48 +0200)
RESTCONF use case requires serializing values using JSON encoding --
without having a document ready.

Introduce JSONValue to encapsulate what sort of values we can have along
with JSONCodec.unparseValue() -- which produces such values.

JIRA: YANGTOOLS-1569
Change-Id: I398af058b5c017803a9dc4c61d3f7cfbc45cca5b
Signed-off-by: Robert Varga <robert.varga@pantheon.tech>
codec/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/BooleanJSONCodec.java
codec/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/EmptyJSONCodec.java
codec/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/IdentityrefJSONCodec.java
codec/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/JSONCodec.java
codec/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/JSONInstanceIdentifierCodec.java
codec/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/JSONValue.java [new file with mode: 0644]
codec/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/NullJSONCodec.java
codec/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/NumberJSONCodec.java
codec/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/QuotedJSONCodec.java
codec/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/UnionJSONCodec.java

index 401465b9291e91597e8c4941708f95069eab122e..9730124cc35ee0abd2102c2c9488d977a3354673 100644 (file)
@@ -22,4 +22,9 @@ final class BooleanJSONCodec extends AbstractJSONCodec<Boolean> {
     public void writeValue(final JSONValueWriter ctx, final Boolean value) throws IOException {
         ctx.writeBoolean(value);
     }
+
+    @Override
+    public JSONValue unparseValue(final Boolean value) {
+        return value ? JSONValue.TRUE : JSONValue.FALSE;
+    }
 }
index 46f33cb2d3d1ae0a96090e91374a46006797a10b..4a6d8b810988b357c0194cb31743eb28e64f9d56 100644 (file)
@@ -27,6 +27,11 @@ final class EmptyJSONCodec implements JSONCodec<Empty> {
         return Empty.value();
     }
 
+    @Override
+    public JSONValue unparseValue(final Empty value) {
+        return JSONValue.EMPTY;
+    }
+
     @Override
     public void writeValue(final JSONValueWriter ctx, final Empty value) throws IOException {
         ctx.writeEmpty();
index 3c3dad959cba8795eccd35600ed14459907811e4..c4d0de9947e248bbec95ce1d987129dd3a13fd1b 100644 (file)
@@ -14,6 +14,7 @@ import java.io.IOException;
 import org.eclipse.jdt.annotation.NonNull;
 import org.opendaylight.yangtools.yang.common.QName;
 import org.opendaylight.yangtools.yang.common.QNameModule;
+import org.opendaylight.yangtools.yang.data.codec.gson.JSONValue.Kind;
 import org.opendaylight.yangtools.yang.data.util.codec.IdentityCodecUtil;
 import org.opendaylight.yangtools.yang.data.util.codec.QNameCodecUtil;
 import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
@@ -45,10 +46,19 @@ final class IdentityrefJSONCodec implements JSONCodec<QName> {
         }).getQName();
     }
 
+    @Override
+    public JSONValue unparseValue(final QName value) {
+        return new JSONValue(encode(value), Kind.STRING);
+    }
+
     @Override
     public void writeValue(final JSONValueWriter ctx, final QName value) throws IOException {
-        ctx.writeString(QNameCodecUtil.encodeQName(value, uri -> context.findModuleStatement(uri)
+        ctx.writeString(encode(value));
+    }
+
+    private @NonNull String encode(final QName value) {
+        return QNameCodecUtil.encodeQName(value, uri -> context.findModuleStatement(uri)
             .map(module -> module.argument().getLocalName())
-            .orElseThrow(() -> new IllegalArgumentException("Cannot find module for " + uri))));
+            .orElseThrow(() -> new IllegalArgumentException("Cannot find module for " + uri)));
     }
 }
index 908709d88fa3bc9f57ed15a4ca13c01216f009e7..d872cced6eb21ffb7166f279b6f19dd0566123d5 100644 (file)
@@ -9,6 +9,7 @@ package org.opendaylight.yangtools.yang.data.codec.gson;
 
 import com.google.gson.stream.JsonWriter;
 import java.io.IOException;
+import org.eclipse.jdt.annotation.NonNull;
 import org.opendaylight.yangtools.yang.data.util.codec.TypeAwareCodec;
 
 /**
@@ -60,4 +61,13 @@ public sealed interface JSONCodec<T> extends TypeAwareCodec<T, Object, JsonWrite
      * @throws IllegalArgumentException if the value does not parse or pass type validation
      */
     T parseValue(String str);
+
+    /**
+     * Return the {@link JSONValue} representation of a native value.
+     *
+     * @param value Value in native format
+     * @return A {@link JSONValue}
+     * @throws IllegalArgumentException if the value does not parse or pass type validation
+     */
+    @NonNull JSONValue unparseValue(T value);
 }
\ No newline at end of file
index ed7ec5bb01022cd89cb7678d2bee4124add4745e..2c45a60f6523d009bf3b14be3bd2d631c1a3a055 100644 (file)
@@ -16,6 +16,7 @@ import org.opendaylight.yangtools.yang.common.QName;
 import org.opendaylight.yangtools.yang.common.QNameModule;
 import org.opendaylight.yangtools.yang.common.XMLNamespace;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.codec.gson.JSONValue.Kind;
 import org.opendaylight.yangtools.yang.data.util.AbstractStringInstanceIdentifierCodec;
 import org.opendaylight.yangtools.yang.data.util.DataSchemaContextTree;
 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
@@ -100,6 +101,11 @@ abstract sealed class JSONInstanceIdentifierCodec extends AbstractStringInstance
         return deserialize(str);
     }
 
+    @Override
+    public JSONValue unparseValue(final YangInstanceIdentifier value) {
+        return new JSONValue(serialize(value), Kind.STRING);
+    }
+
     @Override
     public final void writeValue(final JSONValueWriter ctx, final YangInstanceIdentifier value) throws IOException {
         final String str;
diff --git a/codec/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/JSONValue.java b/codec/yang-data-codec-gson/src/main/java/org/opendaylight/yangtools/yang/data/codec/gson/JSONValue.java
new file mode 100644 (file)
index 0000000..d8b83e1
--- /dev/null
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2024 PANTHEON.tech, s.r.o. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.yangtools.yang.data.codec.gson;
+
+import static java.util.Objects.requireNonNull;
+
+import org.eclipse.jdt.annotation.NonNullByDefault;
+
+/**
+ * A serialized JSON string, indicating what kind of value it represents.
+ *
+ * @param rawString unescaped string
+ * @param kind string kind
+ */
+@NonNullByDefault
+public record JSONValue(String rawString, Kind kind) {
+    /**
+     * The kind of a {@link JSONValue}. Indicates the semantics of {@link JSONValue#rawString()}.
+     */
+    public enum Kind {
+        /**
+         * A {@code boolean} value.
+         */
+        BOOLEAN,
+        /**
+         * An {@code empty} value.
+         */
+        EMPTY,
+        /**
+         * A numeric value, excluding {@code int64} and {@code uint64)}.
+         */
+        NUMBER,
+        /**
+         * A string value.
+         */
+        STRING
+    }
+
+    /**
+     * The equivalent on {@link Boolean#FALSE}.
+     */
+    public static final JSONValue FALSE = new JSONValue("false", Kind.BOOLEAN);
+    /**
+     * The equivalent on {@link Boolean#TRUE}.
+     */
+    public static final JSONValue TRUE = new JSONValue("true", Kind.BOOLEAN);
+    /**
+     * The equivalent on {@link org.opendaylight.yangtools.yang.common.Empty#value()}.
+     */
+    public static final JSONValue EMPTY = new JSONValue("[null]", Kind.EMPTY);
+
+    public JSONValue {
+        requireNonNull(rawString);
+        requireNonNull(kind);
+    }
+}
index 3ab79f517ce11e909e6d9cb3fa6880ba2deb5a5f..88204e5404f44cb7e870415f73a2885c158e4dad 100644 (file)
@@ -34,4 +34,9 @@ final class NullJSONCodec implements JSONCodec<Object> {
         // NOOP since codec is unknown.
         LOG.warn("Call of the serializeToWriter method on null codec. No operation performed.");
     }
+
+    @Override
+    public JSONValue unparseValue(final Object value) {
+        throw new UnsupportedOperationException();
+    }
 }
index 6d4a4bf008ed212948e7198221059a3c910a36fe..bc912b91dc5c6349e224835d7e4bd734bff182cd 100644 (file)
@@ -8,6 +8,7 @@
 package org.opendaylight.yangtools.yang.data.codec.gson;
 
 import java.io.IOException;
+import org.opendaylight.yangtools.yang.data.codec.gson.JSONValue.Kind;
 import org.opendaylight.yangtools.yang.data.impl.codec.DataStringCodec;
 
 /**
@@ -20,6 +21,11 @@ final class NumberJSONCodec<T extends Number> extends AbstractJSONCodec<T> {
         super(codec);
     }
 
+    @Override
+    public JSONValue unparseValue(final T value) {
+        return new JSONValue(value.toString(), Kind.NUMBER);
+    }
+
     @Override
     public void writeValue(final JSONValueWriter ctx, final T value) throws IOException {
         ctx.writeNumber(value);
index 9eec9d4038498e88f1b6f9a03b4d827c16dc0daf..5ce1e381d8e4634e47661054506555b75423d410 100644 (file)
@@ -8,6 +8,7 @@
 package org.opendaylight.yangtools.yang.data.codec.gson;
 
 import java.io.IOException;
+import org.opendaylight.yangtools.yang.data.codec.gson.JSONValue.Kind;
 import org.opendaylight.yangtools.yang.data.impl.codec.DataStringCodec;
 
 /**
@@ -20,6 +21,11 @@ final class QuotedJSONCodec<T> extends AbstractJSONCodec<T> {
         super(codec);
     }
 
+    @Override
+    public JSONValue unparseValue(final T value) {
+        return new JSONValue(serialize(value), Kind.STRING);
+    }
+
     @Override
     public void writeValue(final JSONValueWriter ctx, final T value) throws IOException {
         ctx.writeString(serialize(value));
index 8f5b54132f10f1e48bf5ca968f51348713509833..d39ac5ca96738840dbad58603d2bf8c7f8001b71 100644 (file)
@@ -98,21 +98,41 @@ abstract sealed class UnionJSONCodec<T> implements JSONCodec<T> {
     @SuppressWarnings("checkstyle:illegalCatch")
     public final void writeValue(final JSONValueWriter ctx, final T value) throws IOException {
         for (var codec : codecs) {
-            if (!codec.getDataType().isInstance(value)) {
+            if (codec.getDataType().isInstance(value)) {
+                @SuppressWarnings("unchecked")
+                final var objCodec = (JSONCodec<Object>) codec;
+                try {
+                    objCodec.writeValue(ctx, value);
+                    return;
+                } catch (RuntimeException e) {
+                    LOG.debug("Codec {} failed to serialize {}", codec, value, e);
+                }
+            } else {
                 LOG.debug("Codec {} cannot accept input {}, skipping it", codec, value);
-                continue;
             }
+        }
 
-            @SuppressWarnings("unchecked")
-            final var objCodec = (JSONCodec<Object>) codec;
-            try {
-                objCodec.writeValue(ctx, value);
-                return;
-            } catch (RuntimeException e) {
-                LOG.debug("Codec {} failed to serialize {}", codec, value, e);
+        throw new IllegalArgumentException("No codecs could serialize" + value);
+    }
+
+
+    @Override
+    @SuppressWarnings("checkstyle:illegalCatch")
+    public JSONValue unparseValue(final Object value) {
+        for (var codec : codecs) {
+            if (codec.getDataType().isInstance(value)) {
+                @SuppressWarnings("unchecked")
+                final var objCodec = (JSONCodec<Object>) codec;
+                try {
+                    return objCodec.unparseValue(value);
+                } catch (RuntimeException e) {
+                    LOG.debug("Codec {} failed to unparse {}", codec, value, e);
+                }
+            } else {
+                LOG.debug("Codec {} cannot accept input {}, skipping it", codec, value);
             }
         }
 
-        throw new IllegalArgumentException("No codecs could serialize" + value);
+        throw new IllegalArgumentException("No codecs could unparse" + value);
     }
 }