Skip to content

Hash DynamicUnionType by its DataType's NodeId - #1954

Merged
kevinherron merged 1 commit into
eclipse-milo:mainfrom
alxkm:fix/dynamic-union-type-hashcode
Sep 8, 2026
Merged

Hash DynamicUnionType by its DataType's NodeId#1954
kevinherron merged 1 commit into
eclipse-milo:mainfrom
alxkm:fix/dynamic-union-type-hashcode

Conversation

@alxkm

@alxkm alxkm commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

DynamicUnionType.equals compares the DataType by NodeId:

public boolean equals(Object o) {
  ...
  return Objects.equals(dataType.getNodeId(), that.dataType.getNodeId())
      && Objects.equals(value, that.value);
}

public int hashCode() {
  return Objects.hash(dataType, value);   // the DataType object, not its NodeId
}

DataType implementations are not required to define equals/hashCode, and several do not —
BsdDataType and DictionaryDataType both implements DataType without either, so they hash by
identity. Two DynamicUnionType values built from distinct DataType instances describing the
same DataType are then equal with different hashes:

NodeId id = new NodeId(2, "MyUnion");
DataType dt1 = /* MyUnion */;
DataType dt2 = /* MyUnion, a second instance */;

var u1 = new DynamicUnionType(dt1, new UnionValue("foo", 42));
var u2 = new DynamicUnionType(dt2, new UnionValue("foo", 42));

u1.equals(u2);                    // true
u1.hashCode() == u2.hashCode();   // false
map.put(u1, "value"); map.get(u2) // null

The two sibling classes written alongside it already hash the NodeId:

DynamicStructType.hashCode() -> Objects.hash(dataType.getNodeId(), members)
DynamicEnumType.hashCode()   -> Objects.hash(dataType.getNodeId(), name, value)

Scope

Within a single DataTypeTree this does not bite: TypeTree holds a Map<NodeId, Tree<T>>, so
getDataType(id) returns the same instance every time and the hash is stable. It bites when the
instances differ — a rebuilt DataTypeTree, or a DataType constructed separately. Nothing in the
repository puts a DynamicUnionType into a hash-based collection today, so this is a public API
contract fix rather than a fix for a broken internal path.

Change

Hash dataType.getNodeId() instead of dataType, matching equals and the two siblings. When
both values share a DataType instance the hash is unchanged.

Tests

DynamicUnionTypeTest.equalUnionsHashTheSame fails before the change and passes after:

DynamicUnionTypeTest.equalUnionsHashTheSame  expected: <912482254> but was: <-751886955>

It uses the existing AbstractDataType test helper, which defines no equals/hashCode, to build
two distinct DataType instances with the same NodeId.
DynamicUnionTypeTest.unionsWithDifferentValuesAreNotEqual is added as a guard that the change
does not make unrelated unions compare equal.

Verified on JDK 17 with mvn -pl opc-ua-sdk/integration-tests -am verify: all 14 modules pass —
2329 tests, 0 failures, 0 errors — including spotless:check and Checkstyle.


  • You have signed an Eclipse Contributor Agreement and are committing using the same email address
  • Your code contains any tests relevant to the problem you are solving
  • All new and existing tests passed
  • Code follows the style guidelines and Checkstyle passes

DynamicUnionType.equals compares the DataType by NodeId, because DataType
implementations are not required to define equals()/hashCode() and several do
not, but hashCode hashed the DataType object itself. Two DynamicUnionType
values built from distinct DataType instances describing the same DataType are
therefore equal with different hashes, and a lookup for one does not find the
other.

The sibling classes written alongside it already do this: DynamicStructType and
DynamicEnumType both hash dataType.getNodeId().

Hash the NodeId too. Nothing else changes; when both values share a DataType
instance the hash is the same as before.

Signed-off-by: alxkm <alexanderklmn@gmail.com>
@kevinherron
kevinherron merged commit e897d33 into eclipse-milo:main Sep 8, 2026
3 checks passed
@kevinherron kevinherron added this to the 1.1.7 milestone Sep 9, 2026
@alxkm
alxkm deleted the fix/dynamic-union-type-hashcode branch September 9, 2026 13:23
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants