feat(client-java): add writeArrayElements default-fill helper and document semantics
This commit is contained in:
@@ -124,6 +124,29 @@ the unchanged elements included. For example, to change 2 elements of a
|
|||||||
the 2 new ones). Sending only the 2 changed values overwrites the attribute
|
the 2 new ones). Sending only the 2 changed values overwrites the attribute
|
||||||
with a 2-element array.
|
with a 2-element array.
|
||||||
|
|
||||||
|
When only a few indices need changing and the rest should be reset to the
|
||||||
|
element type's default, use `writeArrayElements` instead of building the full
|
||||||
|
array manually:
|
||||||
|
|
||||||
|
```java
|
||||||
|
session.writeArrayElements(
|
||||||
|
serverHandle, itemHandle,
|
||||||
|
MxDataType.MX_DATA_TYPE_INTEGER,
|
||||||
|
20, // totalLength
|
||||||
|
Map.of(
|
||||||
|
2, MxValues.int32Value(42),
|
||||||
|
7, MxValues.int32Value(99)),
|
||||||
|
userId);
|
||||||
|
```
|
||||||
|
|
||||||
|
The gateway expands the sparse descriptor into a full `totalLength`-element
|
||||||
|
array before forwarding to the worker. Indices not listed in the map are
|
||||||
|
written as the element type's default — this is a **reset**, not a preserve;
|
||||||
|
current values at those positions are discarded. `totalLength` is required and
|
||||||
|
must match the declared length of the array attribute. Bare-name array items
|
||||||
|
(`Area001.Pump001.Speed`) are auto-normalized to the `[]` form at `AddItem` so
|
||||||
|
the array attribute accepts the write.
|
||||||
|
|
||||||
## Galaxy Repository Browse
|
## Galaxy Repository Browse
|
||||||
|
|
||||||
The Galaxy Repository service is a separate metadata-only gRPC service exposed
|
The Galaxy Repository service is a separate metadata-only gRPC service exposed
|
||||||
|
|||||||
+9506
-436
File diff suppressed because it is too large
Load Diff
+53
@@ -4,7 +4,9 @@ import java.security.SecureRandom;
|
|||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.util.HexFormat;
|
import java.util.HexFormat;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import java.util.TreeMap;
|
||||||
import mxaccess_gateway.v1.MxaccessGateway.AddItem2Command;
|
import mxaccess_gateway.v1.MxaccessGateway.AddItem2Command;
|
||||||
import mxaccess_gateway.v1.MxaccessGateway.AddItemBulkCommand;
|
import mxaccess_gateway.v1.MxaccessGateway.AddItemBulkCommand;
|
||||||
import mxaccess_gateway.v1.MxaccessGateway.AddItemCommand;
|
import mxaccess_gateway.v1.MxaccessGateway.AddItemCommand;
|
||||||
@@ -18,6 +20,9 @@ import mxaccess_gateway.v1.MxaccessGateway.MxCommand;
|
|||||||
import mxaccess_gateway.v1.MxaccessGateway.MxCommandKind;
|
import mxaccess_gateway.v1.MxaccessGateway.MxCommandKind;
|
||||||
import mxaccess_gateway.v1.MxaccessGateway.MxCommandReply;
|
import mxaccess_gateway.v1.MxaccessGateway.MxCommandReply;
|
||||||
import mxaccess_gateway.v1.MxaccessGateway.MxCommandRequest;
|
import mxaccess_gateway.v1.MxaccessGateway.MxCommandRequest;
|
||||||
|
import mxaccess_gateway.v1.MxaccessGateway.MxDataType;
|
||||||
|
import mxaccess_gateway.v1.MxaccessGateway.MxSparseArray;
|
||||||
|
import mxaccess_gateway.v1.MxaccessGateway.MxSparseElement;
|
||||||
import mxaccess_gateway.v1.MxaccessGateway.MxValue;
|
import mxaccess_gateway.v1.MxaccessGateway.MxValue;
|
||||||
import mxaccess_gateway.v1.MxaccessGateway.OpenSessionReply;
|
import mxaccess_gateway.v1.MxaccessGateway.OpenSessionReply;
|
||||||
import mxaccess_gateway.v1.MxaccessGateway.ReadBulkCommand;
|
import mxaccess_gateway.v1.MxaccessGateway.ReadBulkCommand;
|
||||||
@@ -603,6 +608,54 @@ public final class MxGatewaySession implements AutoCloseable {
|
|||||||
.build());
|
.build());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Writes a subset of an array's elements using MXAccess {@code Write}, building a
|
||||||
|
* write-only {@link MxSparseArray} value that the gateway expands into a full,
|
||||||
|
* default-filled array before forwarding to the worker.
|
||||||
|
*
|
||||||
|
* <p><strong>Default-fill semantics:</strong> only the indices supplied in
|
||||||
|
* {@code elements} are written; every unmentioned index is <em>reset</em> to the
|
||||||
|
* element type's default (for example {@code 0}, {@code false}, or an empty string),
|
||||||
|
* <em>not</em> preserved from the array's current contents. Use a full
|
||||||
|
* {@link MxValue} array write when you need to keep existing element values.
|
||||||
|
*
|
||||||
|
* <p>{@code totalLength} is required and defines the length of the expanded array;
|
||||||
|
* supplied indices must be within {@code [0, totalLength)}. Elements are iterated in
|
||||||
|
* ascending index order so the produced command is deterministic.
|
||||||
|
*
|
||||||
|
* @param serverHandle the {@code ServerHandle} owning the item
|
||||||
|
* @param itemHandle the {@code ItemHandle} to write
|
||||||
|
* @param elementDataType the {@link MxDataType} of the array's elements
|
||||||
|
* @param totalLength the total length of the expanded array
|
||||||
|
* @param elements the indices to write mapped to their scalar values; unmentioned
|
||||||
|
* indices are reset to the element type default
|
||||||
|
* @param userId the MXAccess user id used for security checks
|
||||||
|
* @throws MxGatewayException on transport or protocol failure
|
||||||
|
*/
|
||||||
|
public void writeArrayElements(
|
||||||
|
int serverHandle,
|
||||||
|
int itemHandle,
|
||||||
|
MxDataType elementDataType,
|
||||||
|
int totalLength,
|
||||||
|
Map<Integer, MxValue> elements,
|
||||||
|
int userId) {
|
||||||
|
Objects.requireNonNull(elementDataType, "elementDataType");
|
||||||
|
Objects.requireNonNull(elements, "elements");
|
||||||
|
MxSparseArray.Builder sparse = MxSparseArray.newBuilder()
|
||||||
|
.setElementDataType(elementDataType)
|
||||||
|
.setTotalLength(totalLength);
|
||||||
|
// Iterate in ascending index order so the built command is deterministic.
|
||||||
|
for (Map.Entry<Integer, MxValue> entry : new TreeMap<>(elements).entrySet()) {
|
||||||
|
sparse.addElements(MxSparseElement.newBuilder()
|
||||||
|
.setIndex(entry.getKey())
|
||||||
|
.setValue(Objects.requireNonNull(entry.getValue(), "elements value")));
|
||||||
|
}
|
||||||
|
MxValue value = MxValue.newBuilder()
|
||||||
|
.setSparseArrayValue(sparse)
|
||||||
|
.build();
|
||||||
|
writeRaw(serverHandle, itemHandle, value, userId);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Invokes MXAccess {@code Write2}, which carries an explicit timestamp.
|
* Invokes MXAccess {@code Write2}, which carries an explicit timestamp.
|
||||||
*
|
*
|
||||||
|
|||||||
+3
@@ -153,6 +153,9 @@ public final class MxValues {
|
|||||||
case TIMESTAMP_VALUE -> instant(value.getTimestampValue());
|
case TIMESTAMP_VALUE -> instant(value.getTimestampValue());
|
||||||
case ARRAY_VALUE -> nativeArray(value.getArrayValue());
|
case ARRAY_VALUE -> nativeArray(value.getArrayValue());
|
||||||
case RAW_VALUE -> value.getRawValue().toByteArray();
|
case RAW_VALUE -> value.getRawValue().toByteArray();
|
||||||
|
// Write-only sparse descriptor: never produced by a read/decoded
|
||||||
|
// value, so it has no native representation.
|
||||||
|
case SPARSE_ARRAY_VALUE -> null;
|
||||||
case KIND_NOT_SET -> null;
|
case KIND_NOT_SET -> null;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
+55
@@ -19,6 +19,7 @@ import io.grpc.stub.ServerCallStreamObserver;
|
|||||||
import io.grpc.stub.StreamObserver;
|
import io.grpc.stub.StreamObserver;
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.concurrent.CountDownLatch;
|
import java.util.concurrent.CountDownLatch;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
@@ -36,7 +37,10 @@ import mxaccess_gateway.v1.MxaccessGateway.CloseSessionRequest;
|
|||||||
import mxaccess_gateway.v1.MxaccessGateway.MxCommandKind;
|
import mxaccess_gateway.v1.MxaccessGateway.MxCommandKind;
|
||||||
import mxaccess_gateway.v1.MxaccessGateway.MxCommandReply;
|
import mxaccess_gateway.v1.MxaccessGateway.MxCommandReply;
|
||||||
import mxaccess_gateway.v1.MxaccessGateway.MxCommandRequest;
|
import mxaccess_gateway.v1.MxaccessGateway.MxCommandRequest;
|
||||||
|
import mxaccess_gateway.v1.MxaccessGateway.MxDataType;
|
||||||
import mxaccess_gateway.v1.MxaccessGateway.MxEvent;
|
import mxaccess_gateway.v1.MxaccessGateway.MxEvent;
|
||||||
|
import mxaccess_gateway.v1.MxaccessGateway.MxSparseElement;
|
||||||
|
import mxaccess_gateway.v1.MxaccessGateway.MxValue;
|
||||||
import mxaccess_gateway.v1.MxaccessGateway.OpenSessionReply;
|
import mxaccess_gateway.v1.MxaccessGateway.OpenSessionReply;
|
||||||
import mxaccess_gateway.v1.MxaccessGateway.OpenSessionRequest;
|
import mxaccess_gateway.v1.MxaccessGateway.OpenSessionRequest;
|
||||||
import mxaccess_gateway.v1.MxaccessGateway.ProtocolStatus;
|
import mxaccess_gateway.v1.MxaccessGateway.ProtocolStatus;
|
||||||
@@ -396,6 +400,57 @@ final class MxGatewayClientSessionTests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void writeArrayElementsBuildsSparseArrayWriteCommand() throws Exception {
|
||||||
|
AtomicReference<MxCommandRequest> commandRequest = new AtomicReference<>();
|
||||||
|
TestGatewayService service = new TestGatewayService() {
|
||||||
|
@Override
|
||||||
|
public void invoke(MxCommandRequest request, StreamObserver<MxCommandReply> responseObserver) {
|
||||||
|
commandRequest.set(request);
|
||||||
|
responseObserver.onNext(MxCommandReply.newBuilder()
|
||||||
|
.setSessionId(request.getSessionId())
|
||||||
|
.setKind(request.getCommand().getKind())
|
||||||
|
.setProtocolStatus(ok())
|
||||||
|
.build());
|
||||||
|
responseObserver.onCompleted();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
try (InProcessGateway gateway = InProcessGateway.start(service, new AtomicReference<>());
|
||||||
|
MxGatewayClient client = gateway.client("", Duration.ofSeconds(5))) {
|
||||||
|
MxGatewaySession session = MxGatewaySession.forSessionId(client, "sparse-session");
|
||||||
|
|
||||||
|
// Supply indices out of order to prove deterministic ascending iteration.
|
||||||
|
Map<Integer, MxValue> elements = Map.of(
|
||||||
|
3, MxValues.int32Value(99),
|
||||||
|
1, MxValues.int32Value(7));
|
||||||
|
|
||||||
|
session.writeArrayElements(12, 34, MxDataType.MX_DATA_TYPE_INTEGER, 5, elements, 56);
|
||||||
|
|
||||||
|
MxCommandRequest request = commandRequest.get();
|
||||||
|
assertNotNull(request);
|
||||||
|
assertEquals(MxCommandKind.MX_COMMAND_KIND_WRITE, request.getCommand().getKind());
|
||||||
|
assertEquals(12, request.getCommand().getWrite().getServerHandle());
|
||||||
|
assertEquals(34, request.getCommand().getWrite().getItemHandle());
|
||||||
|
assertEquals(56, request.getCommand().getWrite().getUserId());
|
||||||
|
|
||||||
|
MxValue written = request.getCommand().getWrite().getValue();
|
||||||
|
assertEquals(MxValue.KindCase.SPARSE_ARRAY_VALUE, written.getKindCase());
|
||||||
|
assertEquals(5, written.getSparseArrayValue().getTotalLength());
|
||||||
|
assertEquals(
|
||||||
|
MxDataType.MX_DATA_TYPE_INTEGER,
|
||||||
|
written.getSparseArrayValue().getElementDataType());
|
||||||
|
|
||||||
|
List<MxSparseElement> sparse = written.getSparseArrayValue().getElementsList();
|
||||||
|
assertEquals(2, sparse.size());
|
||||||
|
// Ascending index order is guaranteed by the helper.
|
||||||
|
assertEquals(1, sparse.get(0).getIndex());
|
||||||
|
assertEquals(7, sparse.get(0).getValue().getInt32Value());
|
||||||
|
assertEquals(3, sparse.get(1).getIndex());
|
||||||
|
assertEquals(99, sparse.get(1).getValue().getInt32Value());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static ProtocolStatus ok() {
|
private static ProtocolStatus ok() {
|
||||||
return ProtocolStatus.newBuilder()
|
return ProtocolStatus.newBuilder()
|
||||||
.setCode(ProtocolStatusCode.PROTOCOL_STATUS_CODE_OK)
|
.setCode(ProtocolStatusCode.PROTOCOL_STATUS_CODE_OK)
|
||||||
|
|||||||
Reference in New Issue
Block a user