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
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/
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
15 * The Original Code is Mozilla Communicator client code.
17 * The Initial Developer of the Original Code is
18 * Netscape Communications Corporation.
19 * Portions created by the Initial Developer are Copyright (C) 1998
20 * the Initial Developer. All Rights Reserved.
23 * Pierre Phaneuf <pp@ludusdesign.com>
25 * Alternatively, the contents of this file may be used under the terms of
26 * either of the GNU General Public License Version 2 or later (the "GPL"),
27 * or 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.
37 * ***** END LICENSE BLOCK ***** */
41 Implementation for the RDF container.
46 1. RDF containers are one-indexed. This means that a lot of the loops
47 that you'd normally think you'd write like this:
49 for (i = 0; i < count; ++i) {}
51 You've gotta write like this:
53 for (i = 1; i <= count; ++i) {}
55 "Sure, right, yeah, of course.", you say. Well maybe I'm just
56 thick, but it's easy to slip up.
58 2. The RDF:nextVal property on the container is an
59 implementation-level hack that is used to quickly compute the
60 next value for appending to the container. It will no doubt
61 become royally screwed up in the case of aggregation.
63 3. The RDF:nextVal property is also used to retrieve the count of
64 elements in the container.
70 #include "nsIRDFContainer.h"
71 #include "nsIRDFContainerUtils.h"
72 #include "nsIRDFInMemoryDataSource.h"
73 #include "nsIRDFPropagatableDataSource.h"
74 #include "nsIRDFService.h"
75 #include "nsIServiceManager.h"
78 #include "nsXPIDLString.h"
81 static NS_DEFINE_CID(kRDFServiceCID
, NS_RDFSERVICE_CID
);
82 static NS_DEFINE_CID(kRDFContainerUtilsCID
, NS_RDFCONTAINERUTILS_CID
);
83 static const char kRDFNameSpaceURI
[] = RDF_NAMESPACE_URI
;
85 #define RDF_SEQ_LIST_LIMIT 8
87 class RDFContainerImpl
: public nsIRDFContainer
91 // nsISupports interface
94 // nsIRDFContainer interface
95 NS_DECL_NSIRDFCONTAINER
98 friend nsresult
NS_NewRDFContainer(nsIRDFContainer
** aResult
);
101 virtual ~RDFContainerImpl();
105 nsresult
Renumber(PRInt32 aStartIndex
, PRInt32 aIncrement
);
106 nsresult
SetNextValue(PRInt32 aIndex
);
107 nsresult
GetNextValue(nsIRDFResource
** aResult
);
109 nsIRDFDataSource
* mDataSource
;
110 nsIRDFResource
* mContainer
;
113 static PRInt32 gRefCnt
;
114 static nsIRDFService
* gRDFService
;
115 static nsIRDFContainerUtils
* gRDFContainerUtils
;
116 static nsIRDFResource
* kRDF_nextVal
;
120 PRInt32
RDFContainerImpl::gRefCnt
= 0;
121 nsIRDFService
* RDFContainerImpl::gRDFService
;
122 nsIRDFContainerUtils
* RDFContainerImpl::gRDFContainerUtils
;
123 nsIRDFResource
* RDFContainerImpl::kRDF_nextVal
;
125 ////////////////////////////////////////////////////////////////////////
126 // nsISupports interface
128 NS_IMPL_ISUPPORTS1(RDFContainerImpl
, nsIRDFContainer
)
132 ////////////////////////////////////////////////////////////////////////
133 // nsIRDFContainer interface
136 RDFContainerImpl::GetDataSource(nsIRDFDataSource
** _retval
)
138 *_retval
= mDataSource
;
139 NS_IF_ADDREF(*_retval
);
145 RDFContainerImpl::GetResource(nsIRDFResource
** _retval
)
147 *_retval
= mContainer
;
148 NS_IF_ADDREF(*_retval
);
154 RDFContainerImpl::Init(nsIRDFDataSource
*aDataSource
, nsIRDFResource
*aContainer
)
156 NS_PRECONDITION(aDataSource
!= nsnull
, "null ptr");
158 return NS_ERROR_NULL_POINTER
;
160 NS_PRECONDITION(aContainer
!= nsnull
, "null ptr");
162 return NS_ERROR_NULL_POINTER
;
166 rv
= gRDFContainerUtils
->IsContainer(aDataSource
, aContainer
, &isContainer
);
167 if (NS_FAILED(rv
)) return rv
;
169 // ``throw'' if we can't create a container on the specified
170 // datasource/resource combination.
172 return NS_ERROR_FAILURE
;
174 NS_IF_RELEASE(mDataSource
);
175 mDataSource
= aDataSource
;
176 NS_ADDREF(mDataSource
);
178 NS_IF_RELEASE(mContainer
);
179 mContainer
= aContainer
;
180 NS_ADDREF(mContainer
);
187 RDFContainerImpl::GetCount(PRInt32
*aCount
)
189 if (!mDataSource
|| !mContainer
)
190 return NS_ERROR_NOT_INITIALIZED
;
194 // Get the next value, which hangs off of the bag via the
195 // RDF:nextVal property. This is the _next value_ that will get
196 // assigned in a one-indexed array. So, it's actually _one more_
197 // than the actual count of elements in the container.
199 // XXX To handle aggregation, this should probably be a
200 // GetTargets() that enumerates all of the values and picks the
202 nsCOMPtr
<nsIRDFNode
> nextValNode
;
203 rv
= mDataSource
->GetTarget(mContainer
, kRDF_nextVal
, PR_TRUE
, getter_AddRefs(nextValNode
));
204 if (NS_FAILED(rv
)) return rv
;
206 if (rv
== NS_RDF_NO_VALUE
)
207 return NS_ERROR_UNEXPECTED
;
209 nsCOMPtr
<nsIRDFLiteral
> nextValLiteral
;
210 rv
= nextValNode
->QueryInterface(NS_GET_IID(nsIRDFLiteral
), getter_AddRefs(nextValLiteral
));
211 if (NS_FAILED(rv
)) return rv
;
214 rv
= nextValLiteral
->GetValueConst( &s
);
215 if (NS_FAILED(rv
)) return rv
;
217 nsAutoString
nextValStr(s
);
221 nextVal
= nextValStr
.ToInteger(&err
);
223 return NS_ERROR_UNEXPECTED
;
225 *aCount
= nextVal
- 1;
231 RDFContainerImpl::GetElements(nsISimpleEnumerator
**_retval
)
233 if (!mDataSource
|| !mContainer
)
234 return NS_ERROR_NOT_INITIALIZED
;
236 return NS_NewContainerEnumerator(mDataSource
, mContainer
, _retval
);
241 RDFContainerImpl::AppendElement(nsIRDFNode
*aElement
)
243 if (!mDataSource
|| !mContainer
)
244 return NS_ERROR_NOT_INITIALIZED
;
246 NS_PRECONDITION(aElement
!= nsnull
, "null ptr");
248 return NS_ERROR_NULL_POINTER
;
252 nsCOMPtr
<nsIRDFResource
> nextVal
;
253 rv
= GetNextValue(getter_AddRefs(nextVal
));
254 if (NS_FAILED(rv
)) return rv
;
256 rv
= mDataSource
->Assert(mContainer
, nextVal
, aElement
, PR_TRUE
);
257 if (NS_FAILED(rv
)) return rv
;
264 RDFContainerImpl::RemoveElement(nsIRDFNode
*aElement
, PRBool aRenumber
)
266 if (!mDataSource
|| !mContainer
)
267 return NS_ERROR_NOT_INITIALIZED
;
269 NS_PRECONDITION(aElement
!= nsnull
, "null ptr");
271 return NS_ERROR_NULL_POINTER
;
276 rv
= IndexOf(aElement
, &idx
);
277 if (NS_FAILED(rv
)) return rv
;
282 // Remove the element.
283 nsCOMPtr
<nsIRDFResource
> ordinal
;
284 rv
= gRDFContainerUtils
->IndexToOrdinalResource(idx
,
285 getter_AddRefs(ordinal
));
286 if (NS_FAILED(rv
)) return rv
;
288 rv
= mDataSource
->Unassert(mContainer
, ordinal
, aElement
);
289 if (NS_FAILED(rv
)) return rv
;
292 // Now slide the rest of the collection backwards to fill in
293 // the gap. This will have the side effect of completely
294 // renumber the container from index to the end.
295 rv
= Renumber(idx
+ 1, -1);
296 if (NS_FAILED(rv
)) return rv
;
304 RDFContainerImpl::InsertElementAt(nsIRDFNode
*aElement
, PRInt32 aIndex
, PRBool aRenumber
)
306 if (!mDataSource
|| !mContainer
)
307 return NS_ERROR_NOT_INITIALIZED
;
309 NS_PRECONDITION(aElement
!= nsnull
, "null ptr");
311 return NS_ERROR_NULL_POINTER
;
313 NS_PRECONDITION(aIndex
>= 1, "illegal value");
315 return NS_ERROR_ILLEGAL_VALUE
;
320 rv
= GetCount(&count
);
321 if (NS_FAILED(rv
)) return rv
;
323 NS_ASSERTION(aIndex
<= count
+ 1, "illegal value");
324 if (aIndex
> count
+ 1)
325 return NS_ERROR_ILLEGAL_VALUE
;
328 // Make a hole for the element. This will have the side effect of
329 // completely renumbering the container from 'aIndex' to 'count',
330 // and will spew assertions.
331 rv
= Renumber(aIndex
, +1);
332 if (NS_FAILED(rv
)) return rv
;
335 nsCOMPtr
<nsIRDFResource
> ordinal
;
336 rv
= gRDFContainerUtils
->IndexToOrdinalResource(aIndex
, getter_AddRefs(ordinal
));
337 if (NS_FAILED(rv
)) return rv
;
339 rv
= mDataSource
->Assert(mContainer
, ordinal
, aElement
, PR_TRUE
);
340 if (NS_FAILED(rv
)) return rv
;
346 RDFContainerImpl::RemoveElementAt(PRInt32 aIndex
, PRBool aRenumber
, nsIRDFNode
** _retval
)
348 if (!mDataSource
|| !mContainer
)
349 return NS_ERROR_NOT_INITIALIZED
;
354 return NS_ERROR_ILLEGAL_VALUE
;
359 rv
= GetCount(&count
);
360 if (NS_FAILED(rv
)) return rv
;
363 return NS_ERROR_ILLEGAL_VALUE
;
365 nsCOMPtr
<nsIRDFResource
> ordinal
;
366 rv
= gRDFContainerUtils
->IndexToOrdinalResource(aIndex
, getter_AddRefs(ordinal
));
367 if (NS_FAILED(rv
)) return rv
;
369 nsCOMPtr
<nsIRDFNode
> old
;
370 rv
= mDataSource
->GetTarget(mContainer
, ordinal
, PR_TRUE
, getter_AddRefs(old
));
371 if (NS_FAILED(rv
)) return rv
;
374 rv
= mDataSource
->Unassert(mContainer
, ordinal
, old
);
375 if (NS_FAILED(rv
)) return rv
;
378 // Now slide the rest of the collection backwards to fill in
379 // the gap. This will have the side effect of completely
380 // renumber the container from index to the end.
381 rv
= Renumber(aIndex
+ 1, -1);
382 if (NS_FAILED(rv
)) return rv
;
392 RDFContainerImpl::IndexOf(nsIRDFNode
*aElement
, PRInt32
*aIndex
)
394 if (!mDataSource
|| !mContainer
)
395 return NS_ERROR_NOT_INITIALIZED
;
397 return gRDFContainerUtils
->IndexOf(mDataSource
, mContainer
,
402 ////////////////////////////////////////////////////////////////////////
405 RDFContainerImpl::RDFContainerImpl()
406 : mDataSource(nsnull
), mContainer(nsnull
)
412 RDFContainerImpl::Init()
414 if (gRefCnt
++ == 0) {
417 rv
= CallGetService(kRDFServiceCID
, &gRDFService
);
419 NS_ERROR("unable to get RDF service");
423 rv
= gRDFService
->GetResource(NS_LITERAL_CSTRING(RDF_NAMESPACE_URI
"nextVal"),
425 if (NS_FAILED(rv
)) return rv
;
427 rv
= CallGetService(kRDFContainerUtilsCID
, &gRDFContainerUtils
);
429 NS_ERROR("unable to get RDF container utils service");
438 RDFContainerImpl::~RDFContainerImpl()
442 fprintf(stdout
, "%d - RDF: RDFContainerImpl\n", gInstanceCount
);
445 NS_IF_RELEASE(mContainer
);
446 NS_IF_RELEASE(mDataSource
);
448 if (--gRefCnt
== 0) {
449 NS_IF_RELEASE(gRDFContainerUtils
);
450 NS_IF_RELEASE(gRDFService
);
451 NS_IF_RELEASE(kRDF_nextVal
);
457 NS_NewRDFContainer(nsIRDFContainer
** aResult
)
459 RDFContainerImpl
* result
= new RDFContainerImpl();
461 return NS_ERROR_OUT_OF_MEMORY
;
477 NS_NewRDFContainer(nsIRDFDataSource
* aDataSource
,
478 nsIRDFResource
* aResource
,
479 nsIRDFContainer
** aResult
)
482 rv
= NS_NewRDFContainer(aResult
);
483 if (NS_FAILED(rv
)) return rv
;
485 rv
= (*aResult
)->Init(aDataSource
, aResource
);
487 NS_RELEASE(*aResult
);
494 RDFContainerImpl::Renumber(PRInt32 aStartIndex
, PRInt32 aIncrement
)
496 if (!mDataSource
|| !mContainer
)
497 return NS_ERROR_NOT_INITIALIZED
;
499 // Renumber the elements in the container starting with
500 // aStartIndex, updating each element's index by aIncrement. For
515 rv
= GetCount(&count
);
516 if (NS_FAILED(rv
)) return rv
;
518 if (aIncrement
> 0) {
519 // Update the container's nextVal to reflect the
520 // renumbering. We do this now if aIncrement > 0 because we'll
521 // want to be able to acknowledge that new elements are in the
523 rv
= SetNextValue(count
+ aIncrement
+ 1);
524 if (NS_FAILED(rv
)) return rv
;
528 if (aIncrement
< 0) {
532 i
= count
; // we're one-indexed.
535 // Note: once we disable notifications, don't exit this method until
536 // enabling notifications
537 nsCOMPtr
<nsIRDFPropagatableDataSource
> propagatable
=
538 do_QueryInterface(mDataSource
);
540 propagatable
->SetPropagateChanges(PR_FALSE
);
543 PRBool err
= PR_FALSE
;
544 while ((err
== PR_FALSE
) && ((aIncrement
< 0) ? (i
<= count
) : (i
>= aStartIndex
)))
546 nsCOMPtr
<nsIRDFResource
> oldOrdinal
;
547 rv
= gRDFContainerUtils
->IndexToOrdinalResource(i
, getter_AddRefs(oldOrdinal
));
554 nsCOMPtr
<nsIRDFResource
> newOrdinal
;
555 rv
= gRDFContainerUtils
->IndexToOrdinalResource(i
+ aIncrement
, getter_AddRefs(newOrdinal
));
562 // Because of aggregation, we need to be paranoid about the
563 // possibility that >1 element may be present per ordinal. If
564 // there _is_ in fact more than one element, they'll all get
565 // assigned to the same new ordinal; i.e., we don't make any
566 // attempt to "clean up" the duplicate numbering. (Doing so
567 // would require two passes.)
568 nsCOMPtr
<nsISimpleEnumerator
> targets
;
569 rv
= mDataSource
->GetTargets(mContainer
, oldOrdinal
, PR_TRUE
, getter_AddRefs(targets
));
578 rv
= targets
->HasMoreElements(&hasMore
);
588 nsCOMPtr
<nsISupports
> isupports
;
589 rv
= targets
->GetNext(getter_AddRefs(isupports
));
596 nsCOMPtr
<nsIRDFNode
> element( do_QueryInterface(isupports
) );
597 NS_ASSERTION(element
!= nsnull
, "something funky in the enumerator");
601 rv
= NS_ERROR_UNEXPECTED
;
605 rv
= mDataSource
->Unassert(mContainer
, oldOrdinal
, element
);
612 rv
= mDataSource
->Assert(mContainer
, newOrdinal
, element
, PR_TRUE
);
623 if ((err
== PR_FALSE
) && (aIncrement
< 0))
625 // Update the container's nextVal to reflect the
626 // renumbering. We do this now if aIncrement < 0 because, up
627 // until this point, we'll want people to be able to find
628 // things that are still "at the end".
629 rv
= SetNextValue(count
+ aIncrement
+ 1);
636 // Note: MUST enable notifications before exiting this method
638 propagatable
->SetPropagateChanges(PR_TRUE
);
641 if (err
== PR_TRUE
) return(rv
);
649 RDFContainerImpl::SetNextValue(PRInt32 aIndex
)
651 if (!mDataSource
|| !mContainer
)
652 return NS_ERROR_NOT_INITIALIZED
;
656 // Remove the current value of nextVal, if there is one.
657 nsCOMPtr
<nsIRDFNode
> nextValNode
;
658 if (NS_SUCCEEDED(rv
= mDataSource
->GetTarget(mContainer
,
661 getter_AddRefs(nextValNode
)))) {
662 if (NS_FAILED(rv
= mDataSource
->Unassert(mContainer
, kRDF_nextVal
, nextValNode
))) {
663 NS_ERROR("unable to update nextVal");
669 s
.AppendInt(aIndex
, 10);
671 nsCOMPtr
<nsIRDFLiteral
> nextVal
;
672 if (NS_FAILED(rv
= gRDFService
->GetLiteral(s
.get(), getter_AddRefs(nextVal
)))) {
673 NS_ERROR("unable to get nextVal literal");
677 rv
= mDataSource
->Assert(mContainer
, kRDF_nextVal
, nextVal
, PR_TRUE
);
678 if (rv
!= NS_RDF_ASSERTION_ACCEPTED
) {
679 NS_ERROR("unable to update nextVal");
680 return NS_ERROR_FAILURE
;
688 RDFContainerImpl::GetNextValue(nsIRDFResource
** aResult
)
690 if (!mDataSource
|| !mContainer
)
691 return NS_ERROR_NOT_INITIALIZED
;
695 // Get the next value, which hangs off of the bag via the
696 // RDF:nextVal property.
697 nsCOMPtr
<nsIRDFNode
> nextValNode
;
698 rv
= mDataSource
->GetTarget(mContainer
, kRDF_nextVal
, PR_TRUE
, getter_AddRefs(nextValNode
));
699 if (NS_FAILED(rv
)) return rv
;
701 if (rv
== NS_RDF_NO_VALUE
)
702 return NS_ERROR_UNEXPECTED
;
704 nsCOMPtr
<nsIRDFLiteral
> nextValLiteral
;
705 rv
= nextValNode
->QueryInterface(NS_GET_IID(nsIRDFLiteral
), getter_AddRefs(nextValLiteral
));
706 if (NS_FAILED(rv
)) return rv
;
709 rv
= nextValLiteral
->GetValueConst(&s
);
710 if (NS_FAILED(rv
)) return rv
;
714 for (const PRUnichar
* p
= s
; *p
!= 0; ++p
) {
715 NS_ASSERTION(*p
>= '0' && *p
<= '9', "not a digit");
716 if (*p
< '0' || *p
> '9')
724 char buf
[sizeof(kRDFNameSpaceURI
) + 16];
725 nsFixedCString
nextValStr(buf
, sizeof(buf
), 0);
726 nextValStr
= kRDFNameSpaceURI
;
727 nextValStr
.Append("_");
728 nextValStr
.AppendInt(nextVal
, 10);
730 rv
= gRDFService
->GetResource(nextValStr
, aResult
);
731 if (NS_FAILED(rv
)) return rv
;
733 // Now increment the RDF:nextVal property.
734 rv
= mDataSource
->Unassert(mContainer
, kRDF_nextVal
, nextValLiteral
);
735 if (NS_FAILED(rv
)) return rv
;
738 nextValStr
.Truncate();
739 nextValStr
.AppendInt(nextVal
, 10);
741 rv
= gRDFService
->GetLiteral(NS_ConvertASCIItoUTF16(nextValStr
).get(), getter_AddRefs(nextValLiteral
));
742 if (NS_FAILED(rv
)) return rv
;
744 rv
= mDataSource
->Assert(mContainer
, kRDF_nextVal
, nextValLiteral
, PR_TRUE
);
745 if (NS_FAILED(rv
)) return rv
;
747 if (RDF_SEQ_LIST_LIMIT
== nextVal
)
749 // focal point for RDF container mutation;
750 // basically, provide a hint to allow for fast access
751 nsCOMPtr
<nsIRDFInMemoryDataSource
> inMem
= do_QueryInterface(mDataSource
);
754 // ignore error; failure just means slower access
755 (void)inMem
->EnsureFastContainment(mContainer
);