"use strict";
/**
* @module opcua.address_space.types
*/
const assert = require("node-opcua-assert").assert;
const _ = require("underscore");
const util = require("util");
const BinaryStreamSizeCalculator = require("node-opcua-binary-stream").BinaryStreamSizeCalculator;
const hexDump = require("node-opcua-debug").hexDump;
const utils = require("node-opcua-utils");
const getFactory = require("./factories_factories").getFactory;
const _defaultTypeMap = require("./factories_builtin_types")._defaultTypeMap;
const get_base_schema = require("./factories_schema_helpers").get_base_schema;
/**
* @class BaseUAObject
* @constructor
*/
function BaseUAObject() {
}
/**
* Encode the object to the binary stream.
* @class BaseUAObject
* @method encode
* @param stream {BinaryStream}
*/
BaseUAObject.prototype.encode = function (stream) {
assert(stream !== null);
};
/**
* Decode the object from the binary stream.
* @class BaseUAObject
* @method decode
* @param stream {BinaryStream}
*/
BaseUAObject.prototype.decode = function (stream) {
assert(stream !== null);
};
/**
* Calculate the required size to store this object in a binary stream.
* @method binaryStoreSize
* @return {Number}
*/
BaseUAObject.prototype.binaryStoreSize = function () {
const stream = new BinaryStreamSizeCalculator();
this.encode(stream);
return stream.length;
};
/**
* @method toString
* @return {String}
*/
BaseUAObject.prototype.toString = function () {
const self = this;
if (self._schema && self._schema.hasOwnProperty("toString")) {
return self._schema.toString.apply(self, arguments);
} else {
if (!self.explore) {
console.log(util.inspect(self));
return Object.prototype.toString.apply(self,arguments);
}
return self.explore.apply(self,arguments);
}
};
/**
*
* verify that all object attributes values are valid according to schema
* @method isValid
* @return {Boolean}
*/
BaseUAObject.prototype.isValid = function () {
assert(this._schema);
if (this._schema.isValid) {
return this._schema.isValid(this);
} else {
return true;
}
};
function _decode_member_(value, field, stream, options) {
const tracer = options.tracer;
const cursor_before = stream.length;
const fieldType = field.fieldType;
if (field.category === "basic") {
value = field.schema.decode(stream);
tracer.trace("member", options.name, value, cursor_before, stream.length, fieldType);
} else if (field.category === "enumeration") {
value = field.schema.decode(stream);
tracer.trace("member", options.name, value, cursor_before, stream.length, fieldType);
} else {
assert(field.category === "complex");
assert(_.isFunction(field.schema));
const Constructor = field.schema;
value = new Constructor();
value.decode_debug(stream, options);
}
return value;
}
/**
* @method decode_debug
*
*/
BaseUAObject.prototype.decode_debug = function (stream, options) {
const tracer = options.tracer;
const schema = this._schema;
tracer.trace("start", options.name + "(" + schema.name + ")", stream.length, stream.length);
const self = this;
for (const field of schema.fields) {
const value = self[field.name];
if (field.isArray) {
const cursor_before = stream.length;
let nb = stream.readUInt32();
if (nb === 0xFFFFFFFF) {
nb = 0;
}
options.name = field.name + [];
tracer.trace("start_array", field.name, nb, cursor_before, stream.length);
for (let i = 0; i < nb; i++) {
tracer.trace("start_element", field.name, i);
options.name = "element #" + i;
_decode_member_(value, field, stream, options);
tracer.trace("end_element", field.name, i);
}
tracer.trace("end_array", field.name, stream.length - 4);
} else {
options.name = field.name;
_decode_member_(value, field, stream, options);
}
}
tracer.trace("end", schema.name, stream.length, stream.length);
};
function r(str) {
return (str + " ").substr(0, 30);
}
function apply_on_all_schema_fields(self, schema, data, functor , args) {
assert(schema);
const fields = schema.fields;
let field;
let i;
const n = fields.length;
for (i = 0; i < n; i++) {
field = fields[i];
functor(self, field, data , args);
}
}
const _nb_elements = process.env.ARRAYLENGTH ? parseInt(process.env.ARRAYLENGTH) : 10;
function _array_ellypsis(value) {
if (!value) {
return "null []";
} else {
if (value.length===0) {
return "[ /* empty*/ ]";
}
assert(_.isArray(value));
const v = [];
const m = Math.min(_nb_elements, value.length);
for (let i = 0; i < m; i++) {
const element = value[i];
v.push( !utils.isNullOrUndefined(element) ? element.toString() : null);
}
return "[ " + v.join(",") + ( value.length > 10 ? " ... " : "") + "] (l=" + value.length + ")";
}
}
function _exploreObject(self, field, data, args) {
if (!self) {
return;
}
assert(self);
let fieldType = field.fieldType;
const fieldName = field.name;
const category = field.category;
const padding = data.padding;
let value = self[fieldName];
let str;
const fieldName_f = r(padding + fieldName, 30).yellow;
const fieldType_f = ("/* " + r(fieldType, 10) + ( field.isArray ? "[]" : " ") + " */").cyan;
// compact version of very usual objects
if (fieldType === "QualifiedName" && !field.isArray && value) {
value = value.toString() || "<null>";
str = fieldName_f + " " + fieldType_f + ": " + value.toString().green;
data.lines.push(str);
return;
}
if (fieldType === "LocalizedText" && !field.isArray && value) {
value = value.toString() || "<null>";
str = fieldName_f + " " + fieldType_f + ": " + value.toString().green;
data.lines.push(str);
return;
}
function _dump_simple_value(self, field, data, value, fieldType) {
let str = "";
if (value instanceof Buffer) {
const _hexDump = hexDump(value);
data.lines.push(fieldName_f + " " + fieldType_f);
data.lines.push("BUFFER{" + _hexDump + "}");
} else {
if (field.isArray) {
str = fieldName_f + " " + fieldType_f + ": " + _array_ellypsis(value,field);
} else {
if (fieldType === "IntegerId" || fieldType === "UInt32") {
value = "" + value + " 0x" + value.toString(16);
} else if (fieldType === "DateTime" || fieldType === "UtcTime") {
value = (value && value.toISOString) ? value.toISOString() : value;
} else if (typeof value === "object" && value !== null && value !== undefined) {
value = value.toString.apply(value,args);
}
str = fieldName_f + " " + fieldType_f + ": " + ((value === null || value === undefined) ? "null".blue.bold : value.toString());
}
data.lines.push(str);
}
}
function _dump_complex_value(self,field,data,value,fieldType) {
if (field.subtype) {
// this is a synonymous
fieldType = field.subType;
_dump_simple_value(self, field, data, value, fieldType);
} else {
field.fieldTypeConstructor = field.fieldTypeConstructor || getFactory(fieldType);
const fieldTypeConstructor = field.fieldTypeConstructor;
const _new_desc = fieldTypeConstructor.prototype._schema;
if (field.isArray) {
if (value === null) {
data.lines.push(fieldName_f + " " + fieldType_f + ": null []");
} else if (value.length === 0) {
data.lines.push(fieldName_f + " " + fieldType_f + ": [ /* empty */ ]");
} else {
data.lines.push(fieldName_f + " " + fieldType_f + ": [");
const m = Math.min(_nb_elements, value.length);
for (let i = 0; i < m; i++) {
const element = value[i];
data.lines.push(padding + " { " + ("/*" + i + "*/").cyan);
const data1 = {padding: padding + " ", lines: []};
apply_on_all_schema_fields(element, _new_desc, data1, _exploreObject , args);
data.lines = data.lines.concat(data1.lines);
data.lines.push(padding + " }" + ((i === value.length - 1) ? "" : ","));
}
if (m < value.length) {
data.lines.push(padding + " ..... ( " + value.length + " elements )");
}
data.lines.push(padding + "]");
}
} else {
data.lines.push(fieldName_f + " " + fieldType_f + ": {");
const data1 = {padding: padding + " ", lines: []};
apply_on_all_schema_fields(value, _new_desc, data1, _exploreObject,args);
data.lines = data.lines.concat(data1.lines);
data.lines.push(padding + "}");
}
}
}
switch (category) {
case "enumeration":
str = fieldName_f + " " + fieldType_f + ": " + value.key + " ( " + value.value + ")";
data.lines.push(str);
break;
case "basic":
_dump_simple_value(self, field, data, value, fieldType);
break;
case "complex":
_dump_complex_value(self, field, data, value, fieldType);
break;
default:
throw new Error("internal error: unknown kind_of_field " + category);
}
}
BaseUAObject.prototype.explore = function () {
const self = this;
const data = {padding: " ", lines: []};
data.lines.push("{" + (" /*" + this._schema.name + "*/").cyan);
apply_on_all_schema_fields(self, self._schema, data, _exploreObject,arguments);
data.lines.push("};");
return data.lines.join("\n");
};
function _visit_schema_chain(self, schema, options, func, extra_data) {
assert(_.isFunction(func));
// apply also construct to baseType schema first
const base_schema = get_base_schema(schema);
if (base_schema) {
_visit_schema_chain(self, base_schema, options, func, extra_data);
}
func.call(self, schema, options, extra_data);
}
function jsonify(t,f,field,value) {
if (_.isFunction(field.toJSON)) {
return field.toJSON(value);
} else if (t && t.toJSON) {
return t.toJSON(value);
} else if (value.toJSON) {
return value.toJSON();
} else {
return f;
}
}
function _JSONify(schema, options) {
/* jshint validthis: true */
const self = this;
for (const field of schema.fields) {
const f = self[field.name];
if (f === null || f === undefined) {
continue;
}
const t = _defaultTypeMap[field.fieldType];
if (field.isArray) {
options[field.name] = f.map(value => jsonify(t,value,field,value));
} else {
options[field.name] = jsonify(t,f,field,f);
}
}
}
BaseUAObject.prototype.toJSON = function () {
const self = this;
assert(this._schema);
if (this._schema.toJSON) {
return this._schema.toJSON.apply(this, arguments);
} else {
//xx return Object.toJSON.apply(this,arguments);
assert(self._schema);
const schema = self._schema;
const options = {};
_visit_schema_chain(self, schema, options, _JSONify);
return options;
}
};
BaseUAObject.prototype.clone = function (/*options,optionalFilter,extraInfo*/) {
const self = this;
const params = {};
function construct_param(schema, options) {
/* jshint validthis: true */
const self = this;
for (const field of schema.fields) {
const f = self[field.name];
if (f === null || f === undefined) {
continue;
}
if (field.isArray) {
options[field.name] = self[field.name];
} else {
options[field.name] = self[field.name];
}
}
}
construct_param.call(this,self._schema,params);
return new self.constructor(params);
};
exports.BaseUAObject = BaseUAObject;