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