Skip to content

Commit

Permalink
~ enum/struct Matrix encode/decode functions
Browse files Browse the repository at this point in the history
  • Loading branch information
kevinherron committed Sep 16, 2022
1 parent 2b1ccd5 commit 586f320
Show file tree
Hide file tree
Showing 16 changed files with 627 additions and 58 deletions.
Expand Up @@ -45,7 +45,6 @@
import org.eclipse.milo.opcua.stack.core.types.enumerated.StructureType;
import org.eclipse.milo.opcua.stack.core.types.structured.StructureDefinition;
import org.eclipse.milo.opcua.stack.core.types.structured.StructureField;
import org.eclipse.milo.opcua.stack.core.util.ArrayUtil;
import org.jetbrains.annotations.NotNull;

public class DynamicStructCodec extends GenericDataTypeCodec<DynamicStruct> {
Expand Down Expand Up @@ -295,16 +294,11 @@ private Object decodeFieldValue(UaDecoder decoder, StructureField field) {
TypeHint typeHint = (TypeHint) hint;

switch (typeHint) {
// TODO do we need decodeEnumMatrix and decodeStructMatrix on UaDecoder?
// I'm not sure this is going to work as is, at least with non-binary encodings.

case ENUM: {
// TODO should the field value be multidimensional DynamicEnum or left as Matrix?

Matrix m = decoder.decodeMatrix(fieldName, BuiltinDataType.Int32);
Matrix matrix = decoder.decodeEnumMatrix(fieldName);

if (m.getElements() instanceof Integer[]) {
Integer[] enumValues = (Integer[]) m.getElements();
if (matrix.getElements() instanceof Integer[]) {
Integer[] enumValues = (Integer[]) matrix.getElements();

Function<Integer, DynamicEnum> factory = enumFactories.get(dataTypeId);
assert factory != null;
Expand All @@ -313,27 +307,14 @@ private Object decodeFieldValue(UaDecoder decoder, StructureField field) {
.map(factory)
.toArray(DynamicEnum[]::new);

value = ArrayUtil.unflatten(dynamicEnums, m.getDimensions());
value = new Matrix(dynamicEnums, matrix.getDimensions());
} else {
value = null;
}
break;
}
case STRUCT:
Matrix m = decoder.decodeMatrix(fieldName, BuiltinDataType.ExtensionObject);

if (m.getElements() instanceof ExtensionObject[]) {
ExtensionObject[] xos = (ExtensionObject[]) m.getElements();

// TODO Obviously broken, need EncodingContext
DynamicStruct[] dynamicStructs = Arrays.stream(xos)
.map(xo -> (DynamicStruct) xo.decodeOrNull(null))
.toArray(DynamicStruct[]::new);

value = ArrayUtil.unflatten(dynamicStructs, m.getDimensions());
} else {
value = null;
}
value = decoder.decodeStructMatrix(fieldName, dataTypeId);
break;
default:
throw new RuntimeException("codecType: " + typeHint);
Expand Down Expand Up @@ -390,8 +371,25 @@ private void encodeFieldValue(UaEncoder encoder, StructureField field, Object va
}
}
} else if (valueRank > 1) {
// TODO special matrix encoding for multi-dimensional array structure fields
throw new RuntimeException("not implemented");
Matrix matrix = (Matrix) value;

Object hint = fieldHints.get(field);
if (hint instanceof BuiltinDataType) {
encoder.encodeMatrix(fieldName, matrix);
} else {
TypeHint typeHint = (TypeHint) hint;

switch (typeHint) {
case ENUM:
encoder.encodeEnumMatrix(fieldName, matrix);
break;
case STRUCT:
encoder.encodeStructMatrix(fieldName, matrix, dataTypeId);
break;
default:
throw new RuntimeException("codecType: " + typeHint);
}
}
} else {
throw new UaSerializationException(
StatusCodes.Bad_EncodingError, "illegal ValueRank: " + valueRank);
Expand Down
Expand Up @@ -1589,20 +1589,67 @@ public Matrix decodeMatrix(String field, BuiltinDataType builtinDataType) throws
}
}

Object nestedArray = decodeNestedMultiDimensionalArrayValue(builtinDataType.getTypeId());
Object nestedArray = decodeNestedMultiDimensionalArrayBuiltinValue(builtinDataType.getTypeId());

return new Matrix(nestedArray);
} catch (IOException e) {
throw new UaSerializationException(StatusCodes.Bad_DecodingError, e);
}
}

