|
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ |
|
2 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
3 * License, v. 2.0. If a copy of the MPL was not distributed with this |
|
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
5 |
|
6 #include "txNodeSet.h" |
|
7 #include "txLog.h" |
|
8 #include "nsMemory.h" |
|
9 #include "txXPathTreeWalker.h" |
|
10 #include <algorithm> |
|
11 |
|
12 /** |
|
13 * Implementation of an XPath nodeset |
|
14 */ |
|
15 |
|
16 #ifdef NS_BUILD_REFCNT_LOGGING |
|
17 #define LOG_CHUNK_MOVE(_start, _new_start, _count) \ |
|
18 { \ |
|
19 txXPathNode *start = const_cast<txXPathNode*>(_start); \ |
|
20 while (start < _start + _count) { \ |
|
21 NS_LogDtor(start, "txXPathNode", sizeof(*start)); \ |
|
22 ++start; \ |
|
23 } \ |
|
24 start = const_cast<txXPathNode*>(_new_start); \ |
|
25 while (start < _new_start + _count) { \ |
|
26 NS_LogCtor(start, "txXPathNode", sizeof(*start)); \ |
|
27 ++start; \ |
|
28 } \ |
|
29 } |
|
30 #else |
|
31 #define LOG_CHUNK_MOVE(_start, _new_start, _count) |
|
32 #endif |
|
33 |
|
34 static const int32_t kTxNodeSetMinSize = 4; |
|
35 static const int32_t kTxNodeSetGrowFactor = 2; |
|
36 |
|
37 #define kForward 1 |
|
38 #define kReversed -1 |
|
39 |
|
40 txNodeSet::txNodeSet(txResultRecycler* aRecycler) |
|
41 : txAExprResult(aRecycler), |
|
42 mStart(nullptr), |
|
43 mEnd(nullptr), |
|
44 mStartBuffer(nullptr), |
|
45 mEndBuffer(nullptr), |
|
46 mDirection(kForward), |
|
47 mMarks(nullptr) |
|
48 { |
|
49 } |
|
50 |
|
51 txNodeSet::txNodeSet(const txXPathNode& aNode, txResultRecycler* aRecycler) |
|
52 : txAExprResult(aRecycler), |
|
53 mStart(nullptr), |
|
54 mEnd(nullptr), |
|
55 mStartBuffer(nullptr), |
|
56 mEndBuffer(nullptr), |
|
57 mDirection(kForward), |
|
58 mMarks(nullptr) |
|
59 { |
|
60 if (!ensureGrowSize(1)) { |
|
61 return; |
|
62 } |
|
63 |
|
64 new(mStart) txXPathNode(aNode); |
|
65 ++mEnd; |
|
66 } |
|
67 |
|
68 txNodeSet::txNodeSet(const txNodeSet& aSource, txResultRecycler* aRecycler) |
|
69 : txAExprResult(aRecycler), |
|
70 mStart(nullptr), |
|
71 mEnd(nullptr), |
|
72 mStartBuffer(nullptr), |
|
73 mEndBuffer(nullptr), |
|
74 mDirection(kForward), |
|
75 mMarks(nullptr) |
|
76 { |
|
77 append(aSource); |
|
78 } |
|
79 |
|
80 txNodeSet::~txNodeSet() |
|
81 { |
|
82 delete [] mMarks; |
|
83 |
|
84 if (mStartBuffer) { |
|
85 destroyElements(mStart, mEnd); |
|
86 |
|
87 nsMemory::Free(mStartBuffer); |
|
88 } |
|
89 } |
|
90 |
|
91 nsresult txNodeSet::add(const txXPathNode& aNode) |
|
92 { |
|
93 NS_ASSERTION(mDirection == kForward, |
|
94 "only append(aNode) is supported on reversed nodesets"); |
|
95 |
|
96 if (isEmpty()) { |
|
97 return append(aNode); |
|
98 } |
|
99 |
|
100 bool dupe; |
|
101 txXPathNode* pos = findPosition(aNode, mStart, mEnd, dupe); |
|
102 |
|
103 if (dupe) { |
|
104 return NS_OK; |
|
105 } |
|
106 |
|
107 // save pos, ensureGrowSize messes with the pointers |
|
108 int32_t moveSize = mEnd - pos; |
|
109 int32_t offset = pos - mStart; |
|
110 if (!ensureGrowSize(1)) { |
|
111 return NS_ERROR_OUT_OF_MEMORY; |
|
112 } |
|
113 // set pos to where it was |
|
114 pos = mStart + offset; |
|
115 |
|
116 if (moveSize > 0) { |
|
117 LOG_CHUNK_MOVE(pos, pos + 1, moveSize); |
|
118 memmove(pos + 1, pos, moveSize * sizeof(txXPathNode)); |
|
119 } |
|
120 |
|
121 new(pos) txXPathNode(aNode); |
|
122 ++mEnd; |
|
123 |
|
124 return NS_OK; |
|
125 } |
|
126 |
|
127 nsresult txNodeSet::add(const txNodeSet& aNodes) |
|
128 { |
|
129 return add(aNodes, copyElements, nullptr); |
|
130 } |
|
131 |
|
132 nsresult txNodeSet::addAndTransfer(txNodeSet* aNodes) |
|
133 { |
|
134 // failure is out-of-memory, transfer didn't happen |
|
135 nsresult rv = add(*aNodes, transferElements, destroyElements); |
|
136 NS_ENSURE_SUCCESS(rv, rv); |
|
137 |
|
138 #ifdef TX_DONT_RECYCLE_BUFFER |
|
139 if (aNodes->mStartBuffer) { |
|
140 nsMemory::Free(aNodes->mStartBuffer); |
|
141 aNodes->mStartBuffer = aNodes->mEndBuffer = nullptr; |
|
142 } |
|
143 #endif |
|
144 aNodes->mStart = aNodes->mEnd = aNodes->mStartBuffer; |
|
145 |
|
146 return NS_OK; |
|
147 } |
|
148 |
|
149 /** |
|
150 * add(aNodeSet, aTransferOp) |
|
151 * |
|
152 * The code is optimized to make a minimum number of calls to |
|
153 * Node::compareDocumentPosition. The idea is this: |
|
154 * We have the two nodesets (number indicate "document position") |
|
155 * |
|
156 * 1 3 7 <- source 1 |
|
157 * 2 3 6 8 9 <- source 2 |
|
158 * _ _ _ _ _ _ _ _ <- result |
|
159 * |
|
160 * |
|
161 * When merging these nodesets into the result, the nodes are transfered |
|
162 * in chunks to the end of the buffer so that each chunk does not contain |
|
163 * a node from the other nodeset, in document order. |
|
164 * |
|
165 * We select the last non-transfered node in the first nodeset and find |
|
166 * where in the other nodeset it would be inserted. In this case we would |
|
167 * take the 7 from the first nodeset and find the position between the |
|
168 * 6 and 8 in the second. We then take the nodes after the insert-position |
|
169 * and transfer them to the end of the resulting nodeset. Which in this case |
|
170 * means that we first transfered the 8 and 9 nodes, giving us the following: |
|
171 * |
|
172 * 1 3 7 <- source 1 |
|
173 * 2 3 6 <- source 2 |
|
174 * _ _ _ _ _ _ 8 9 <- result |
|
175 * |
|
176 * The corresponding procedure is done for the second nodeset, that is |
|
177 * the insertion position of the 6 in the first nodeset is found, which |
|
178 * is between the 3 and the 7. The 7 is memmoved (as it stays within |
|
179 * the same nodeset) to the result buffer. |
|
180 * |
|
181 * As the result buffer is filled from the end, it is safe to share the |
|
182 * buffer between this nodeset and the result. |
|
183 * |
|
184 * This is repeated until both of the nodesets are empty. |
|
185 * |
|
186 * If we find a duplicate node when searching for where insertposition we |
|
187 * check for sequences of duplicate nodes, which can be optimized. |
|
188 * |
|
189 */ |
|
190 nsresult txNodeSet::add(const txNodeSet& aNodes, transferOp aTransfer, |
|
191 destroyOp aDestroy) |
|
192 { |
|
193 NS_ASSERTION(mDirection == kForward, |
|
194 "only append(aNode) is supported on reversed nodesets"); |
|
195 |
|
196 if (aNodes.isEmpty()) { |
|
197 return NS_OK; |
|
198 } |
|
199 |
|
200 if (!ensureGrowSize(aNodes.size())) { |
|
201 return NS_ERROR_OUT_OF_MEMORY; |
|
202 } |
|
203 |
|
204 // This is probably a rather common case, so lets try to shortcut. |
|
205 if (mStart == mEnd || |
|
206 txXPathNodeUtils::comparePosition(mEnd[-1], *aNodes.mStart) < 0) { |
|
207 aTransfer(mEnd, aNodes.mStart, aNodes.mEnd); |
|
208 mEnd += aNodes.size(); |
|
209 |
|
210 return NS_OK; |
|
211 } |
|
212 |
|
213 // Last element in this nodeset |
|
214 txXPathNode* thisPos = mEnd; |
|
215 |
|
216 // Last element of the other nodeset |
|
217 txXPathNode* otherPos = aNodes.mEnd; |
|
218 |
|
219 // Pointer to the insertion point in this nodeset |
|
220 txXPathNode* insertPos = mEndBuffer; |
|
221 |
|
222 bool dupe; |
|
223 txXPathNode* pos; |
|
224 int32_t count; |
|
225 while (thisPos > mStart || otherPos > aNodes.mStart) { |
|
226 // Find where the last remaining node of this nodeset would |
|
227 // be inserted in the other nodeset. |
|
228 if (thisPos > mStart) { |
|
229 pos = findPosition(thisPos[-1], aNodes.mStart, otherPos, dupe); |
|
230 |
|
231 if (dupe) { |
|
232 const txXPathNode *deletePos = thisPos; |
|
233 --thisPos; // this is already added |
|
234 // check dupe sequence |
|
235 while (thisPos > mStart && pos > aNodes.mStart && |
|
236 thisPos[-1] == pos[-1]) { |
|
237 --thisPos; |
|
238 --pos; |
|
239 } |
|
240 |
|
241 if (aDestroy) { |
|
242 aDestroy(thisPos, deletePos); |
|
243 } |
|
244 } |
|
245 } |
|
246 else { |
|
247 pos = aNodes.mStart; |
|
248 } |
|
249 |
|
250 // Transfer the otherNodes after the insertion point to the result |
|
251 count = otherPos - pos; |
|
252 if (count > 0) { |
|
253 insertPos -= count; |
|
254 aTransfer(insertPos, pos, otherPos); |
|
255 otherPos -= count; |
|
256 } |
|
257 |
|
258 // Find where the last remaining node of the otherNodeset would |
|
259 // be inserted in this nodeset. |
|
260 if (otherPos > aNodes.mStart) { |
|
261 pos = findPosition(otherPos[-1], mStart, thisPos, dupe); |
|
262 |
|
263 if (dupe) { |
|
264 const txXPathNode *deletePos = otherPos; |
|
265 --otherPos; // this is already added |
|
266 // check dupe sequence |
|
267 while (otherPos > aNodes.mStart && pos > mStart && |
|
268 otherPos[-1] == pos[-1]) { |
|
269 --otherPos; |
|
270 --pos; |
|
271 } |
|
272 |
|
273 if (aDestroy) { |
|
274 aDestroy(otherPos, deletePos); |
|
275 } |
|
276 } |
|
277 } |
|
278 else { |
|
279 pos = mStart; |
|
280 } |
|
281 |
|
282 // Move the nodes from this nodeset after the insertion point |
|
283 // to the result |
|
284 count = thisPos - pos; |
|
285 if (count > 0) { |
|
286 insertPos -= count; |
|
287 LOG_CHUNK_MOVE(pos, insertPos, count); |
|
288 memmove(insertPos, pos, count * sizeof(txXPathNode)); |
|
289 thisPos -= count; |
|
290 } |
|
291 } |
|
292 mStart = insertPos; |
|
293 mEnd = mEndBuffer; |
|
294 |
|
295 return NS_OK; |
|
296 } |
|
297 |
|
298 /** |
|
299 * Append API |
|
300 * These functions should be used with care. |
|
301 * They are intended to be used when the caller assures that the resulting |
|
302 * nodeset remains in document order. |
|
303 * Abuse will break document order, and cause errors in the result. |
|
304 * These functions are significantly faster than the add API, as no |
|
305 * order info operations will be performed. |
|
306 */ |
|
307 |
|
308 nsresult |
|
309 txNodeSet::append(const txXPathNode& aNode) |
|
310 { |
|
311 if (!ensureGrowSize(1)) { |
|
312 return NS_ERROR_OUT_OF_MEMORY; |
|
313 } |
|
314 |
|
315 if (mDirection == kForward) { |
|
316 new(mEnd) txXPathNode(aNode); |
|
317 ++mEnd; |
|
318 |
|
319 return NS_OK; |
|
320 } |
|
321 |
|
322 new(--mStart) txXPathNode(aNode); |
|
323 |
|
324 return NS_OK; |
|
325 } |
|
326 |
|
327 nsresult |
|
328 txNodeSet::append(const txNodeSet& aNodes) |
|
329 { |
|
330 NS_ASSERTION(mDirection == kForward, |
|
331 "only append(aNode) is supported on reversed nodesets"); |
|
332 |
|
333 if (aNodes.isEmpty()) { |
|
334 return NS_OK; |
|
335 } |
|
336 |
|
337 int32_t appended = aNodes.size(); |
|
338 if (!ensureGrowSize(appended)) { |
|
339 return NS_ERROR_OUT_OF_MEMORY; |
|
340 } |
|
341 |
|
342 copyElements(mEnd, aNodes.mStart, aNodes.mEnd); |
|
343 mEnd += appended; |
|
344 |
|
345 return NS_OK; |
|
346 } |
|
347 |
|
348 nsresult |
|
349 txNodeSet::mark(int32_t aIndex) |
|
350 { |
|
351 NS_ASSERTION(aIndex >= 0 && mStart && mEnd - mStart > aIndex, |
|
352 "index out of bounds"); |
|
353 if (!mMarks) { |
|
354 int32_t length = size(); |
|
355 mMarks = new bool[length]; |
|
356 NS_ENSURE_TRUE(mMarks, NS_ERROR_OUT_OF_MEMORY); |
|
357 memset(mMarks, 0, length * sizeof(bool)); |
|
358 } |
|
359 if (mDirection == kForward) { |
|
360 mMarks[aIndex] = true; |
|
361 } |
|
362 else { |
|
363 mMarks[size() - aIndex - 1] = true; |
|
364 } |
|
365 |
|
366 return NS_OK; |
|
367 } |
|
368 |
|
369 nsresult |
|
370 txNodeSet::sweep() |
|
371 { |
|
372 if (!mMarks) { |
|
373 // sweep everything |
|
374 clear(); |
|
375 } |
|
376 |
|
377 int32_t chunk, pos = 0; |
|
378 int32_t length = size(); |
|
379 txXPathNode* insertion = mStartBuffer; |
|
380 |
|
381 while (pos < length) { |
|
382 while (pos < length && !mMarks[pos]) { |
|
383 // delete unmarked |
|
384 mStart[pos].~txXPathNode(); |
|
385 ++pos; |
|
386 } |
|
387 // find chunk to move |
|
388 chunk = 0; |
|
389 while (pos < length && mMarks[pos]) { |
|
390 ++pos; |
|
391 ++chunk; |
|
392 } |
|
393 // move chunk |
|
394 if (chunk > 0) { |
|
395 LOG_CHUNK_MOVE(mStart + pos - chunk, insertion, chunk); |
|
396 memmove(insertion, mStart + pos - chunk, |
|
397 chunk * sizeof(txXPathNode)); |
|
398 insertion += chunk; |
|
399 } |
|
400 } |
|
401 mStart = mStartBuffer; |
|
402 mEnd = insertion; |
|
403 delete [] mMarks; |
|
404 mMarks = nullptr; |
|
405 |
|
406 return NS_OK; |
|
407 } |
|
408 |
|
409 void |
|
410 txNodeSet::clear() |
|
411 { |
|
412 destroyElements(mStart, mEnd); |
|
413 #ifdef TX_DONT_RECYCLE_BUFFER |
|
414 if (mStartBuffer) { |
|
415 nsMemory::Free(mStartBuffer); |
|
416 mStartBuffer = mEndBuffer = nullptr; |
|
417 } |
|
418 #endif |
|
419 mStart = mEnd = mStartBuffer; |
|
420 delete [] mMarks; |
|
421 mMarks = nullptr; |
|
422 mDirection = kForward; |
|
423 } |
|
424 |
|
425 int32_t |
|
426 txNodeSet::indexOf(const txXPathNode& aNode, uint32_t aStart) const |
|
427 { |
|
428 NS_ASSERTION(mDirection == kForward, |
|
429 "only append(aNode) is supported on reversed nodesets"); |
|
430 |
|
431 if (!mStart || mStart == mEnd) { |
|
432 return -1; |
|
433 } |
|
434 |
|
435 txXPathNode* pos = mStart + aStart; |
|
436 for (; pos < mEnd; ++pos) { |
|
437 if (*pos == aNode) { |
|
438 return pos - mStart; |
|
439 } |
|
440 } |
|
441 |
|
442 return -1; |
|
443 } |
|
444 |
|
445 const txXPathNode& |
|
446 txNodeSet::get(int32_t aIndex) const |
|
447 { |
|
448 if (mDirection == kForward) { |
|
449 return mStart[aIndex]; |
|
450 } |
|
451 |
|
452 return mEnd[-aIndex - 1]; |
|
453 } |
|
454 |
|
455 short |
|
456 txNodeSet::getResultType() |
|
457 { |
|
458 return txAExprResult::NODESET; |
|
459 } |
|
460 |
|
461 bool |
|
462 txNodeSet::booleanValue() |
|
463 { |
|
464 return !isEmpty(); |
|
465 } |
|
466 double |
|
467 txNodeSet::numberValue() |
|
468 { |
|
469 nsAutoString str; |
|
470 stringValue(str); |
|
471 |
|
472 return txDouble::toDouble(str); |
|
473 } |
|
474 |
|
475 void |
|
476 txNodeSet::stringValue(nsString& aStr) |
|
477 { |
|
478 NS_ASSERTION(mDirection == kForward, |
|
479 "only append(aNode) is supported on reversed nodesets"); |
|
480 if (isEmpty()) { |
|
481 return; |
|
482 } |
|
483 txXPathNodeUtils::appendNodeValue(get(0), aStr); |
|
484 } |
|
485 |
|
486 const nsString* |
|
487 txNodeSet::stringValuePointer() |
|
488 { |
|
489 return nullptr; |
|
490 } |
|
491 |
|
492 bool txNodeSet::ensureGrowSize(int32_t aSize) |
|
493 { |
|
494 // check if there is enough place in the buffer as is |
|
495 if (mDirection == kForward && aSize <= mEndBuffer - mEnd) { |
|
496 return true; |
|
497 } |
|
498 |
|
499 if (mDirection == kReversed && aSize <= mStart - mStartBuffer) { |
|
500 return true; |
|
501 } |
|
502 |
|
503 // check if we just have to align mStart to have enough space |
|
504 int32_t oldSize = mEnd - mStart; |
|
505 int32_t oldLength = mEndBuffer - mStartBuffer; |
|
506 int32_t ensureSize = oldSize + aSize; |
|
507 if (ensureSize <= oldLength) { |
|
508 // just move the buffer |
|
509 txXPathNode* dest = mStartBuffer; |
|
510 if (mDirection == kReversed) { |
|
511 dest = mEndBuffer - oldSize; |
|
512 } |
|
513 LOG_CHUNK_MOVE(mStart, dest, oldSize); |
|
514 memmove(dest, mStart, oldSize * sizeof(txXPathNode)); |
|
515 mStart = dest; |
|
516 mEnd = dest + oldSize; |
|
517 |
|
518 return true; |
|
519 } |
|
520 |
|
521 // This isn't 100% safe. But until someone manages to make a 1gig nodeset |
|
522 // it should be ok. |
|
523 int32_t newLength = std::max(oldLength, kTxNodeSetMinSize); |
|
524 |
|
525 while (newLength < ensureSize) { |
|
526 newLength *= kTxNodeSetGrowFactor; |
|
527 } |
|
528 |
|
529 txXPathNode* newArr = static_cast<txXPathNode*> |
|
530 (nsMemory::Alloc(newLength * |
|
531 sizeof(txXPathNode))); |
|
532 if (!newArr) { |
|
533 return false; |
|
534 } |
|
535 |
|
536 txXPathNode* dest = newArr; |
|
537 if (mDirection == kReversed) { |
|
538 dest += newLength - oldSize; |
|
539 } |
|
540 |
|
541 if (oldSize > 0) { |
|
542 LOG_CHUNK_MOVE(mStart, dest, oldSize); |
|
543 memcpy(dest, mStart, oldSize * sizeof(txXPathNode)); |
|
544 } |
|
545 |
|
546 if (mStartBuffer) { |
|
547 #ifdef DEBUG |
|
548 memset(mStartBuffer, 0, |
|
549 (mEndBuffer - mStartBuffer) * sizeof(txXPathNode)); |
|
550 #endif |
|
551 nsMemory::Free(mStartBuffer); |
|
552 } |
|
553 |
|
554 mStartBuffer = newArr; |
|
555 mEndBuffer = mStartBuffer + newLength; |
|
556 mStart = dest; |
|
557 mEnd = dest + oldSize; |
|
558 |
|
559 return true; |
|
560 } |
|
561 |
|
562 txXPathNode* |
|
563 txNodeSet::findPosition(const txXPathNode& aNode, txXPathNode* aFirst, |
|
564 txXPathNode* aLast, bool& aDupe) const |
|
565 { |
|
566 aDupe = false; |
|
567 if (aLast - aFirst <= 2) { |
|
568 // If we search 2 nodes or less there is no point in further divides |
|
569 txXPathNode* pos = aFirst; |
|
570 for (; pos < aLast; ++pos) { |
|
571 int cmp = txXPathNodeUtils::comparePosition(aNode, *pos); |
|
572 if (cmp < 0) { |
|
573 return pos; |
|
574 } |
|
575 |
|
576 if (cmp == 0) { |
|
577 aDupe = true; |
|
578 |
|
579 return pos; |
|
580 } |
|
581 } |
|
582 return pos; |
|
583 } |
|
584 |
|
585 // (cannot add two pointers) |
|
586 txXPathNode* midpos = aFirst + (aLast - aFirst) / 2; |
|
587 int cmp = txXPathNodeUtils::comparePosition(aNode, *midpos); |
|
588 if (cmp == 0) { |
|
589 aDupe = true; |
|
590 |
|
591 return midpos; |
|
592 } |
|
593 |
|
594 if (cmp > 0) { |
|
595 return findPosition(aNode, midpos + 1, aLast, aDupe); |
|
596 } |
|
597 |
|
598 // midpos excluded as end of range |
|
599 |
|
600 return findPosition(aNode, aFirst, midpos, aDupe); |
|
601 } |
|
602 |
|
603 /* static */ |
|
604 void |
|
605 txNodeSet::copyElements(txXPathNode* aDest, |
|
606 const txXPathNode* aStart, const txXPathNode* aEnd) |
|
607 { |
|
608 const txXPathNode* pos = aStart; |
|
609 while (pos < aEnd) { |
|
610 new(aDest) txXPathNode(*pos); |
|
611 ++aDest; |
|
612 ++pos; |
|
613 } |
|
614 } |
|
615 |
|
616 /* static */ |
|
617 void |
|
618 txNodeSet::transferElements(txXPathNode* aDest, |
|
619 const txXPathNode* aStart, const txXPathNode* aEnd) |
|
620 { |
|
621 LOG_CHUNK_MOVE(aStart, aDest, (aEnd - aStart)); |
|
622 memcpy(aDest, aStart, (aEnd - aStart) * sizeof(txXPathNode)); |
|
623 } |