diff --git a/praxiscore-api/src/main/java/org/praxislive/core/Connection.java b/praxiscore-api/src/main/java/org/praxislive/core/Connection.java
index 51750e7c..6372bf8f 100644
--- a/praxiscore-api/src/main/java/org/praxislive/core/Connection.java
+++ b/praxiscore-api/src/main/java/org/praxislive/core/Connection.java
@@ -1,7 +1,7 @@
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
- * Copyright 2023 Neil C Smith.
+ * Copyright 2024 Neil C Smith.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License version 3 only, as
@@ -21,99 +21,102 @@
*/
package org.praxislive.core;
-import org.praxislive.core.protocols.ContainerProtocol;
+import java.util.Optional;
import org.praxislive.core.types.PArray;
import org.praxislive.core.types.PString;
/**
* A type representing a connection between two ports.
*/
-public final class Connection {
-
- private final PArray dataArray;
+public final class Connection extends PArray.ArrayBasedValue {
/**
- * Create a connection reference. The child IDs must be valid according to
- * {@link ComponentAddress#isValidID(java.lang.String)}. The port IDs must
- * be valid according to {@link PortAddress#isValidID(java.lang.String)}.
- *
- * @param child1 ID of first child
- * @param port1 ID of port on first child
- * @param child2 ID of second child
- * @param port2 ID of port on second child
- * @throws IllegalArgumentException if the IDs are not valid
+ * Value type name.
*/
- public Connection(String child1, String port1, String child2, String port2) {
- verifyChildID(child1);
- verifyChildID(child2);
- verifyPortID(port1);
- verifyPortID(port2);
- dataArray = PArray.of(PString.of(child1), PString.of(port1),
- PString.of(child2), PString.of(port2));
+ public static final String TYPE_NAME = "Connection";
+
+ private Connection(PArray data) {
+ super(data);
+ if (data.size() != 4) {
+ throw new IllegalArgumentException("Invalid connection data");
+ }
+ verifyChildID(data.get(0).toString());
+ verifyPortID(data.get(1).toString());
+ verifyChildID(data.get(2).toString());
+ verifyPortID(data.get(3).toString());
}
/**
- * Query the component ID of the first connected component.
+ * Query the component ID of the source component.
*
* @return ID of first child
*/
- public String child1() {
- return dataArray.get(0).toString();
+ public String sourceComponent() {
+ return dataArray().get(0).toString();
}
/**
- * Query the port ID of the connected port on the first component.
+ * Query the port ID of the source port.
*
* @return ID of port on first child
*/
- public String port1() {
- return dataArray.get(1).toString();
+ public String sourcePort() {
+ return dataArray().get(1).toString();
}
/**
- * Query the component ID of the second connected component.
+ * Query the component ID of the target component.
*
* @return ID of the second child
*/
- public String child2() {
- return dataArray.get(2).toString();
+ public String targetComponent() {
+ return dataArray().get(2).toString();
}
/**
- * Query the port ID of the connected port on the second component.
+ * Query the port ID of the target port.
*
* @return ID of port on second child
*/
- public String port2() {
- return dataArray.get(3).toString();
+ public String targetPort() {
+ return dataArray().get(3).toString();
}
/**
- * Access the Connection as the backing PArray data. The data consists of
- * four values, {@code child1 port1 child2 port2}.
- *
- * This is the same format included in the list returned from
- * {@link ContainerProtocol#CONNECTIONS}.
+ * Create a connection reference. The child IDs must be valid according to
+ * {@link ComponentAddress#isValidID(java.lang.String)}. The port IDs must
+ * be valid according to {@link PortAddress#isValidID(java.lang.String)}.
*
- * @return backing data array
+ * @param child1 ID of first child
+ * @param port1 ID of port on first child
+ * @param child2 ID of second child
+ * @param port2 ID of port on second child
+ * @return new connection
+ * @throws IllegalArgumentException if the IDs are not valid
*/
- public PArray dataArray() {
- return dataArray;
- }
-
- @Override
- public String toString() {
- return dataArray.toString();
- }
-
- @Override
- public int hashCode() {
- return dataArray.hashCode();
+ public static Connection of(String child1, String port1, String child2, String port2) {
+ return new Connection(PArray.of(
+ PString.of(child1), PString.of(port1),
+ PString.of(child2), PString.of(port2)
+ ));
}
- @Override
- public boolean equals(Object obj) {
- return obj == this || (obj instanceof Connection c && dataArray.equals(c.dataArray));
+ /**
+ * Coerce the provided value into a Connection if possible.
+ *
+ * @param value value of unknown type
+ * @return connection or empty optional
+ */
+ public static Optional from(Value value) {
+ if (value instanceof Connection connection) {
+ return Optional.of(connection);
+ } else {
+ try {
+ return PArray.from(value).map(Connection::new);
+ } catch (Exception ex) {
+ return Optional.empty();
+ }
+ }
}
private static void verifyChildID(String childID) {
diff --git a/praxiscore-api/src/main/java/org/praxislive/core/Value.java b/praxiscore-api/src/main/java/org/praxislive/core/Value.java
index a469370e..c3c7a723 100644
--- a/praxiscore-api/src/main/java/org/praxislive/core/Value.java
+++ b/praxiscore-api/src/main/java/org/praxislive/core/Value.java
@@ -1,7 +1,7 @@
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
- * Copyright 2022 Neil C Smith.
+ * Copyright 2024 Neil C Smith.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License version 3 only, as
@@ -320,6 +320,7 @@ public static List> listAll() {
types.add(new Type<>(PortAddress.class, PortAddress.TYPE_NAME, PortAddress::from));
types.add(new Type<>(ComponentType.class, ComponentType.TYPE_NAME, ComponentType::from));
+ types.add(new Type<>(Connection.class, Connection.TYPE_NAME, Connection::from));
Map, Type>> typesByClass = new HashMap<>();
Map> typesByName = new HashMap<>();
diff --git a/praxiscore-api/src/main/java/org/praxislive/core/types/PArray.java b/praxiscore-api/src/main/java/org/praxislive/core/types/PArray.java
index e6690fa4..2810ee98 100644
--- a/praxiscore-api/src/main/java/org/praxislive/core/types/PArray.java
+++ b/praxiscore-api/src/main/java/org/praxislive/core/types/PArray.java
@@ -1,7 +1,7 @@
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
- * Copyright 2023 Neil C Smith.
+ * Copyright 2024 Neil C Smith.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License version 3 only, as
@@ -25,6 +25,7 @@
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
+import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collector;
import java.util.stream.Stream;
@@ -349,4 +350,78 @@ public static ArgumentInfo info() {
);
}
+ /**
+ * An abstract superclass for values that are backed solely by a PArray.
+ * Subclassing this type can help with efficient serialization of the
+ * underlying representation. The concrete value type must be able to
+ * construct an equivalent value entirely from the PArray returned from
+ * {@link #dataArray()}.
+ *
+ * The toString, equals, equivalent and hashCode methods are all implemented
+ * based solely on the array data.
+ */
+ public static abstract class ArrayBasedValue extends Value {
+
+ private final PArray data;
+
+ /**
+ * Construct a MapBasedValue using the provided data map.
+ *
+ * @param data data map
+ */
+ protected ArrayBasedValue(PArray data) {
+ this.data = Objects.requireNonNull(data);
+ }
+
+ /**
+ * Access the backing PArray data.
+ *
+ * @return backing array
+ */
+ public final PArray dataArray() {
+ return data;
+ }
+
+ @Override
+ public final String toString() {
+ return data.toString();
+ }
+
+ @Override
+ public final int hashCode() {
+ return data.hashCode();
+ }
+
+ @Override
+ public final boolean equals(Object obj) {
+ if (this == obj) {
+ return true;
+ }
+ if (obj == null) {
+ return false;
+ }
+ if (getClass() != obj.getClass()) {
+ return false;
+ }
+ final ArrayBasedValue other = (ArrayBasedValue) obj;
+ return Objects.equals(this.data, other.data);
+ }
+
+ @Override
+ public final boolean equivalent(Value value) {
+ if (this == value) {
+ return true;
+ }
+ if (value instanceof ArrayBasedValue arrayBased) {
+ return data.equivalent(arrayBased.data);
+ } else {
+ return PMap.from(value)
+ .map(data::equivalent)
+ .orElse(false);
+ }
+
+ }
+
+ }
+
}
diff --git a/praxiscore-base/src/main/java/org/praxislive/base/AbstractContainer.java b/praxiscore-base/src/main/java/org/praxislive/base/AbstractContainer.java
index 9bfcc89e..f19d3b25 100644
--- a/praxiscore-base/src/main/java/org/praxislive/base/AbstractContainer.java
+++ b/praxiscore-base/src/main/java/org/praxislive/base/AbstractContainer.java
@@ -63,7 +63,7 @@ public abstract class AbstractContainer extends AbstractComponent implements Con
private final static System.Logger LOG = System.getLogger(AbstractContainer.class.getName());
private final Map childMap;
- private final Set connections;
+ private final Set connections;
protected AbstractContainer() {
childMap = new LinkedHashMap<>();
@@ -124,9 +124,7 @@ protected final void writeChildren(TreeWriter writer) {
}
protected final void writeConnections(TreeWriter writer) {
- connections.forEach(c -> writer.writeConnection(
- new Connection(c.get(0).toString(), c.get(1).toString(),
- c.get(2).toString(), c.get(3).toString())));
+ connections.forEach(writer::writeConnection);
}
protected void addChild(String id, Component child) throws VetoException {
@@ -173,34 +171,26 @@ protected String getChildID(Component child) {
protected void connect(String component1, String port1, String component2, String port2)
throws PortConnectionException {
- handleConnection(true,
- PString.of(component1),
- PString.of(port1),
- PString.of(component2),
- PString.of(port2));
+ handleConnection(true, component1, port1, component2, port2);
}
protected void disconnect(String component1, String port1, String component2, String port2) {
try {
- handleConnection(false,
- PString.of(component1),
- PString.of(port1),
- PString.of(component2),
- PString.of(port2));
+ handleConnection(false, component1, port1, component2, port2);
} catch (PortConnectionException ex) {
LOG.log(System.Logger.Level.ERROR, "", ex);
}
}
- private void handleConnection(boolean connect, PString c1id, PString p1id, PString c2id, PString p2id)
+ private void handleConnection(boolean connect, String component1, String port1, String component2, String port2)
throws PortConnectionException {
try {
- Component c1 = getChild(c1id.toString());
- final Port p1 = c1.getPort(p1id.toString());
- Component c2 = getChild(c2id.toString());
- final Port p2 = c2.getPort(p2id.toString());
+ Component c1 = getChild(component1);
+ final Port p1 = c1.getPort(port1);
+ Component c2 = getChild(component2);
+ final Port p2 = c2.getPort(port2);
- final PArray connection = PArray.of(c1id, p1id, c2id, p2id);
+ final Connection connection = Connection.of(component1, port1, component2, port2);
if (connect) {
p1.connect(p2);
@@ -214,8 +204,8 @@ private void handleConnection(boolean connect, PString c1id, PString p1id, PStri
}
} catch (Exception ex) {
LOG.log(System.Logger.Level.DEBUG, "Can't connect ports.", ex);
- throw new PortConnectionException("Can't connect " + c1id + "!" + p1id
- + " to " + c2id + "!" + p2id);
+ throw new PortConnectionException("Can't connect " + component1 + "!" + port1
+ + " to " + component2 + "!" + port2);
}
}
@@ -277,10 +267,10 @@ protected class ConnectControl implements Control {
@Override
public void call(Call call, PacketRouter router) throws Exception {
handleConnection(true,
- PString.from(call.args().get(0)).orElseThrow(),
- PString.from(call.args().get(1)).orElseThrow(),
- PString.from(call.args().get(2)).orElseThrow(),
- PString.from(call.args().get(3)).orElseThrow());
+ call.args().get(0).toString(),
+ call.args().get(1).toString(),
+ call.args().get(2).toString(),
+ call.args().get(3).toString());
router.route(call.reply());
}
@@ -291,10 +281,10 @@ protected class DisconnectControl implements Control {
@Override
public void call(Call call, PacketRouter router) throws Exception {
handleConnection(false,
- PString.from(call.args().get(0)).orElseThrow(),
- PString.from(call.args().get(1)).orElseThrow(),
- PString.from(call.args().get(2)).orElseThrow(),
- PString.from(call.args().get(3)).orElseThrow());
+ call.args().get(0).toString(),
+ call.args().get(1).toString(),
+ call.args().get(2).toString(),
+ call.args().get(3).toString());
router.route(call.reply());
}
@@ -314,9 +304,9 @@ private class ConnectionListener implements PortListener {
Port p1;
Port p2;
- PArray connection;
+ Connection connection;
- private ConnectionListener(Port p1, Port p2, PArray connection) {
+ private ConnectionListener(Port p1, Port p2, Connection connection) {
this.p1 = p1;
this.p2 = p2;
this.connection = connection;
diff --git a/praxiscore-base/src/test/java/org/praxislive/base/MapTreeWriterTest.java b/praxiscore-base/src/test/java/org/praxislive/base/MapTreeWriterTest.java
index d578648d..acb5dff7 100644
--- a/praxiscore-base/src/test/java/org/praxislive/base/MapTreeWriterTest.java
+++ b/praxiscore-base/src/test/java/org/praxislive/base/MapTreeWriterTest.java
@@ -63,8 +63,8 @@ public void testWriter() {
.writeInfo(ComponentProtocol.API_INFO)
.writeProperty("p1", PNumber.of(42));
})
- .writeConnection(new Connection("child1", "out", "child2", "in"))
- .writeConnection(new Connection("child2", "ready", "child1", "trigger"))
+ .writeConnection(Connection.of("child1", "out", "child2", "in"))
+ .writeConnection(Connection.of("child2", "ready", "child1", "trigger"))
.build();
if (VERBOSE) {
System.out.println("Writer output\n=================");
@@ -94,8 +94,8 @@ private PMap buildComparison() {
.build()
);
builder.put("%connections", PArray.of(
- new Connection("child1", "out", "child2", "in").dataArray(),
- new Connection("child2", "ready", "child1", "trigger").dataArray()
+ Connection.of("child1", "out", "child2", "in").dataArray(),
+ Connection.of("child2", "ready", "child1", "trigger").dataArray()
));
return builder.build();
}
diff --git a/praxiscore-project/src/main/java/org/praxislive/project/GraphElement.java b/praxiscore-project/src/main/java/org/praxislive/project/GraphElement.java
index 76026e86..561d7efc 100644
--- a/praxiscore-project/src/main/java/org/praxislive/project/GraphElement.java
+++ b/praxiscore-project/src/main/java/org/praxislive/project/GraphElement.java
@@ -420,68 +420,46 @@ public String toString() {
public static final class Connection extends GraphElement {
- private final String sourceComponent;
- private final String sourcePort;
- private final String targetComponent;
- private final String targetPort;
+ private final org.praxislive.core.Connection value;
private Connection(String sourceComponent, String sourcePort, String targetComponent, String targetPort) {
- if (!ComponentAddress.isValidID(Objects.requireNonNull(sourceComponent))) {
- throw new IllegalArgumentException(sourceComponent + " is not a valid component ID");
- }
- if (!ComponentAddress.isValidID(Objects.requireNonNull(targetComponent))) {
- throw new IllegalArgumentException(targetComponent + " is not a valid component ID");
- }
- if (!PortAddress.isValidID(Objects.requireNonNull(sourcePort))) {
- throw new IllegalArgumentException(sourcePort + " is not a valid port ID");
- }
- if (!PortAddress.isValidID(Objects.requireNonNull(targetPort))) {
- throw new IllegalArgumentException(targetPort + " is not a valid port ID");
- }
-
- this.sourceComponent = sourceComponent;
- this.sourcePort = sourcePort;
- this.targetComponent = targetComponent;
- this.targetPort = targetPort;
+ value = org.praxislive.core.Connection.of(sourceComponent, sourcePort, targetComponent, targetPort);
}
public String sourceComponent() {
- return sourceComponent;
+ return value.sourceComponent();
}
public String sourcePort() {
- return sourcePort;
+ return value.sourcePort();
}
public String targetComponent() {
- return targetComponent;
+ return value.targetComponent();
}
public String targetPort() {
- return targetPort;
+ return value.targetPort();
}
@Override
public int hashCode() {
- return Objects.hash(sourceComponent, sourcePort, targetComponent, targetPort);
+ return Objects.hash(value);
}
@Override
public boolean equals(Object obj) {
return obj == this
- || (obj instanceof Connection c
- && Objects.equals(this.sourceComponent, c.sourceComponent)
- && Objects.equals(this.sourcePort, c.sourcePort)
- && Objects.equals(this.targetComponent, c.targetComponent)
- && Objects.equals(this.targetPort, c.targetPort));
+ || obj instanceof Connection c
+ && Objects.equals(this.value, c.value);
}
@Override
public String toString() {
- return "Connection{" + "sourceComponent=" + sourceComponent
- + ", sourcePort=" + sourcePort
- + ", targetComponent=" + targetComponent
- + ", targetPort=" + targetPort + "}";
+ return "Connection{" + "sourceComponent=" + sourceComponent()
+ + ", sourcePort=" + sourcePort()
+ + ", targetComponent=" + targetComponent()
+ + ", targetPort=" + targetPort() + "}";
}
}
diff --git a/praxiscore-project/src/test/java/org/praxislive/project/GraphModelTest.java b/praxiscore-project/src/test/java/org/praxislive/project/GraphModelTest.java
index efc9d28a..431f5790 100644
--- a/praxiscore-project/src/test/java/org/praxislive/project/GraphModelTest.java
+++ b/praxiscore-project/src/test/java/org/praxislive/project/GraphModelTest.java
@@ -517,9 +517,9 @@ private static PMap serializedGraph() {
.build()
);
builder.put("%connections", PArray.of(
- new Connection("child1", "out", "child2", "in").dataArray(),
- new Connection("child1", "out", "container", "in").dataArray(),
- new Connection("container", "ready", "child1", "trigger").dataArray()
+ Connection.of("child1", "out", "child2", "in"),
+ Connection.of("child1", "out", "container", "in"),
+ Connection.of("container", "ready", "child1", "trigger")
));
return builder.build();
}