private Object decodeNestedMultiDimensionalArrayValue(int typeId) throws IOException {
@Override
public Matrix decodeEnumMatrix(String field) throws UaSerializationException {
return decodeMatrix(field, BuiltinDataType.Int32);
}

@Override
public Matrix decodeStructMatrix(String field, NodeId dataTypeId) throws UaSerializationException {
DataTypeCodec codec = context.getDataTypeManager()
.getCodec(OpcUaDefaultJsonEncoding.ENCODING_NAME, dataTypeId);

if (codec != null) {
try {
if (field != null) {
String nextName = nextName();
if (!field.equals(nextName)) {
throw new UaSerializationException(
StatusCodes.Bad_DecodingError,
String.format("readStruct: %s != %s", field, nextName)
);
}
}

Object nestedArray = decodeNestedMultiDimensionalArrayStructValue(codec);

return new Matrix(nestedArray);
} catch (IOException e) {
throw new UaSerializationException(StatusCodes.Bad_DecodingError, e);
}
} else {
throw new UaSerializationException(
StatusCodes.Bad_DecodingError,
"decodeStructMatrix: no codec registered: " + dataTypeId
);
}
}

@Override
public Matrix decodeStructMatrix(String field, ExpandedNodeId dataTypeId) throws UaSerializationException {
NodeId localDataTypeId = dataTypeId.toNodeId(context.getNamespaceTable())
.orElseThrow(() -> new UaSerializationException(
StatusCodes.Bad_DecodingError,
"decodeStructMatrix: namespace not registered: " + dataTypeId
));

return decodeStructMatrix(field, localDataTypeId);
}

private Object decodeNestedMultiDimensionalArrayBuiltinValue(int typeId) throws IOException {
if (jsonReader.peek() == JsonToken.BEGIN_ARRAY) {
jsonReader.beginArray();
List<Object> elements = new ArrayList<>();
while (jsonReader.peek() != JsonToken.END_ARRAY) {
elements.add(decodeNestedMultiDimensionalArrayValue(typeId));
elements.add(decodeNestedMultiDimensionalArrayBuiltinValue(typeId));
}
jsonReader.endArray();

Expand All @@ -1616,6 +1663,25 @@ private Object decodeNestedMultiDimensionalArrayValue(int typeId) throws IOExcep
}
}

private Object decodeNestedMultiDimensionalArrayStructValue(DataTypeCodec codec) throws IOException {
if (jsonReader.peek() == JsonToken.BEGIN_ARRAY) {
jsonReader.beginArray();
List<Object> elements = new ArrayList<>();
while (jsonReader.peek() != JsonToken.END_ARRAY) {
elements.add(decodeNestedMultiDimensionalArrayStructValue(codec));
}
jsonReader.endArray();

Object array = Array.newInstance(elements.get(0).getClass(), elements.size());
for (int i = 0; i < elements.size(); i++) {
Array.set(array, i, elements.get(i));
}
return array;
} else {
return decodeStruct(null, codec);
}
}

/**
* This ugly hack is so `readArray` can "push back" the next name it reads if it doesn't match
* the expected field name, because that probably means it was omitted due to being null.
Expand Down
Expand Up @@ -1269,12 +1269,16 @@ public void encodeStructArray(String field, Object[] value, NodeId dataTypeId) t
}

@Override
public void encodeStructArray(String field, Object[] value, ExpandedNodeId dataTypeId) throws
UaSerializationException {
public void encodeStructArray(
String field,
Object[] value,
ExpandedNodeId dataTypeId
) throws UaSerializationException {

NodeId localDataTypeId = dataTypeId.toNodeId(encodingContext.getNamespaceTable())
.orElseThrow(() -> new UaSerializationException(
StatusCodes.Bad_EncodingError,
"no codec registered: " + dataTypeId
"encodeStructArray: namespace not registered: " + dataTypeId
));

encodeStructArray(field, value, localDataTypeId);
Expand Down Expand Up @@ -1340,6 +1344,79 @@ public void encodeMatrix(String field, Matrix value) throws UaSerializationExcep
}
}

@Override
public void encodeEnumMatrix(String field, Matrix value) throws UaSerializationException {
try {
EncodingContext context = contextPeek();
if (!reversible || context == OpcUaJsonEncoder.EncodingContext.BUILTIN || (value != null && value.isNotNull())) {
if (field != null) {
jsonWriter.name(field);
}

Object flatArray = value.getElements();
if (flatArray == null) {
try {
jsonWriter.nullValue();
} catch (IOException e) {
throw new UaSerializationException(StatusCodes.Bad_EncodingError, e);
}
} else {
int[] dimensions = value.getDimensions();

try {
encodeFlatEnumArrayAsNested((UaEnumeratedType[]) flatArray, dimensions, 0);
} catch (IOException e) {
throw new UaSerializationException(StatusCodes.Bad_EncodingError, e);
}
}
}
} catch (IOException e) {
throw new UaSerializationException(StatusCodes.Bad_EncodingError, e);
}
}

@Override
public void encodeStructMatrix(String field, Matrix value, NodeId dataTypeId) throws UaSerializationException {
try {
EncodingContext context = contextPeek();
if (!reversible || context == OpcUaJsonEncoder.EncodingContext.BUILTIN || (value != null && value.isNotNull())) {
if (field != null) {
jsonWriter.name(field);
}

Object flatArray = value.getElements();
if (flatArray == null) {
try {
jsonWriter.nullValue();
} catch (IOException e) {
throw new UaSerializationException(StatusCodes.Bad_EncodingError, e);
}
} else {
int[] dimensions = value.getDimensions();

try {
encodeFlatStructArrayAsNested(flatArray, dataTypeId, dimensions, 0);
} catch (IOException e) {
throw new UaSerializationException(StatusCodes.Bad_EncodingError, e);
}
}
}
} catch (IOException e) {
throw new UaSerializationException(StatusCodes.Bad_EncodingError, e);
}
}

@Override
public void encodeStructMatrix(String field, Matrix value, ExpandedNodeId dataTypeId) throws UaSerializationException {
NodeId localDataTypeId = dataTypeId.toNodeId(encodingContext.getNamespaceTable())
.orElseThrow(() -> new UaSerializationException(
StatusCodes.Bad_EncodingError,
"encodeStructArray: namespace not registered: " + dataTypeId
));

encodeStructMatrix(field, value, localDataTypeId);
}

private void encodeFlatArrayAsNested(
Object value,
int[] dimensions,
Expand All @@ -1364,6 +1441,53 @@ private void encodeFlatArrayAsNested(
}
}

private void encodeFlatEnumArrayAsNested(
UaEnumeratedType[] value,
int[] dimensions,
int offset
) throws IOException {

if (dimensions.length == 1) {
jsonWriter.beginArray();
for (int i = 0; i < dimensions[0]; i++) {
Object e = Array.get(value, offset + i);
encodeEnum(null, (UaEnumeratedType) e);
}
jsonWriter.endArray();
} else {
jsonWriter.beginArray();
int[] tail = Arrays.copyOfRange(dimensions, 1, dimensions.length);
for (int i = 0; i < dimensions[0]; i++) {
encodeFlatEnumArrayAsNested(value, tail, offset + i * length(tail));
}
jsonWriter.endArray();
}
}

private void encodeFlatStructArrayAsNested(
Object value,
NodeId dataTypeId,
int[] dimensions,
int offset
) throws IOException {

if (dimensions.length == 1) {
jsonWriter.beginArray();
for (int i = 0; i < dimensions[0]; i++) {
Object e = Array.get(value, offset + i);
encodeStruct(null, e, dataTypeId);
}
jsonWriter.endArray();
} else {
jsonWriter.beginArray();
int[] tail = Arrays.copyOfRange(dimensions, 1, dimensions.length);
for (int i = 0; i < dimensions[0]; i++) {
encodeFlatStructArrayAsNested(value, dataTypeId, tail, offset + i * length(tail));
}
jsonWriter.endArray();
}
}

private static int length(int[] tail) {
int product = 1;
for (int aTail : tail) {
Expand Down

0 comments on commit 586f320

Please sign in to comment.