From 2e73a409787d1f8e5226f1c5be4452d1c8c3ae42 Mon Sep 17 00:00:00 2001 From: Neil C Smith Date: Fri, 13 Oct 2023 14:20:33 +0100 Subject: [PATCH] Update IonCodec to support map based values. --- .../java/org/praxislive/hub/net/IonCodec.java | 67 +++++++++++++++---- .../org/praxislive/hub/net/IonCodecTest.java | 54 ++++++++++++++- 2 files changed, 104 insertions(+), 17 deletions(-) diff --git a/praxiscore-hub-net/src/main/java/org/praxislive/hub/net/IonCodec.java b/praxiscore-hub-net/src/main/java/org/praxislive/hub/net/IonCodec.java index e44d1bb8..9423cf61 100644 --- a/praxiscore-hub-net/src/main/java/org/praxislive/hub/net/IonCodec.java +++ b/praxiscore-hub-net/src/main/java/org/praxislive/hub/net/IonCodec.java @@ -56,8 +56,8 @@ class IonCodec { private static final String ERROR = "Error"; private static final String SYSTEM = "System"; - private static final String TYPE_MAP = Value.Type.of(PMap.class).name(); - private static final String TYPE_ARRAY = Value.Type.of(PArray.class).name(); + private static final String TYPE_MAP = PMap.TYPE_NAME; + private static final String TYPE_ARRAY = PArray.TYPE_NAME; private static final String FIELD_MATCH_ID = "matchID"; private static final String FIELD_TO = "to"; @@ -126,11 +126,16 @@ private Message readMessage(IonReader reader) throws IOException { } try { return switch (annotations[0]) { - case SEND -> readSendMessage(reader); - case SERVICE -> readServiceMessage(reader); - case REPLY -> readReplyMessage(reader); - case ERROR -> readErrorMessage(reader); - case SYSTEM -> readSystemMessage(reader); + case SEND -> + readSendMessage(reader); + case SERVICE -> + readServiceMessage(reader); + case REPLY -> + readReplyMessage(reader); + case ERROR -> + readErrorMessage(reader); + case SYSTEM -> + readSystemMessage(reader); default -> throw new IOException("Unknown message type"); }; @@ -301,9 +306,9 @@ private Value readValue(IonReader reader) throws Exception { case INT -> PNumber.of(reader.intValue()); case LIST -> { - var annotations = reader.getTypeAnnotations(); - if (annotations.length > 0 && TYPE_MAP.equals(annotations[0])) { - yield readMap(reader); + String[] annotations = reader.getTypeAnnotations(); + if (isMap(annotations)) { + yield readMapValue(annotations, reader); } else { yield PArray.of(readValues(reader)); } @@ -313,6 +318,38 @@ private Value readValue(IonReader reader) throws Exception { }; } + private boolean isMap(String[] annotations) { + for (String annotation : annotations) { + if (PMap.TYPE_NAME.equals(annotation)) { + return true; + } + } + return false; + } + + private Value readMapValue(String[] annotations, IonReader reader) throws Exception { + Value.Type type = null; + if (annotations.length > 1) { + for (String annotation : annotations) { + if (PMap.TYPE_NAME.equals(annotation)) { + continue; + } + var vt = Value.Type.fromName(annotation).orElse(null); + if (vt != null && PMap.MapBasedValue.class.isAssignableFrom(vt.asClass())) { + type = vt; + break; + } + } + } + PMap map = readMap(reader); + if (type != null) { + Value v = type.converter().apply(map).orElse(null); + return v == null ? map : v; + } else { + return map; + } + } + private PMap readMap(IonReader reader) throws Exception { if (reader.getType() != IonType.LIST) { throw new IllegalArgumentException("Not a list"); @@ -340,14 +377,16 @@ private void writeValues(IonWriter writer, List values) throws IOExceptio private void writeValue(IonWriter writer, Value value) throws IOException { if (value instanceof PNumber n) { writeNumber(writer, n); - } else if (value instanceof PMap m) { - writeMap(writer, m); } else if (value instanceof PArray a) { writeArray(writer, a); } else if (value instanceof PBytes b) { writeBytes(writer, b); } else if (value instanceof PBoolean b) { writer.writeBool(b.value()); + } else if (value instanceof PMap m) { + writeMap(writer, m, TYPE_MAP); + } else if (value instanceof PMap.MapBasedValue v) { + writeMap(writer, v.dataMap(), v.type().name(), TYPE_MAP); } else { writer.writeString(value.toString()); } @@ -361,8 +400,8 @@ private void writeNumber(IonWriter writer, PNumber number) throws IOException { } } - private void writeMap(IonWriter writer, PMap map) throws IOException { - writer.setTypeAnnotations(TYPE_MAP); + private void writeMap(IonWriter writer, PMap map, String ... annotations) throws IOException { + writer.setTypeAnnotations(annotations); writer.stepIn(IonType.LIST); for (var key : map.keys()) { writer.writeString(key); diff --git a/praxiscore-hub-net/src/test/java/org/praxislive/hub/net/IonCodecTest.java b/praxiscore-hub-net/src/test/java/org/praxislive/hub/net/IonCodecTest.java index 05d16a35..fa556d98 100644 --- a/praxiscore-hub-net/src/test/java/org/praxislive/hub/net/IonCodecTest.java +++ b/praxiscore-hub-net/src/test/java/org/praxislive/hub/net/IonCodecTest.java @@ -26,9 +26,16 @@ import com.amazon.ion.system.IonTextWriterBuilder; import java.net.URI; import java.util.List; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInfo; +import org.praxislive.core.ComponentInfo; import org.praxislive.core.ControlAddress; +import org.praxislive.core.ControlPort; +import org.praxislive.core.Info; import org.praxislive.core.Value; +import org.praxislive.core.protocols.ComponentProtocol; import org.praxislive.core.types.PArray; import org.praxislive.core.types.PBoolean; import org.praxislive.core.types.PBytes; @@ -45,12 +52,27 @@ */ public class IonCodecTest { - private static final boolean DEBUG_INFO = false; + private static final boolean VERBOSE = Boolean.getBoolean("praxis.test.verbose"); private static final PBytes bytes = PBytes.valueOf(new byte[]{ (byte) 0xCA, (byte) 0xFE, (byte) 0xBA, (byte) 0xBE }); + @BeforeEach + public void beforeEach(TestInfo info) { + if (VERBOSE) { + System.out.println("START TEST : " + info.getDisplayName()); + } + } + + @AfterEach + public void afterEach(TestInfo info) { + if (VERBOSE) { + System.out.println("END TEST : " + info.getDisplayName()); + System.out.println("====================================="); + } + } + @Test public void testSendMessage() throws Exception { var matchID = 1234; @@ -157,12 +179,38 @@ public void testMultiMessage() throws Exception { assertEquals(msg3, msgList.get(2)); assertEquals(msg4, msgList.get(3)); assertEquals(msg5, msgList.get(4)); - + + } + + @Test + public void testMapBasedValues() throws Exception { + int matchID = -987654321; + var customProp = PMap.of("flag", true, "data", bytes); + var info = Info.component() + .merge(ComponentProtocol.API_INFO) + .port("in", Info.port().input(ControlPort.class).build()) + .port("out", Info.port().output(ControlPort.class).build()) + .property("custom", customProp) + .build(); + var msg = new Message.Reply(matchID, List.of(info)); + var msgList = roundTrip(List.of(msg)); + assertEquals(1, msgList.size()); + var decoded = (Message.Reply) msgList.get(0); + assertEquals(matchID, decoded.matchID()); + var decodedInfo = decoded.args().get(0); + assertTrue(info.equivalent(decodedInfo)); + assertTrue(decodedInfo instanceof ComponentInfo); + assertEquals(info, decodedInfo); + var byteProp = ComponentInfo.from(decodedInfo) + .flatMap(i -> PMap.from(i.properties().get("custom"))) + .map(m -> m.get("data")) + .orElseThrow(); + assertEquals(bytes, byteProp); } private List roundTrip(List messages) throws Exception { byte[] data = IonCodec.getDefault().writeMessages(messages); - if (DEBUG_INFO) { + if (VERBOSE) { var sb = new StringBuilder(); sb.append("\nMESSAGE\n=======\n"); var system = IonSystemBuilder.standard().build();