dom/xslt/xpath/txNodeSet.cpp

branch
TOR_BUG_9701
changeset 8
97036ab72558
equal deleted inserted replaced
-1:000000000000 0:1df78e66b4a8
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 }

mercurial