1// Copyright (C) 2015 basysKom GmbH, opensource@basyskom.com
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qopcuamultidimensionalarray.h"
5
6QT_BEGIN_NAMESPACE
7
8/*
9 This class has been modelled in the style of the Variant encoding
10 defined in OPC-UA part 6, 5.2.2.16.
11
12 This solution has been preferred to returning nested QVariantLists
13 due to the following reasons:
14 - A QVariantList inside a QVariantList is stored as a QVariant which must be converted
15 to QVariantList before the elements can be accessed. This makes it impossible to update the
16 values in place.
17 - The length of the array is encoded as a 32 bit unsigned integer.
18 Array dimensions are encoded in an array, so an array can have UINT32_MAX dimensions.
19 Depending on the number of dimensions, there could be lots of nested QVariantLists
20 which would require a huge effort when calculating the array dimensions for conversions
21 between QVariantList and the sdk specific variant type.
22*/
23
24/*!
25 \class QOpcUaMultiDimensionalArray
26 \inmodule QtOpcUa
27 \brief A container class for multidimensional arrays.
28
29 This class manages arrays of Qt OPC UA types with associated array dimensions information.
30 It is returned as value when a multidimensional array is received from the server. It can also
31 be used as a write value or as parameter for filters and method calls.
32*/
33
34class QOpcUaMultiDimensionalArrayData : public QSharedData
35{
36public:
37 QVariantList value;
38 QList<quint32> arrayDimensions;
39 quint32 expectedArrayLength{0};
40};
41
42QOpcUaMultiDimensionalArray::QOpcUaMultiDimensionalArray()
43 : data(new QOpcUaMultiDimensionalArrayData)
44{
45}
46
47/*!
48 Constructs a multidimensional array from \a other.
49*/
50QOpcUaMultiDimensionalArray::QOpcUaMultiDimensionalArray(const QOpcUaMultiDimensionalArray &other)
51 : data(other.data)
52{
53}
54
55/*!
56 Sets the values from \a rhs in the multidimensional array.
57*/
58QOpcUaMultiDimensionalArray &QOpcUaMultiDimensionalArray::operator=(const QOpcUaMultiDimensionalArray &rhs)
59{
60 if (this != &rhs)
61 data.operator=(o: rhs.data);
62 return *this;
63}
64
65/*!
66 Constructs a multidimensional array with value \a value and array dimensions \a arrayDimensions.
67*/
68QOpcUaMultiDimensionalArray::QOpcUaMultiDimensionalArray(const QVariantList &value, const QList<quint32> &arrayDimensions)
69 : data(new QOpcUaMultiDimensionalArrayData)
70{
71 setValueArray(value);
72 setArrayDimensions(arrayDimensions);
73}
74
75/*!
76 Creates a multidimensional array with preallocated data fitting \a arrayDimensions.
77*/
78QOpcUaMultiDimensionalArray::QOpcUaMultiDimensionalArray(const QList<quint32> &arrayDimensions)
79 : data(new QOpcUaMultiDimensionalArrayData)
80{
81 setArrayDimensions(arrayDimensions);
82 if (data->expectedArrayLength) {
83 data->value.reserve(asize: data->expectedArrayLength);
84 for (size_t i = 0; i < data->expectedArrayLength; ++i)
85 data->value.append(t: QVariant());
86 }
87}
88
89QOpcUaMultiDimensionalArray::~QOpcUaMultiDimensionalArray()
90{
91}
92
93/*!
94 Returns the dimensions of the multidimensional array.
95 The element at position n contains the length of the n-th dimension.
96*/
97QList<quint32> QOpcUaMultiDimensionalArray::arrayDimensions() const
98{
99 return data->arrayDimensions;
100}
101
102/*!
103 Sets the dimensions of the multidimensional array to \a arrayDimensions.
104*/
105void QOpcUaMultiDimensionalArray::setArrayDimensions(const QList<quint32> &arrayDimensions)
106{
107 data->arrayDimensions = arrayDimensions;
108 data->expectedArrayLength = std::accumulate(first: data->arrayDimensions.begin(), last: data->arrayDimensions.end(),
109 init: 1, binary_op: std::multiplies<quint32>());
110}
111
112/*!
113 Returns \c true if this multidimensional array has the same value as \a other.
114*/
115bool QOpcUaMultiDimensionalArray::operator==(const QOpcUaMultiDimensionalArray &other) const
116{
117 return arrayDimensions() == other.arrayDimensions() &&
118 valueArray() == other.valueArray();
119}
120
121/*!
122 Converts this multidimensional array to \l QVariant.
123*/
124QOpcUaMultiDimensionalArray::operator QVariant() const
125{
126 return QVariant::fromValue(value: *this);
127}
128
129/*!
130 Returns the value array of the multidimensional array.
131*/
132QVariantList QOpcUaMultiDimensionalArray::valueArray() const
133{
134 return data->value;
135}
136
137/*!
138 Returns a reference to the value array of the multidimensional array.
139*/
140QVariantList &QOpcUaMultiDimensionalArray::valueArrayRef()
141{
142 return data->value;
143}
144
145/*!
146 Sets the value array of the multidimensional array to \a value.
147*/
148void QOpcUaMultiDimensionalArray::setValueArray(const QVariantList &value)
149{
150 data->value = value;
151}
152
153/*!
154 Returns the array index in \l valueArray() of the element identified by \a indices.
155 If \a indices is invalid for the array or if the array's dimensions don't match
156 the size of \l valueArray(), the invalid index \c -1 is returned.
157*/
158int QOpcUaMultiDimensionalArray::arrayIndex(const QList<quint32> &indices) const
159{
160 // A QList can store INT_MAX values. Depending on the platform, this allows a size > UINT32_MAX
161 if (data->expectedArrayLength > static_cast<quint64>((std::numeric_limits<int>::max)()) ||
162 static_cast<quint64>(data->value.size()) > (std::numeric_limits<quint32>::max)())
163 return -1;
164
165 // Check number of dimensions and data size
166 if (indices.size() != data->arrayDimensions.size() ||
167 data->expectedArrayLength != static_cast<quint32>(data->value.size()))
168 return -1; // Missing array dimensions or array dimensions don't fit the array
169
170 quint32 index = 0;
171 quint32 stride = 1;
172 // Reverse iteration to avoid repetitions while calculating the stride
173 for (int i = data->arrayDimensions.size() - 1; i >= 0; --i) {
174 if (indices.at(i) >= data->arrayDimensions.at(i)) // Out of bounds
175 return -1;
176
177 // Arrays are encoded in row-major order: [0,0,0], [0,0,1], [0,1,0], [0,1,1], [1,0,0], [1,0,1], [1,1,0], [1,1,1]
178 // The stride for dimension i in a n dimensional array is the product of all array dimensions from i+1 to n
179 if (i < data->arrayDimensions.size() - 1)
180 stride *= data->arrayDimensions.at(i: i + 1);
181 index += stride * indices.at(i);
182 }
183
184 return (index <= static_cast<quint64>((std::numeric_limits<int>::max)())) ?
185 static_cast<int>(index) : -1;
186}
187
188/*!
189 Returns the value of the element identified by \a indices.
190 If the indices are invalid for the array, an empty \l QVariant is returned.
191*/
192QVariant QOpcUaMultiDimensionalArray::value(const QList<quint32> &indices) const
193{
194 int index = arrayIndex(indices);
195
196 if (index < 0)
197 return QVariant();
198
199 return data->value.at(i: index);
200}
201
202/*!
203 Sets the value at position \a indices to \a value.
204 Returns \c true if the value has been successfully set.
205*/
206bool QOpcUaMultiDimensionalArray::setValue(const QList<quint32> &indices, const QVariant &value)
207{
208 int index = arrayIndex(indices);
209
210 if (index < 0)
211 return false;
212
213 data->value[index] = value;
214 return true;
215}
216
217/*!
218 Returns \c true if the multidimensional array is valid
219*/
220bool QOpcUaMultiDimensionalArray::isValid() const
221{
222 return static_cast<quint64>(data->value.size()) == data->expectedArrayLength &&
223 static_cast<quint64>(data->value.size()) <= (std::numeric_limits<quint32>::max)() &&
224 static_cast<quint64>(data->arrayDimensions.size()) <= (std::numeric_limits<quint32>::max)();
225}
226
227QT_END_NAMESPACE
228

source code of qtopcua/src/opcua/client/qopcuamultidimensionalarray.cpp