1 /*
2     This file is part of the Okteta Kasten Framework, made within the KDE community.
3 
4     SPDX-FileCopyrightText: 2012 Alex Richardson <alex.richardson@gmx.de>
5 
6     SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
7 */
8 
9 #include "taggeduniondatainformation.hpp"
10 
11 #include "topleveldatainformation.hpp"
12 #include "structuredatainformation.hpp"
13 #include "primitive/primitivedatainformation.hpp"
14 #include "../parsers/parserutils.hpp"
15 
16 #include <KLocalizedString>
17 
TaggedUnionDataInformation(const QString & name,DataInformation * parent)18 TaggedUnionDataInformation::TaggedUnionDataInformation(const QString& name, DataInformation* parent)
19     : DataInformationWithChildren(name, QVector<DataInformation*>(), parent)
20 {
21 }
22 
TaggedUnionDataInformation(const TaggedUnionDataInformation & d)23 TaggedUnionDataInformation::TaggedUnionDataInformation(const TaggedUnionDataInformation& d)
24     : DataInformationWithChildren(d)
25     , mDefaultFields(cloneList(d.mDefaultFields, this))
26 {
27     Q_ASSERT(mDefaultFields.isEmpty() || mDefaultFields.at(0) != nullptr);
28     mAlternatives.reserve(d.mAlternatives.size());
29     for (const FieldInfo& fi : d.mAlternatives) {
30         mAlternatives.append(FieldInfo(fi.name, fi.selectIf, cloneList(fi.fields, this)));
31     }
32 }
33 
~TaggedUnionDataInformation()34 TaggedUnionDataInformation::~TaggedUnionDataInformation()
35 {
36     qDeleteAll(mDefaultFields);
37     for (const FieldInfo& fi : qAsConst(mAlternatives)) {
38         qDeleteAll(fi.fields);
39     }
40 }
41 
typeNameImpl() const42 QString TaggedUnionDataInformation::typeNameImpl() const
43 {
44     if (mLastIndex >= 0) {
45         return i18nc("data type in C/C++, then name", "struct %1", mAlternatives.at(mLastIndex).name);
46     }
47 
48     return i18nc("data type, then name", "tagged union %1", name());
49 }
50 
appendDefaultField(DataInformation * field,bool emitSignal)51 void TaggedUnionDataInformation::appendDefaultField(DataInformation* field, bool emitSignal)
52 {
53     const uint oldCount = childCount();
54     if (emitSignal && mLastIndex == -1) {
55         topLevelDataInformation()->_childCountAboutToChange(this, oldCount, oldCount + 1);
56     }
57     mDefaultFields.append(field);
58     if (emitSignal && mLastIndex == -1) {
59         topLevelDataInformation()->_childCountChanged(this, oldCount, oldCount + 1);
60     }
61 }
62 
setAlternatives(const QVector<FieldInfo> & alternatives,bool emitSignal)63 void TaggedUnionDataInformation::setAlternatives(const QVector<FieldInfo>& alternatives, bool emitSignal)
64 {
65     const uint oldChildCount = childCount();
66     mLastIndex = -1;
67     const uint newChidCount = childCount();
68     if (emitSignal) {
69         topLevelDataInformation()->_childCountAboutToChange(this, oldChildCount, newChidCount);
70     }
71     // remove them all
72     for (const FieldInfo& fi : qAsConst(mAlternatives)) {
73         qDeleteAll(fi.fields);
74     }
75 
76     mAlternatives.clear();
77     mAlternatives = alternatives;
78     // set parent
79     for (const FieldInfo& fi : qAsConst(mAlternatives)) {
80         for (auto* field : fi.fields) {
81             field->setParent(this);
82         }
83     }
84 
85     if (emitSignal) {
86         topLevelDataInformation()->_childCountChanged(this, oldChildCount, newChidCount);
87     }
88 }
89 
determineSelection(TopLevelDataInformation * top)90 int TaggedUnionDataInformation::determineSelection(TopLevelDataInformation* top)
91 {
92     // now find out which one of the alternatives to select
93     for (int i = 0; i < mAlternatives.size(); ++i) {
94         const FieldInfo& fi = mAlternatives.at(i);
95         if (fi.selectIf.isFunction()) {
96             QScriptValue result = top->scriptHandler()->callFunction(fi.selectIf, this,
97                                                                      ScriptHandlerInfo::Mode::TaggedUnionSelection);
98             if (!result.isBool()) {
99                 logError() << "Evaluating select function for alternative" << i
100                            << "did not return a boolean value. Got the following instead:" << result.toString();
101                 continue;
102             }
103             if (result.toBool()) {
104                 return i;
105             }
106         } else {
107             ParsedNumber<quint64> number = ParserUtils::uint64FromScriptValue(fi.selectIf);
108             if (!number.isValid) {
109                 logError() << "Alternative number" << i << "is not valid. SelectIf"
110                     " is neither function nor number:" << fi.selectIf.toString();
111                 continue;
112 
113             }
114             // number is valid -> there must be exactly one field
115             if (mChildren.size() != 1) {
116                 logError() << "Alternative number" << i << "is not valid. SelectIf is number, but there is not exactly one child!";
117                 continue;
118             }
119             if (!mChildren.at(0)->isPrimitive()) {
120                 logError() << "Alternative number" << i << "is not valid. SelectIf is number, but only child is not primitive!";
121                 continue;
122             }
123             if (mChildren.at(0)->asPrimitive()->value() == number.value) {
124                 return i; // found it
125             }
126         }
127     }
128 
129     return -1;
130 }
131 
readData(Okteta::AbstractByteArrayModel * input,Okteta::Address address,BitCount64 bitsRemaining,quint8 * bitOffset)132 qint64 TaggedUnionDataInformation::readData(Okteta::AbstractByteArrayModel* input,
133                                             Okteta::Address address, BitCount64 bitsRemaining, quint8* bitOffset)
134 {
135     Q_ASSERT(mHasBeenUpdated);
136     // update must have been called prior to reading
137     TopLevelDataInformation* top = topLevelDataInformation();
138     Q_CHECK_PTR(top);
139 
140     const QVector<DataInformation*>& oldChildren = currentChildren();
141 
142     qint64 readBits = 0;
143     mWasAbleToRead = StructureDataInformation::readChildren(mChildren,
144                                                             input, address, bitsRemaining, bitOffset, &readBits, top);
145     mLastIndex = determineSelection(top);
146     const QVector<DataInformation*>& others = currentChildren();
147     // check whether we have different children now, if yes we have to emit child count changed
148     if (oldChildren != others) {
149         const int fixedSize = mChildren.size();
150         // tell the model that all children have changed by setting to 0 and then to new size
151         top->_childCountAboutToChange(this, fixedSize + oldChildren.size(), fixedSize);
152         top->_childCountChanged(this, fixedSize + oldChildren.size(), fixedSize);
153         top->_childCountAboutToChange(this, fixedSize, fixedSize + others.size());
154         top->_childCountChanged(this, fixedSize, fixedSize + others.size());
155     }
156 
157     // this is important since the remaining children might have changed since before the read
158     // where beginRead was called on the children at that time
159     for (auto* other : others) {
160         other->beginRead();
161     }
162 
163     if (!mWasAbleToRead) {
164         Q_ASSERT(readBits == -1);
165         return -1;
166     }
167     // otherwise continue reading the remaining children
168     mWasAbleToRead = StructureDataInformation::readChildren(others,
169                                                             input, address, bitsRemaining, bitOffset, &readBits, top);
170     return readBits;
171 }
172 
childPosition(const DataInformation * child,Okteta::Address start) const173 BitCount64 TaggedUnionDataInformation::childPosition(const DataInformation* child,
174                                                      Okteta::Address start) const
175 {
176     BitCount64 offset = 0;
177     bool found = false;
178     // sum size of elements up to index
179     for (auto* current : mChildren) {
180         if (current == child) {
181             found = true;
182             break;
183         }
184         offset += current->size();
185     }
186 
187     if (!found) {
188         const QVector<DataInformation*> others = currentChildren();
189         for (auto* current : others) {
190             if (current == child) {
191                 found = true;
192                 break;
193             }
194             offset += current->size();
195         }
196     }
197     Q_ASSERT(found);
198     if (mParent->isTopLevel()) {
199         return start * 8 + offset;
200     }
201 
202     return mParent->asDataInformation()->childPosition(this, start) + offset;
203 }
204 
size() const205 BitCount32 TaggedUnionDataInformation::size() const
206 {
207     BitCount32 total = 0;
208     for (auto* child : mChildren) {
209         total += child->size();
210     }
211 
212     const QVector<DataInformation*> others = currentChildren();
213     for (auto* other : others) {
214         total += other->size();
215     }
216 
217     return total;
218 }
219 
replaceChildAt(unsigned int index,DataInformation * newChild)220 bool TaggedUnionDataInformation::replaceChildAt(unsigned int index, DataInformation* newChild)
221 {
222     Q_ASSERT(false); // TODO implement
223     Q_UNUSED(index)
224     Q_UNUSED(newChild)
225     return false;
226 }
227 
indexOf(const DataInformation * const data) const228 int TaggedUnionDataInformation::indexOf(const DataInformation* const data) const
229 {
230     int index = 0;
231     for (auto* child : mChildren) {
232         if (child == data) {
233             return index;
234         }
235         index++;
236     }
237 
238     const QVector<DataInformation*> others = currentChildren();
239     for (auto* other : others) {
240         if (other == data) {
241             return index;
242         }
243         index++;
244     }
245 
246     Q_ASSERT(false); // should never land here
247     return -1;
248 }
249 
childAt(unsigned int index) const250 DataInformation* TaggedUnionDataInformation::childAt(unsigned int index) const
251 {
252     const uint permanentChildCount = uint(mChildren.size());
253     if (index < permanentChildCount) {
254         return mChildren.at(index);
255     }
256     const QVector<DataInformation*> others = currentChildren();
257     if (index < permanentChildCount + others.size()) {
258         return others.at(index - permanentChildCount);
259     }
260     Q_ASSERT(false); // should never happen
261     return nullptr;
262 }
263 
childCount() const264 unsigned int TaggedUnionDataInformation::childCount() const
265 {
266     return mChildren.size() + currentChildren().size();
267 }
268 
isTaggedUnion() const269 bool TaggedUnionDataInformation::isTaggedUnion() const
270 {
271     return true;
272 }
273