Skip to content

Commit

Permalink
improve getComponent/Property/MethodByName
Browse files Browse the repository at this point in the history
  • Loading branch information
erossignon committed Mar 2, 2021
1 parent a5855dd commit 319556f
Show file tree
Hide file tree
Showing 12 changed files with 210 additions and 62 deletions.
20 changes: 14 additions & 6 deletions packages/node-opcua-address-space/source/address_space_ts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ import {
LocalizedTextLike,
NodeClass,
QualifiedName,
QualifiedNameLike
QualifiedNameLike,
QualifiedNameOptions
} from "node-opcua-data-model";
import { DataValue, DataValueOptions, DataValueOptionsT, DataValueT } from "node-opcua-data-value";
import { PreciseClock } from "node-opcua-date-time";
Expand Down Expand Up @@ -198,7 +199,8 @@ export declare class BaseNode extends EventEmitter {

public allReferences(): Reference[];

public getChildByName(browseName: string | QualifiedName): BaseNode | null;
public getChildByName(browseName: QualifiedNameLike): BaseNode | null;
public getChildByName(browseName: string, namespaceIndex?: number): BaseNode | null;

/**
* this methods propagates the forward references to the pointed node
Expand Down Expand Up @@ -274,9 +276,11 @@ export interface VariableAttributes {
}

export interface IPropertyAndComponentHolder {
getComponentByName(componentName: QualifiedNameLike, namespaceIndex?: number): UAObject | UAVariable | null;
getComponentByName(componentName: QualifiedNameOptions): UAObject | UAVariable | null;
getComponentByName(componentName: string, namespaceIndex?: number): UAObject | UAVariable | null;

getPropertyByName(browseName: string, namespaceIndex?: number): UAVariable | null;
getPropertyByName(propertyName: QualifiedNameOptions): UAVariable | null;
getPropertyByName(propertyName: string, namespaceIndex?: number): UAVariable | null;

getAggregates(): BaseNode[];

Expand Down Expand Up @@ -772,12 +776,15 @@ export interface UAObject extends BaseNode, EventRaiser, IPropertyAndComponentHo
readonly hasMethods: boolean;

//
getFolderElementByName(browseName: QualifiedNameOptions): BaseNode | null;
getFolderElementByName(browseName: string, namespaceIndex?: number): BaseNode | null;

// Method accessor
getMethodById(nodeId: NodeId): UAMethod | null;

getMethodByName(methodName: string): UAMethod | null;
getMethodByName(methodName: QualifiedNameOptions): UAMethod | null;
getMethodByName(methodName: string, namespaceIndex?: number): UAMethod | null;
getMethodByName(methodName: QualifiedNameLike, namespaceIndex?: number): UAMethod | null;

getMethods(): UAMethod[];

Expand Down Expand Up @@ -971,7 +978,8 @@ export declare interface UAObjectType extends BaseNode, IPropertyAndComponentHol
// Method accessor
getMethodById(nodeId: NodeId): UAMethod | null;

getMethodByName(methodName: string): UAMethod | null;
getMethodByName(methodName: QualifiedNameOptions): UAMethod | null;
getMethodByName(methodName: string, namespaceIndex?: number): UAMethod | null;

getMethods(): UAMethod[];
}
Expand Down
40 changes: 27 additions & 13 deletions packages/node-opcua-address-space/src/base_node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,11 @@ import {
makeNodeClassMask,
NodeClass,
QualifiedName,
QualifiedNameLike
QualifiedNameLike,
QualifiedNameOptions
} from "node-opcua-data-model";
import { DataValue } from "node-opcua-data-value";
import { dumpIf } from "node-opcua-debug";
import { dumpIf, make_warningLog } from "node-opcua-debug";
import { makeNodeId, NodeId, NodeIdLike, resolveNodeId, sameNodeId } from "node-opcua-nodeid";
import { NumericRange } from "node-opcua-numeric-range";
import { ReferenceDescription } from "node-opcua-service-browse";
Expand All @@ -30,6 +31,7 @@ import { BrowseDescription, RelativePathElement } from "node-opcua-types";
import * as utils from "node-opcua-utils";
import { lowerFirstLetter } from "node-opcua-utils";
import { DataType } from "node-opcua-variant";
import { StringDecoder } from "string_decoder";

import {
AddReferenceOpts,
Expand Down Expand Up @@ -73,6 +75,7 @@ import { UAReferenceType } from "./ua_reference_type";
// tslint:disable:no-console

const doDebug = false;
const warningLog = make_warningLog(__filename);

function defaultBrowseFilterFunc(context?: SessionContext): boolean {
return true;
Expand Down Expand Up @@ -535,14 +538,16 @@ export class BaseNode extends EventEmitter implements BaseNodePublic {
/**
* retrieve a component by name
*/
public getComponentByName(browseName: QualifiedNameOptions): UAVariablePublic | UAObjectPublic | null;
public getComponentByName(browseName: string, namespaceIndex?: number): UAVariablePublic | UAObjectPublic | null;
public getComponentByName(browseName: QualifiedNameLike, namespaceIndex?: number): UAVariablePublic | UAObjectPublic | null {
const components = this.getComponents();
const select = _filter_by_browse_name(components, browseName, namespaceIndex);
assert(select.length <= 1, "BaseNode#getComponentByName found duplicated reference");
if (select.length === 1) {
const component = select[0];
if (component.nodeClass === NodeClass.Method) {
console.log("please use getMethodByName to retrieve a method");
warningLog("please use getMethodByName to retrieve a method");
return null;
}
assert(component.nodeClass === NodeClass.Variable || component.nodeClass === NodeClass.Object);
Expand All @@ -555,21 +560,24 @@ export class BaseNode extends EventEmitter implements BaseNodePublic {
/**
* retrieve a property by name
*/
public getPropertyByName(browseName: string, namespaceIndex?: number): UAVariablePublic | null {
public getPropertyByName(browseName: QualifiedNameOptions): UAVariablePublic | null;
public getPropertyByName(browseName: string, namespaceIndex?: number): UAVariablePublic | null;
public getPropertyByName(browseName: QualifiedNameLike, namespaceIndex?: number): UAVariablePublic | null {
const properties = this.getProperties();
const select = _filter_by_browse_name(properties, browseName, namespaceIndex);
assert(select.length <= 1, "BaseNode#getPropertyByName found duplicated reference");
if (select.length === 1 && select[0].nodeClass !== NodeClass.Variable) {
throw new Error("Expecting a property to be of TypeVariable");
throw new Error("Expecting a property to be of nodeClass==NodeClass.Variable");
}
return select.length === 1 ? ((select[0] as any) as UAVariablePublic) : null;
}

/**
* retrieve a folder by name
* retrieve a folder element by name
*/
public getFolderElementByName(browseName: string, namespaceIndex?: number): BaseNode | null {
assert(typeof browseName === "string");
public getFolderElementByName(browseName: QualifiedNameOptions): BaseNode | null;
public getFolderElementByName(browseName: string, namespaceIndex?: number): BaseNode | null;
public getFolderElementByName(browseName: QualifiedNameLike, namespaceIndex?: number): BaseNode | null {
const elements = this.getFolderElements();
const select = _filter_by_browse_name(elements, browseName, namespaceIndex);
return select.length === 1 ? select[0] : null;
Expand Down Expand Up @@ -608,7 +616,7 @@ export class BaseNode extends EventEmitter implements BaseNodePublic {
return found || null;
}

public getMethodByName(browseName: string, namespaceIndex?: number): UAMethodPublic | null {
public getMethodByName(browseName: QualifiedNameLike, namespaceIndex?: number): UAMethodPublic | null {
const methods = this.getMethods();
const select = _filter_by_browse_name(methods, browseName, namespaceIndex);
assert(select.length <= 1, "BaseNode#getMethodByName found duplicated reference");
Expand Down Expand Up @@ -1070,7 +1078,9 @@ export class BaseNode extends EventEmitter implements BaseNodePublic {
// return _private._cache._HasChildReferences;
}

public getChildByName(browseName: string | QualifiedName): BaseNode | null {
public getChildByName(browseName: QualifiedNameOptions): BaseNode | null;
public getChildByName(browseName: string, namespaceIndex?: number): BaseNode | null;
public getChildByName(browseName: QualifiedNameLike, namespaceIndex?: number): BaseNode | null {
// Attention: getChild doesn't care about namespace on browseName
// !!!!
if (browseName instanceof QualifiedName) {
Expand All @@ -1091,7 +1101,7 @@ export class BaseNode extends EventEmitter implements BaseNodePublic {
_private._cache._childByNameMap[child.browseName.name!.toString()] = child;
}
}
const ret = _private._cache._childByNameMap[browseName] || null;
const ret = _private._cache._childByNameMap[browseName.toString()] || null;
return ret;
}

Expand Down Expand Up @@ -1367,11 +1377,15 @@ function _filter_by_browse_name<T extends BaseNodePublic>(
namespaceIndex?: number
): T[] {
let select: T[] = [];
if (namespaceIndex === null || namespaceIndex === undefined) {
if ((namespaceIndex === null || namespaceIndex === undefined) && (typeof browseName === "string")) {
select = components.filter((c: T) => c.browseName.name!.toString() === browseName);
if (select && select.length > 1) {
warningLog("Multiple children exist with name ", browseName, " please specify a namespace index");
}
} else {
const _browseName = coerceQualifiedName(typeof browseName=== "string" ? { name: browseName, namespaceIndex} : browseName)!;
select = components.filter(
(c: T) => c.browseName.name!.toString() === browseName && c.browseName.namespaceIndex === namespaceIndex
(c: T) => c.browseName.name === _browseName.name && c.browseName.namespaceIndex === _browseName.namespaceIndex
);
}
return select;
Expand Down
8 changes: 5 additions & 3 deletions packages/node-opcua-address-space/src/ua_object.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import * as chalk from "chalk";

import { assert } from "node-opcua-assert";
import { isValidByte } from "node-opcua-basic-types";
import { NodeClass } from "node-opcua-data-model";
import { NodeClass, QualifiedNameLike, QualifiedNameOptions } from "node-opcua-data-model";
import { AttributeIds } from "node-opcua-data-model";
import { DataValue, DataValueLike } from "node-opcua-data-value";
import { getCurrentClock } from "node-opcua-date-time";
Expand Down Expand Up @@ -78,8 +78,10 @@ export class UAObject extends BaseNode implements UAObjectPublic {
return this.getMethods().length > 0;
}

public getMethodByName(methodName: string): UAMethodPublic | null {
return super.getMethodByName(methodName);
public getMethodByName(methodName: QualifiedNameOptions): UAMethodPublic | null;
public getMethodByName(methodName: string, namespaceIndex?: number): UAMethodPublic | null;
public getMethodByName(methodName: QualifiedNameLike, namespaceIndex?: number): UAMethodPublic | null {
return super.getMethodByName(methodName, namespaceIndex);
}

public getMethods(): UAMethodPublic[] {
Expand Down
122 changes: 122 additions & 0 deletions packages/node-opcua-address-space/test/test_get_xxx_by_name.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import { AddressSpace, UAObject } from "..";
import { getMiniAddressSpace } from "../testHelpers";
import * as should from "should";

describe("test component/property/method and method accessors", () => {
let addressSpace: AddressSpace;
let uaObject: UAObject;
before(async () => {
addressSpace = await getMiniAddressSpace();
const namespace1 = addressSpace.registerNamespace("urn:namespace1");
const namespace2 = addressSpace.registerNamespace("urn:namespace2");
const namespace3 = addressSpace.registerNamespace("urn:namespace3");

uaObject = namespace1.addObject({
browseName: "SomeObject"
});
const cu1 = namespace1.addObject({
browseName: "SomeUniqueComponent",
componentOf: uaObject
});
const c1 = namespace1.addObject({
browseName: { namespaceIndex: namespace2.index, name: "SomeComponent" },
componentOf: uaObject
});
const c12 = namespace1.addObject({
browseName: { namespaceIndex: namespace3.index, name: "SomeComponent" },
componentOf: uaObject
});
const pu1 = namespace1.addVariable({
browseName: "SomeUniqueProperty",
dataType: "Double",
propertyOf: uaObject
});
const p1 = namespace1.addVariable({
browseName: { namespaceIndex: namespace2.index, name: "SomeProperty" },
dataType: "Double",
propertyOf: uaObject
});
const p2 = namespace1.addVariable({
browseName: { namespaceIndex: namespace3.index, name: "SomeProperty" },
dataType: "Double",
propertyOf: uaObject
});

const mu1 = namespace1.addMethod(uaObject, {
browseName: "SomeUniqueMethod"
});
const m1 = namespace1.addMethod(uaObject, {
browseName: { namespaceIndex: namespace2.index, name: "SomeMethod" }
});
const m2 = namespace1.addMethod(uaObject, {
browseName: { namespaceIndex: namespace3.index, name: "SomeMethod" }
});
});
after(() => {
addressSpace.dispose();
});

it("getComponentByName form 0", () => {
const t1 = uaObject.getComponentByName("SomeUniqueComponent");
should.exist(t1);
t1?.browseName.toJSON().should.eql({ namespaceIndex: 2, name: "SomeUniqueComponent" });
});
it("getComponentByName form 0 - should throw if multiple component exist with the same name (regardless of namespace)", () => {
should.throws(() => {
const t1 = uaObject.getComponentByName("SomeComponent");
}, "it should throw because 2 components exists with name SomeProperty in two different namespace => we need to provide a namespace ");
});
it("getComponentByName form 1", () => {
const t1 = uaObject.getComponentByName("SomeComponent", 3);
should.exist(t1);
t1?.browseName.toJSON().should.eql({ namespaceIndex: 3, name: "SomeComponent" });
});
it("getComponentByName form 3", () => {
const t1 = uaObject.getComponentByName({ namespaceIndex: 3, name: "SomeComponent" });
should.exist(t1);
t1?.browseName.toJSON().should.eql({ namespaceIndex: 3, name: "SomeComponent" });
});

it("getPropertyByName form 0", () => {
const t1 = uaObject.getPropertyByName("SomeUniqueProperty");
should.exist(t1);
t1?.browseName.toJSON().should.eql({ namespaceIndex: 2, name: "SomeUniqueProperty" });
});
it("getPropertyByName form 0 - should throw if multiple property exist with the same name (regardless of namespace)", () => {
should.throws(() => {
const t1 = uaObject.getPropertyByName("SomeProperty");
}, "it should throw because 2 components exists with name SomeProperty in two different namespace => we need to provide a namespace ");
});
it("getPropertyByName form 1", () => {
const t1 = uaObject.getPropertyByName("SomeProperty", 3);
should.exist(t1);
t1?.browseName.toJSON().should.eql({ namespaceIndex: 3, name: "SomeProperty" });
});
it("getPropertyByName form 3", () => {
const t1 = uaObject.getPropertyByName({ namespaceIndex: 3, name: "SomeProperty" });
should.exist(t1);
t1?.browseName.toJSON().should.eql({ namespaceIndex: 3, name: "SomeProperty" });
});

// --------------------------
it("getMethodByName form 0", () => {
const t1 = uaObject.getMethodByName("SomeUniqueMethod");
should.exist(t1);
t1?.browseName.toJSON().should.eql({ namespaceIndex: 2, name: "SomeUniqueMethod" });
});
it("getMethodByName form 0 - should throw if multiple Method exist with the same name (regardless of namespace)", () => {
should.throws(() => {
const t1 = uaObject.getMethodByName("SomeMethod");
}, "it should throw because 2 components exists with name SomeMethod in two different namespace => we need to provide a namespace ");
});
it("getMethodByName form 1", () => {
const t1 = uaObject.getMethodByName("SomeMethod", 3);
should.exist(t1);
t1?.browseName.toJSON().should.eql({ namespaceIndex: 3, name: "SomeMethod" });
});
it("getMethodByName form 3", () => {
const t1 = uaObject.getMethodByName({ namespaceIndex: 3, name: "SomeMethod" });
should.exist(t1);
t1?.browseName.toJSON().should.eql({ namespaceIndex: 3, name: "SomeMethod" });
});
});
2 changes: 1 addition & 1 deletion packages/node-opcua-address-space/test/test_issue_846.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import "should";
import { AddressSpace, UAObject, SessionContext } from "..";
import { generateAddressSpace } from "../nodeJS";
import { nodesets } from "node-opcua-nodesets";
import { UAVariable } from "../dist/src/ua_variable";
import { UAVariable } from "..";
import { DataType, Variant, VariantArrayType } from "node-opcua-variant";
import { AttributeIds } from "node-opcua-data-model";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,10 @@ describe("Multi-Dimensional Array", () => {
arrayDimensions: defaultMatrixValue.dimensions,
value: defaultMatrixValue
});
matrixVariable.arrayDimensions.should.eql([4, 5]);
matrixVariable.arrayDimensions!.should.eql([4, 5]);
const dataValueCheck = matrixVariable.readValue();
dataValueCheck.value.arrayType.should.eql(VariantArrayType.Matrix);
dataValueCheck.value.dimensions.should.eql([4, 5]);
dataValueCheck.value.dimensions!.should.eql([4, 5]);

arrayVariable = namespace1.addVariable({
browseName: "ArrayVariable",
Expand Down Expand Up @@ -78,7 +78,7 @@ describe("Multi-Dimensional Array", () => {

const dataValueCheck = matrixVariable.readValue();
dataValueCheck.value.arrayType.should.eql(VariantArrayType.Matrix);
dataValueCheck.value.dimensions.should.eql([4, 5]);
dataValueCheck.value.dimensions!.should.eql([4, 5]);
dataValueCheck.value.value.should.eql(
// prettier-ignore
new Float64Array([
Expand Down Expand Up @@ -110,7 +110,7 @@ describe("Multi-Dimensional Array", () => {

const dataValueCheck = matrixVariable.readValue();
dataValueCheck.value.arrayType.should.eql(VariantArrayType.Matrix);
dataValueCheck.value.dimensions.should.eql([4, 5]);
dataValueCheck.value.dimensions!.should.eql([4, 5]);
dataValueCheck.value.value.should.eql(
// prettier-ignore
new Float64Array([
Expand Down Expand Up @@ -143,7 +143,7 @@ describe("Multi-Dimensional Array", () => {

const dataValueCheck = matrixVariable.readValue();
dataValueCheck.value.arrayType.should.eql(VariantArrayType.Matrix);
dataValueCheck.value.dimensions.should.eql([4, 5]);
dataValueCheck.value.dimensions!.should.eql([4, 5]);
dataValueCheck.value.value.should.eql(
// prettier-ignore
new Float64Array([
Expand Down Expand Up @@ -175,7 +175,7 @@ describe("Multi-Dimensional Array", () => {

const dataValueCheck = matrixVariable.readValue();
dataValueCheck.value.arrayType.should.eql(VariantArrayType.Matrix);
dataValueCheck.value.dimensions.should.eql([4, 5]);
dataValueCheck.value.dimensions!.should.eql([4, 5]);
dataValueCheck.value.value.should.eql(
// prettier-ignore
new Float64Array([
Expand Down Expand Up @@ -204,7 +204,7 @@ describe("Multi-Dimensional Array", () => {

const dataValueCheck = matrixVariable.readValue();
dataValueCheck.value.arrayType.should.eql(VariantArrayType.Matrix);
dataValueCheck.value.dimensions.should.eql([4, 5]);
dataValueCheck.value.dimensions!.should.eql([4, 5]);
dataValueCheck.value.value.should.eql(
// prettier-ignore
new Float64Array([
Expand Down

0 comments on commit 319556f

Please sign in to comment.