From 5f8de423f190bbb79a62f804151bc24824fa32d8 Mon Sep 17 00:00:00 2001 From: "Matt A. Tobin" Date: Fri, 2 Feb 2018 04:16:08 -0500 Subject: Add m-esr52 at 52.6.0 --- rdf/base/moz.build | 59 + rdf/base/nsCompositeDataSource.cpp | 1358 ++++++++++++++++++++ rdf/base/nsContainerEnumerator.cpp | 263 ++++ rdf/base/nsDefaultResourceFactory.cpp | 30 + rdf/base/nsIRDFCompositeDataSource.idl | 68 + rdf/base/nsIRDFContainer.idl | 94 ++ rdf/base/nsIRDFContainerUtils.idl | 82 ++ rdf/base/nsIRDFContentSink.h | 62 + rdf/base/nsIRDFDataSource.idl | 181 +++ rdf/base/nsIRDFDelegateFactory.idl | 40 + rdf/base/nsIRDFInMemoryDataSource.idl | 14 + rdf/base/nsIRDFInferDataSource.idl | 28 + rdf/base/nsIRDFLiteral.idl | 68 + rdf/base/nsIRDFNode.idl | 15 + rdf/base/nsIRDFObserver.idl | 94 ++ rdf/base/nsIRDFPropagatableDataSource.idl | 27 + rdf/base/nsIRDFPurgeableDataSource.idl | 19 + rdf/base/nsIRDFRemoteDataSource.idl | 44 + rdf/base/nsIRDFResource.idl | 81 ++ rdf/base/nsIRDFService.idl | 146 +++ rdf/base/nsIRDFXMLParser.idl | 33 + rdf/base/nsIRDFXMLSerializer.idl | 27 + rdf/base/nsIRDFXMLSink.idl | 130 ++ rdf/base/nsIRDFXMLSource.idl | 20 + rdf/base/nsInMemoryDataSource.cpp | 1964 +++++++++++++++++++++++++++++ rdf/base/nsNameSpaceMap.cpp | 64 + rdf/base/nsNameSpaceMap.h | 98 ++ rdf/base/nsRDFBaseDataSources.h | 32 + rdf/base/nsRDFContainer.cpp | 726 +++++++++++ rdf/base/nsRDFContainerUtils.cpp | 515 ++++++++ rdf/base/nsRDFContentSink.cpp | 1476 ++++++++++++++++++++++ rdf/base/nsRDFContentSinkAtomList.h | 18 + rdf/base/nsRDFService.cpp | 1551 +++++++++++++++++++++++ rdf/base/nsRDFService.h | 79 ++ rdf/base/nsRDFXMLDataSource.cpp | 1179 +++++++++++++++++ rdf/base/nsRDFXMLParser.cpp | 137 ++ rdf/base/nsRDFXMLParser.h | 31 + rdf/base/nsRDFXMLSerializer.cpp | 1127 +++++++++++++++++ rdf/base/nsRDFXMLSerializer.h | 117 ++ rdf/base/rdf.h | 61 + rdf/base/rdfIDataSource.idl | 38 + rdf/base/rdfISerializer.idl | 30 + rdf/base/rdfITripleVisitor.idl | 31 + rdf/base/rdfTriplesSerializer.cpp | 151 +++ rdf/base/rdfutil.cpp | 111 ++ rdf/base/rdfutil.h | 40 + 46 files changed, 12559 insertions(+) create mode 100644 rdf/base/moz.build create mode 100644 rdf/base/nsCompositeDataSource.cpp create mode 100644 rdf/base/nsContainerEnumerator.cpp create mode 100644 rdf/base/nsDefaultResourceFactory.cpp create mode 100644 rdf/base/nsIRDFCompositeDataSource.idl create mode 100644 rdf/base/nsIRDFContainer.idl create mode 100644 rdf/base/nsIRDFContainerUtils.idl create mode 100644 rdf/base/nsIRDFContentSink.h create mode 100644 rdf/base/nsIRDFDataSource.idl create mode 100644 rdf/base/nsIRDFDelegateFactory.idl create mode 100644 rdf/base/nsIRDFInMemoryDataSource.idl create mode 100644 rdf/base/nsIRDFInferDataSource.idl create mode 100644 rdf/base/nsIRDFLiteral.idl create mode 100644 rdf/base/nsIRDFNode.idl create mode 100644 rdf/base/nsIRDFObserver.idl create mode 100644 rdf/base/nsIRDFPropagatableDataSource.idl create mode 100644 rdf/base/nsIRDFPurgeableDataSource.idl create mode 100644 rdf/base/nsIRDFRemoteDataSource.idl create mode 100644 rdf/base/nsIRDFResource.idl create mode 100644 rdf/base/nsIRDFService.idl create mode 100644 rdf/base/nsIRDFXMLParser.idl create mode 100644 rdf/base/nsIRDFXMLSerializer.idl create mode 100644 rdf/base/nsIRDFXMLSink.idl create mode 100644 rdf/base/nsIRDFXMLSource.idl create mode 100644 rdf/base/nsInMemoryDataSource.cpp create mode 100644 rdf/base/nsNameSpaceMap.cpp create mode 100644 rdf/base/nsNameSpaceMap.h create mode 100644 rdf/base/nsRDFBaseDataSources.h create mode 100644 rdf/base/nsRDFContainer.cpp create mode 100644 rdf/base/nsRDFContainerUtils.cpp create mode 100644 rdf/base/nsRDFContentSink.cpp create mode 100644 rdf/base/nsRDFContentSinkAtomList.h create mode 100644 rdf/base/nsRDFService.cpp create mode 100644 rdf/base/nsRDFService.h create mode 100644 rdf/base/nsRDFXMLDataSource.cpp create mode 100644 rdf/base/nsRDFXMLParser.cpp create mode 100644 rdf/base/nsRDFXMLParser.h create mode 100644 rdf/base/nsRDFXMLSerializer.cpp create mode 100644 rdf/base/nsRDFXMLSerializer.h create mode 100644 rdf/base/rdf.h create mode 100644 rdf/base/rdfIDataSource.idl create mode 100644 rdf/base/rdfISerializer.idl create mode 100644 rdf/base/rdfITripleVisitor.idl create mode 100644 rdf/base/rdfTriplesSerializer.cpp create mode 100644 rdf/base/rdfutil.cpp create mode 100644 rdf/base/rdfutil.h (limited to 'rdf/base') diff --git a/rdf/base/moz.build b/rdf/base/moz.build new file mode 100644 index 000000000..17e0cdd2d --- /dev/null +++ b/rdf/base/moz.build @@ -0,0 +1,59 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +XPIDL_SOURCES += [ + 'nsIRDFCompositeDataSource.idl', + 'nsIRDFContainer.idl', + 'nsIRDFContainerUtils.idl', + 'nsIRDFDataSource.idl', + 'nsIRDFDelegateFactory.idl', + 'nsIRDFInferDataSource.idl', + 'nsIRDFInMemoryDataSource.idl', + 'nsIRDFLiteral.idl', + 'nsIRDFNode.idl', + 'nsIRDFObserver.idl', + 'nsIRDFPropagatableDataSource.idl', + 'nsIRDFPurgeableDataSource.idl', + 'nsIRDFRemoteDataSource.idl', + 'nsIRDFResource.idl', + 'nsIRDFService.idl', + 'nsIRDFXMLParser.idl', + 'nsIRDFXMLSerializer.idl', + 'nsIRDFXMLSink.idl', + 'nsIRDFXMLSource.idl', + 'rdfIDataSource.idl', + 'rdfISerializer.idl', + 'rdfITripleVisitor.idl', +] + +XPIDL_MODULE = 'rdf' + +EXPORTS += [ + 'nsIRDFContentSink.h', + 'rdf.h', +] + +UNIFIED_SOURCES += [ + 'nsCompositeDataSource.cpp', + 'nsContainerEnumerator.cpp', + 'nsDefaultResourceFactory.cpp', + 'nsInMemoryDataSource.cpp', + 'nsNameSpaceMap.cpp', + 'nsRDFContainer.cpp', + 'nsRDFContainerUtils.cpp', + 'nsRDFContentSink.cpp', + 'nsRDFService.cpp', + 'nsRDFXMLDataSource.cpp', + 'nsRDFXMLParser.cpp', + 'nsRDFXMLSerializer.cpp', + 'rdfTriplesSerializer.cpp', + 'rdfutil.cpp', +] + +FINAL_LIBRARY = 'xul' + +if CONFIG['GNU_CXX']: + CXXFLAGS += ['-Wno-error=shadow'] diff --git a/rdf/base/nsCompositeDataSource.cpp b/rdf/base/nsCompositeDataSource.cpp new file mode 100644 index 000000000..37167d356 --- /dev/null +++ b/rdf/base/nsCompositeDataSource.cpp @@ -0,0 +1,1358 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + + A simple composite data source implementation. A composit data + source is just a strategy for combining individual data sources into + a collective graph. + + + 1) A composite data source holds a sequence of data sources. The set + of data sources can be specified during creation of the + database. Data sources can also be added/deleted from a database + later. + + 2) The aggregation mechanism is based on simple super-positioning of + the graphs from the datasources. If there is a conflict (i.e., + data source A has a true arc from foo to bar while data source B + has a false arc from foo to bar), the data source that it earlier + in the sequence wins. + + The implementation below doesn't really do this and needs to be + fixed. + +*/ + +#include "xpcom-config.h" +#include "nsCOMPtr.h" +#include "nsIComponentManager.h" +#include "nsIRDFCompositeDataSource.h" +#include "nsIRDFNode.h" +#include "nsIRDFObserver.h" +#include "nsIRDFRemoteDataSource.h" +#include "nsTArray.h" +#include "nsCOMArray.h" +#include "nsArrayEnumerator.h" +#include "nsXPIDLString.h" +#include "rdf.h" +#include "nsCycleCollectionParticipant.h" + +#include "nsEnumeratorUtils.h" + +#include "mozilla/Logging.h" +#include "prprf.h" +#include +mozilla::LazyLogModule nsRDFLog("RDF"); + +//---------------------------------------------------------------------- +// +// CompositeDataSourceImpl +// + +class CompositeEnumeratorImpl; +class CompositeArcsInOutEnumeratorImpl; +class CompositeAssertionEnumeratorImpl; + +class CompositeDataSourceImpl : public nsIRDFCompositeDataSource, + public nsIRDFObserver +{ +public: + CompositeDataSourceImpl(void); + explicit CompositeDataSourceImpl(char** dataSources); + + // nsISupports interface + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(CompositeDataSourceImpl, + nsIRDFCompositeDataSource) + + // nsIRDFDataSource interface + NS_DECL_NSIRDFDATASOURCE + + // nsIRDFCompositeDataSource interface + NS_DECL_NSIRDFCOMPOSITEDATASOURCE + + // nsIRDFObserver interface + NS_DECL_NSIRDFOBSERVER + + bool HasAssertionN(int n, nsIRDFResource* source, + nsIRDFResource* property, + nsIRDFNode* target, + bool tv); + +protected: + nsCOMArray mObservers; + nsCOMArray mDataSources; + + bool mAllowNegativeAssertions; + bool mCoalesceDuplicateArcs; + int32_t mUpdateBatchNest; + + virtual ~CompositeDataSourceImpl() {} + + friend class CompositeEnumeratorImpl; + friend class CompositeArcsInOutEnumeratorImpl; + friend class CompositeAssertionEnumeratorImpl; +}; + +//---------------------------------------------------------------------- +// +// CompositeEnumeratorImpl +// + +class CompositeEnumeratorImpl : public nsISimpleEnumerator +{ + // nsISupports + NS_DECL_ISUPPORTS + + // nsISimpleEnumerator interface + NS_DECL_NSISIMPLEENUMERATOR + + // pure abstract methods to be overridden + virtual nsresult + GetEnumerator(nsIRDFDataSource* aDataSource, nsISimpleEnumerator** aResult) = 0; + + virtual nsresult + HasNegation(nsIRDFDataSource* aDataSource, nsIRDFNode* aNode, bool* aResult) = 0; + +protected: + CompositeEnumeratorImpl(CompositeDataSourceImpl* aCompositeDataSource, + bool aAllowNegativeAssertions, + bool aCoalesceDuplicateArcs); + + virtual ~CompositeEnumeratorImpl(); + + CompositeDataSourceImpl* mCompositeDataSource; + + nsISimpleEnumerator* mCurrent; + nsIRDFNode* mResult; + int32_t mNext; + AutoTArray, 8> mAlreadyReturned; + bool mAllowNegativeAssertions; + bool mCoalesceDuplicateArcs; +}; + + +CompositeEnumeratorImpl::CompositeEnumeratorImpl(CompositeDataSourceImpl* aCompositeDataSource, + bool aAllowNegativeAssertions, + bool aCoalesceDuplicateArcs) + : mCompositeDataSource(aCompositeDataSource), + mCurrent(nullptr), + mResult(nullptr), + mNext(0), + mAllowNegativeAssertions(aAllowNegativeAssertions), + mCoalesceDuplicateArcs(aCoalesceDuplicateArcs) +{ + NS_ADDREF(mCompositeDataSource); +} + + +CompositeEnumeratorImpl::~CompositeEnumeratorImpl(void) +{ + NS_IF_RELEASE(mCurrent); + NS_IF_RELEASE(mResult); + NS_RELEASE(mCompositeDataSource); +} + +NS_IMPL_ADDREF(CompositeEnumeratorImpl) +NS_IMPL_RELEASE(CompositeEnumeratorImpl) +NS_IMPL_QUERY_INTERFACE(CompositeEnumeratorImpl, nsISimpleEnumerator) + +NS_IMETHODIMP +CompositeEnumeratorImpl::HasMoreElements(bool* aResult) +{ + NS_PRECONDITION(aResult != nullptr, "null ptr"); + if (! aResult) + return NS_ERROR_NULL_POINTER; + + nsresult rv; + + // If we've already queued up a next target, then yep, there are + // more elements. + if (mResult) { + *aResult = true; + return NS_OK; + } + + // Otherwise, we'll need to find a next target, switching cursors + // if necessary. + for ( ; mNext < mCompositeDataSource->mDataSources.Count(); ++mNext) { + if (! mCurrent) { + // We don't have a current enumerator, so create a new one on + // the next data source. + nsIRDFDataSource* datasource = + mCompositeDataSource->mDataSources[mNext]; + + rv = GetEnumerator(datasource, &mCurrent); + if (NS_FAILED(rv)) return rv; + if (rv == NS_RDF_NO_VALUE) + continue; + + NS_ASSERTION(mCurrent != nullptr, "you're always supposed to return an enumerator from GetEnumerator, punk."); + if (! mCurrent) + continue; + } + + do { + int32_t i; + + bool hasMore; + rv = mCurrent->HasMoreElements(&hasMore); + if (NS_FAILED(rv)) return rv; + + // Is the current enumerator depleted? + if (! hasMore) { + NS_RELEASE(mCurrent); + break; + } + + // Even if the current enumerator has more elements, we still + // need to check that the current element isn't masked by + // a negation in an earlier data source. + + // "Peek" ahead and pull out the next target. + nsCOMPtr result; + rv = mCurrent->GetNext(getter_AddRefs(result)); + if (NS_FAILED(rv)) return rv; + + rv = result->QueryInterface(NS_GET_IID(nsIRDFNode), (void**) &mResult); + if (NS_FAILED(rv)) return rv; + + if (mAllowNegativeAssertions) + { + // See if any previous data source negates this + bool hasNegation = false; + for (i = mNext - 1; i >= 0; --i) + { + nsIRDFDataSource* datasource = + mCompositeDataSource->mDataSources[i]; + + rv = HasNegation(datasource, mResult, &hasNegation); + if (NS_FAILED(rv)) return rv; + + if (hasNegation) + break; + } + + // if so, we've gotta keep looking + if (hasNegation) + { + NS_RELEASE(mResult); + continue; + } + } + + if (mCoalesceDuplicateArcs) + { + // Now see if we've returned it once already. + // XXX N.B. performance here...may want to hash if things get large? + bool alreadyReturned = false; + for (i = mAlreadyReturned.Length() - 1; i >= 0; --i) + { + if (mAlreadyReturned[i] == mResult) + { + alreadyReturned = true; + break; + } + } + if (alreadyReturned) + { + NS_RELEASE(mResult); + continue; + } + } + + // If we get here, then we've really found one. It'll + // remain cached in mResult until GetNext() sucks it out. + *aResult = true; + + // Remember that we returned it, so we don't return duplicates. + + // XXX I wonder if we should make unique-checking be + // optional. This could get to be pretty expensive (this + // implementation turns iteration into O(n^2)). + + if (mCoalesceDuplicateArcs) + { + mAlreadyReturned.AppendElement(mResult); + } + + return NS_OK; + } while (1); + } + + // if we get here, there aren't any elements left. + *aResult = false; + return NS_OK; +} + + +NS_IMETHODIMP +CompositeEnumeratorImpl::GetNext(nsISupports** aResult) +{ + nsresult rv; + + bool hasMore; + rv = HasMoreElements(&hasMore); + if (NS_FAILED(rv)) return rv; + + if (! hasMore) + return NS_ERROR_UNEXPECTED; + + // Don't AddRef: we "transfer" ownership to the caller + *aResult = mResult; + mResult = nullptr; + + return NS_OK; +} + +//---------------------------------------------------------------------- +// +// CompositeArcsInOutEnumeratorImpl +// +// + +class CompositeArcsInOutEnumeratorImpl : public CompositeEnumeratorImpl +{ +public: + enum Type { eArcsIn, eArcsOut }; + + virtual ~CompositeArcsInOutEnumeratorImpl(); + + virtual nsresult + GetEnumerator(nsIRDFDataSource* aDataSource, nsISimpleEnumerator** aResult); + + virtual nsresult + HasNegation(nsIRDFDataSource* aDataSource, nsIRDFNode* aNode, bool* aResult); + + CompositeArcsInOutEnumeratorImpl(CompositeDataSourceImpl* aCompositeDataSource, + nsIRDFNode* aNode, + Type aType, + bool aAllowNegativeAssertions, + bool aCoalesceDuplicateArcs); + +private: + nsIRDFNode* mNode; + Type mType; +}; + + +CompositeArcsInOutEnumeratorImpl::CompositeArcsInOutEnumeratorImpl( + CompositeDataSourceImpl* aCompositeDataSource, + nsIRDFNode* aNode, + Type aType, + bool aAllowNegativeAssertions, + bool aCoalesceDuplicateArcs) + : CompositeEnumeratorImpl(aCompositeDataSource, aAllowNegativeAssertions, aCoalesceDuplicateArcs), + mNode(aNode), + mType(aType) +{ + NS_ADDREF(mNode); +} + +CompositeArcsInOutEnumeratorImpl::~CompositeArcsInOutEnumeratorImpl() +{ + NS_RELEASE(mNode); +} + + +nsresult +CompositeArcsInOutEnumeratorImpl::GetEnumerator( + nsIRDFDataSource* aDataSource, + nsISimpleEnumerator** aResult) +{ + if (mType == eArcsIn) { + return aDataSource->ArcLabelsIn(mNode, aResult); + } + else { + nsCOMPtr resource( do_QueryInterface(mNode) ); + return aDataSource->ArcLabelsOut(resource, aResult); + } +} + +nsresult +CompositeArcsInOutEnumeratorImpl::HasNegation( + nsIRDFDataSource* aDataSource, + nsIRDFNode* aNode, + bool* aResult) +{ + *aResult = false; + return NS_OK; +} + + +//---------------------------------------------------------------------- +// +// CompositeAssertionEnumeratorImpl +// + +class CompositeAssertionEnumeratorImpl : public CompositeEnumeratorImpl +{ +public: + virtual nsresult + GetEnumerator(nsIRDFDataSource* aDataSource, nsISimpleEnumerator** aResult); + + virtual nsresult + HasNegation(nsIRDFDataSource* aDataSource, nsIRDFNode* aNode, bool* aResult); + + CompositeAssertionEnumeratorImpl(CompositeDataSourceImpl* aCompositeDataSource, + nsIRDFResource* aSource, + nsIRDFResource* aProperty, + nsIRDFNode* aTarget, + bool aTruthValue, + bool aAllowNegativeAssertions, + bool aCoalesceDuplicateArcs); + + virtual ~CompositeAssertionEnumeratorImpl(); + +private: + nsIRDFResource* mSource; + nsIRDFResource* mProperty; + nsIRDFNode* mTarget; + bool mTruthValue; +}; + + +CompositeAssertionEnumeratorImpl::CompositeAssertionEnumeratorImpl( + CompositeDataSourceImpl* aCompositeDataSource, + nsIRDFResource* aSource, + nsIRDFResource* aProperty, + nsIRDFNode* aTarget, + bool aTruthValue, + bool aAllowNegativeAssertions, + bool aCoalesceDuplicateArcs) + : CompositeEnumeratorImpl(aCompositeDataSource, aAllowNegativeAssertions, aCoalesceDuplicateArcs), + mSource(aSource), + mProperty(aProperty), + mTarget(aTarget), + mTruthValue(aTruthValue) +{ + NS_IF_ADDREF(mSource); + NS_ADDREF(mProperty); // always must be specified + NS_IF_ADDREF(mTarget); +} + +CompositeAssertionEnumeratorImpl::~CompositeAssertionEnumeratorImpl() +{ + NS_IF_RELEASE(mSource); + NS_RELEASE(mProperty); + NS_IF_RELEASE(mTarget); +} + + +nsresult +CompositeAssertionEnumeratorImpl::GetEnumerator( + nsIRDFDataSource* aDataSource, + nsISimpleEnumerator** aResult) +{ + if (mSource) { + return aDataSource->GetTargets(mSource, mProperty, mTruthValue, aResult); + } + else { + return aDataSource->GetSources(mProperty, mTarget, mTruthValue, aResult); + } +} + +nsresult +CompositeAssertionEnumeratorImpl::HasNegation( + nsIRDFDataSource* aDataSource, + nsIRDFNode* aNode, + bool* aResult) +{ + if (mSource) { + return aDataSource->HasAssertion(mSource, mProperty, aNode, !mTruthValue, aResult); + } + else { + nsCOMPtr source( do_QueryInterface(aNode) ); + return aDataSource->HasAssertion(source, mProperty, mTarget, !mTruthValue, aResult); + } +} + +//////////////////////////////////////////////////////////////////////// + +nsresult +NS_NewRDFCompositeDataSource(nsIRDFCompositeDataSource** result) +{ + CompositeDataSourceImpl* db = new CompositeDataSourceImpl(); + if (! db) + return NS_ERROR_OUT_OF_MEMORY; + + *result = db; + NS_ADDREF(*result); + return NS_OK; +} + + +CompositeDataSourceImpl::CompositeDataSourceImpl(void) + : mAllowNegativeAssertions(true), + mCoalesceDuplicateArcs(true), + mUpdateBatchNest(0) +{ +} + +//---------------------------------------------------------------------- +// +// nsISupports interface +// + +NS_IMPL_CYCLE_COLLECTION_CLASS(CompositeDataSourceImpl) + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(CompositeDataSourceImpl) + uint32_t i, count = tmp->mDataSources.Count(); + for (i = count; i > 0; --i) { + tmp->mDataSources[i - 1]->RemoveObserver(tmp); + tmp->mDataSources.RemoveObjectAt(i - 1); + } + NS_IMPL_CYCLE_COLLECTION_UNLINK(mObservers); +NS_IMPL_CYCLE_COLLECTION_UNLINK_END +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(CompositeDataSourceImpl) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mObservers) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDataSources) +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + + +NS_IMPL_CYCLE_COLLECTING_ADDREF(CompositeDataSourceImpl) +NS_IMPL_CYCLE_COLLECTING_RELEASE(CompositeDataSourceImpl) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CompositeDataSourceImpl) + NS_INTERFACE_MAP_ENTRY(nsIRDFCompositeDataSource) + NS_INTERFACE_MAP_ENTRY(nsIRDFDataSource) + NS_INTERFACE_MAP_ENTRY(nsIRDFObserver) + NS_INTERFACE_MAP_ENTRY(nsIRDFCompositeDataSource) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIRDFCompositeDataSource) +NS_INTERFACE_MAP_END + + +//---------------------------------------------------------------------- +// +// nsIRDFDataSource interface +// + +NS_IMETHODIMP +CompositeDataSourceImpl::GetURI(char* *uri) +{ + *uri = nullptr; + return NS_OK; +} + +NS_IMETHODIMP +CompositeDataSourceImpl::GetSource(nsIRDFResource* property, + nsIRDFNode* target, + bool tv, + nsIRDFResource** source) +{ + if (!mAllowNegativeAssertions && !tv) + return(NS_RDF_NO_VALUE); + + int32_t count = mDataSources.Count(); + for (int32_t i = 0; i < count; ++i) { + nsresult rv; + rv = mDataSources[i]->GetSource(property, target, tv, source); + if (NS_FAILED(rv)) return rv; + + if (rv == NS_RDF_NO_VALUE) + continue; + + if (!mAllowNegativeAssertions) return(NS_OK); + + // okay, found it. make sure we don't have the opposite + // asserted in a more local data source + if (!HasAssertionN(count-1, *source, property, target, !tv)) + return NS_OK; + + NS_RELEASE(*source); + return NS_RDF_NO_VALUE; + } + return NS_RDF_NO_VALUE; +} + +NS_IMETHODIMP +CompositeDataSourceImpl::GetSources(nsIRDFResource* aProperty, + nsIRDFNode* aTarget, + bool aTruthValue, + nsISimpleEnumerator** aResult) +{ + NS_PRECONDITION(aProperty != nullptr, "null ptr"); + if (! aProperty) + return NS_ERROR_NULL_POINTER; + + NS_PRECONDITION(aTarget != nullptr, "null ptr"); + if (! aTarget) + return NS_ERROR_NULL_POINTER; + + NS_PRECONDITION(aResult != nullptr, "null ptr"); + if (! aResult) + return NS_ERROR_NULL_POINTER; + + if (! mAllowNegativeAssertions && ! aTruthValue) + return(NS_RDF_NO_VALUE); + + *aResult = new CompositeAssertionEnumeratorImpl(this, nullptr, aProperty, + aTarget, aTruthValue, + mAllowNegativeAssertions, + mCoalesceDuplicateArcs); + + if (! *aResult) + return NS_ERROR_OUT_OF_MEMORY; + + NS_ADDREF(*aResult); + return NS_OK; +} + +NS_IMETHODIMP +CompositeDataSourceImpl::GetTarget(nsIRDFResource* aSource, + nsIRDFResource* aProperty, + bool aTruthValue, + nsIRDFNode** aResult) +{ + NS_PRECONDITION(aSource != nullptr, "null ptr"); + if (! aSource) + return NS_ERROR_NULL_POINTER; + + NS_PRECONDITION(aProperty != nullptr, "null ptr"); + if (! aProperty) + return NS_ERROR_NULL_POINTER; + + NS_PRECONDITION(aResult != nullptr, "null ptr"); + if (! aResult) + return NS_ERROR_NULL_POINTER; + + if (! mAllowNegativeAssertions && ! aTruthValue) + return(NS_RDF_NO_VALUE); + + int32_t count = mDataSources.Count(); + for (int32_t i = 0; i < count; ++i) { + nsresult rv; + rv = mDataSources[i]->GetTarget(aSource, aProperty, aTruthValue, + aResult); + if (NS_FAILED(rv)) + return rv; + + if (rv == NS_OK) { + // okay, found it. make sure we don't have the opposite + // asserted in an earlier data source + + if (mAllowNegativeAssertions) { + if (HasAssertionN(count-1, aSource, aProperty, *aResult, !aTruthValue)) { + // whoops, it's been negated. + NS_RELEASE(*aResult); + return NS_RDF_NO_VALUE; + } + } + return NS_OK; + } + } + + // Otherwise, we couldn't find it at all. + return NS_RDF_NO_VALUE; +} + +bool +CompositeDataSourceImpl::HasAssertionN(int n, + nsIRDFResource* aSource, + nsIRDFResource* aProperty, + nsIRDFNode* aTarget, + bool aTruthValue) +{ + nsresult rv; + for (int32_t m = 0; m < n; ++m) { + bool result; + rv = mDataSources[m]->HasAssertion(aSource, aProperty, aTarget, + aTruthValue, &result); + if (NS_FAILED(rv)) + return false; + + // found it! + if (result) + return true; + } + return false; +} + + + +NS_IMETHODIMP +CompositeDataSourceImpl::GetTargets(nsIRDFResource* aSource, + nsIRDFResource* aProperty, + bool aTruthValue, + nsISimpleEnumerator** aResult) +{ + NS_PRECONDITION(aSource != nullptr, "null ptr"); + if (! aSource) + return NS_ERROR_NULL_POINTER; + + NS_PRECONDITION(aProperty != nullptr, "null ptr"); + if (! aProperty) + return NS_ERROR_NULL_POINTER; + + NS_PRECONDITION(aResult != nullptr, "null ptr"); + if (! aResult) + return NS_ERROR_NULL_POINTER; + + if (! mAllowNegativeAssertions && ! aTruthValue) + return(NS_RDF_NO_VALUE); + + *aResult = + new CompositeAssertionEnumeratorImpl(this, + aSource, aProperty, nullptr, + aTruthValue, + mAllowNegativeAssertions, + mCoalesceDuplicateArcs); + + if (! *aResult) + return NS_ERROR_OUT_OF_MEMORY; + + NS_ADDREF(*aResult); + return NS_OK; +} + +NS_IMETHODIMP +CompositeDataSourceImpl::Assert(nsIRDFResource* aSource, + nsIRDFResource* aProperty, + nsIRDFNode* aTarget, + bool aTruthValue) +{ + NS_PRECONDITION(aSource != nullptr, "null ptr"); + if (! aSource) + return NS_ERROR_NULL_POINTER; + + NS_PRECONDITION(aProperty != nullptr, "null ptr"); + if (! aProperty) + return NS_ERROR_NULL_POINTER; + + NS_PRECONDITION(aTarget != nullptr, "null ptr"); + if (! aTarget) + return NS_ERROR_NULL_POINTER; + + if (! mAllowNegativeAssertions && ! aTruthValue) + return(NS_RDF_ASSERTION_REJECTED); + + nsresult rv; + + // XXX Need to add back the stuff for unblocking ... + + // We iterate backwards from the last data source which was added + // ("the most remote") to the first ("the most local"), trying to + // apply the assertion in each. + for (int32_t i = mDataSources.Count() - 1; i >= 0; --i) { + rv = mDataSources[i]->Assert(aSource, aProperty, aTarget, aTruthValue); + if (NS_RDF_ASSERTION_ACCEPTED == rv) + return rv; + + if (NS_FAILED(rv)) + return rv; + } + + // nobody wanted to accept it + return NS_RDF_ASSERTION_REJECTED; +} + +NS_IMETHODIMP +CompositeDataSourceImpl::Unassert(nsIRDFResource* aSource, + nsIRDFResource* aProperty, + nsIRDFNode* aTarget) +{ + NS_PRECONDITION(aSource != nullptr, "null ptr"); + if (! aSource) + return NS_ERROR_NULL_POINTER; + + NS_PRECONDITION(aProperty != nullptr, "null ptr"); + if (! aProperty) + return NS_ERROR_NULL_POINTER; + + NS_PRECONDITION(aTarget != nullptr, "null ptr"); + if (! aTarget) + return NS_ERROR_NULL_POINTER; + + nsresult rv; + + // Iterate through each of the datasources, starting with "the + // most local" and moving to "the most remote". If _any_ of the + // datasources have the assertion, attempt to unassert it. + bool unasserted = true; + int32_t i; + int32_t count = mDataSources.Count(); + for (i = 0; i < count; ++i) { + nsIRDFDataSource* ds = mDataSources[i]; + + bool hasAssertion; + rv = ds->HasAssertion(aSource, aProperty, aTarget, true, &hasAssertion); + if (NS_FAILED(rv)) return rv; + + if (hasAssertion) { + rv = ds->Unassert(aSource, aProperty, aTarget); + if (NS_FAILED(rv)) return rv; + + if (rv != NS_RDF_ASSERTION_ACCEPTED) { + unasserted = false; + break; + } + } + } + + // Either none of the datasources had it, or they were all willing + // to let it be unasserted. + if (unasserted) + return NS_RDF_ASSERTION_ACCEPTED; + + // If we get here, one of the datasources already had the + // assertion, and was adamant about not letting us remove + // it. Iterate from the "most local" to the "most remote" + // attempting to assert the negation... + for (i = 0; i < count; ++i) { + rv = mDataSources[i]->Assert(aSource, aProperty, aTarget, false); + if (NS_FAILED(rv)) return rv; + + // Did it take? + if (rv == NS_RDF_ASSERTION_ACCEPTED) + return rv; + } + + // Couln't get anyone to accept the negation, either. + return NS_RDF_ASSERTION_REJECTED; +} + +NS_IMETHODIMP +CompositeDataSourceImpl::Change(nsIRDFResource* aSource, + nsIRDFResource* aProperty, + nsIRDFNode* aOldTarget, + nsIRDFNode* aNewTarget) +{ + NS_PRECONDITION(aSource != nullptr, "null ptr"); + if (! aSource) + return NS_ERROR_NULL_POINTER; + + NS_PRECONDITION(aProperty != nullptr, "null ptr"); + if (! aProperty) + return NS_ERROR_NULL_POINTER; + + NS_PRECONDITION(aOldTarget != nullptr, "null ptr"); + if (! aOldTarget) + return NS_ERROR_NULL_POINTER; + + NS_PRECONDITION(aNewTarget != nullptr, "null ptr"); + if (! aNewTarget) + return NS_ERROR_NULL_POINTER; + + nsresult rv; + + // XXX So we're assuming that a datasource _must_ accept the + // atomic change; i.e., we can't split it up across two + // datasources. That sucks. + + // We iterate backwards from the last data source which was added + // ("the most remote") to the first ("the most local"), trying to + // apply the change in each. + for (int32_t i = mDataSources.Count() - 1; i >= 0; --i) { + rv = mDataSources[i]->Change(aSource, aProperty, aOldTarget, aNewTarget); + if (NS_RDF_ASSERTION_ACCEPTED == rv) + return rv; + + if (NS_FAILED(rv)) + return rv; + } + + // nobody wanted to accept it + return NS_RDF_ASSERTION_REJECTED; +} + +NS_IMETHODIMP +CompositeDataSourceImpl::Move(nsIRDFResource* aOldSource, + nsIRDFResource* aNewSource, + nsIRDFResource* aProperty, + nsIRDFNode* aTarget) +{ + NS_PRECONDITION(aOldSource != nullptr, "null ptr"); + if (! aOldSource) + return NS_ERROR_NULL_POINTER; + + NS_PRECONDITION(aNewSource != nullptr, "null ptr"); + if (! aNewSource) + return NS_ERROR_NULL_POINTER; + + NS_PRECONDITION(aProperty != nullptr, "null ptr"); + if (! aProperty) + return NS_ERROR_NULL_POINTER; + + NS_PRECONDITION(aTarget != nullptr, "null ptr"); + if (! aTarget) + return NS_ERROR_NULL_POINTER; + + nsresult rv; + + // XXX So we're assuming that a datasource _must_ accept the + // atomic move; i.e., we can't split it up across two + // datasources. That sucks. + + // We iterate backwards from the last data source which was added + // ("the most remote") to the first ("the most local"), trying to + // apply the assertion in each. + for (int32_t i = mDataSources.Count() - 1; i >= 0; --i) { + rv = mDataSources[i]->Move(aOldSource, aNewSource, aProperty, aTarget); + if (NS_RDF_ASSERTION_ACCEPTED == rv) + return rv; + + if (NS_FAILED(rv)) + return rv; + } + + // nobody wanted to accept it + return NS_RDF_ASSERTION_REJECTED; +} + + +NS_IMETHODIMP +CompositeDataSourceImpl::HasAssertion(nsIRDFResource* aSource, + nsIRDFResource* aProperty, + nsIRDFNode* aTarget, + bool aTruthValue, + bool* aResult) +{ + NS_PRECONDITION(aSource != nullptr, "null ptr"); + if (! aSource) + return NS_ERROR_NULL_POINTER; + + NS_PRECONDITION(aProperty != nullptr, "null ptr"); + if (! aProperty) + return NS_ERROR_NULL_POINTER; + + NS_PRECONDITION(aResult != nullptr, "null ptr"); + if (! aResult) + return NS_ERROR_NULL_POINTER; + + if (! mAllowNegativeAssertions && ! aTruthValue) + { + *aResult = false; + return(NS_OK); + } + + nsresult rv; + + // Otherwise, look through all the data sources to see if anyone + // has the positive... + int32_t count = mDataSources.Count(); + for (int32_t i = 0; i < count; ++i) { + nsIRDFDataSource* datasource = mDataSources[i]; + rv = datasource->HasAssertion(aSource, aProperty, aTarget, aTruthValue, aResult); + if (NS_FAILED(rv)) return rv; + + if (*aResult) + return NS_OK; + + if (mAllowNegativeAssertions) + { + bool hasNegation; + rv = datasource->HasAssertion(aSource, aProperty, aTarget, !aTruthValue, &hasNegation); + if (NS_FAILED(rv)) return rv; + + if (hasNegation) + { + *aResult = false; + return NS_OK; + } + } + } + + // If we get here, nobody had the assertion at all + *aResult = false; + return NS_OK; +} + +NS_IMETHODIMP +CompositeDataSourceImpl::AddObserver(nsIRDFObserver* aObserver) +{ + NS_PRECONDITION(aObserver != nullptr, "null ptr"); + if (! aObserver) + return NS_ERROR_NULL_POINTER; + + // XXX ensure uniqueness? + mObservers.AppendObject(aObserver); + + return NS_OK; +} + +NS_IMETHODIMP +CompositeDataSourceImpl::RemoveObserver(nsIRDFObserver* aObserver) +{ + NS_PRECONDITION(aObserver != nullptr, "null ptr"); + if (! aObserver) + return NS_ERROR_NULL_POINTER; + + mObservers.RemoveObject(aObserver); + + return NS_OK; +} + +NS_IMETHODIMP +CompositeDataSourceImpl::HasArcIn(nsIRDFNode *aNode, nsIRDFResource *aArc, bool *result) +{ + nsresult rv; + *result = false; + int32_t count = mDataSources.Count(); + for (int32_t i = 0; i < count; ++i) { + rv = mDataSources[i]->HasArcIn(aNode, aArc, result); + if (NS_FAILED(rv)) return rv; + if (*result) + return NS_OK; + } + return NS_OK; +} + +NS_IMETHODIMP +CompositeDataSourceImpl::HasArcOut(nsIRDFResource *aSource, nsIRDFResource *aArc, bool *result) +{ + nsresult rv; + *result = false; + int32_t count = mDataSources.Count(); + for (int32_t i = 0; i < count; ++i) { + rv = mDataSources[i]->HasArcOut(aSource, aArc, result); + if (NS_FAILED(rv)) return rv; + if (*result) + return NS_OK; + } + return NS_OK; +} + +NS_IMETHODIMP +CompositeDataSourceImpl::ArcLabelsIn(nsIRDFNode* aTarget, nsISimpleEnumerator** aResult) +{ + NS_PRECONDITION(aTarget != nullptr, "null ptr"); + if (! aTarget) + return NS_ERROR_NULL_POINTER; + + NS_PRECONDITION(aResult != nullptr, "null ptr"); + if (! aResult) + return NS_ERROR_NULL_POINTER; + + nsISimpleEnumerator* result = + new CompositeArcsInOutEnumeratorImpl(this, aTarget, + CompositeArcsInOutEnumeratorImpl::eArcsIn, + mAllowNegativeAssertions, + mCoalesceDuplicateArcs); + + if (! result) + return NS_ERROR_OUT_OF_MEMORY; + + NS_ADDREF(result); + *aResult = result; + return NS_OK; +} + +NS_IMETHODIMP +CompositeDataSourceImpl::ArcLabelsOut(nsIRDFResource* aSource, + nsISimpleEnumerator** aResult) +{ + NS_PRECONDITION(aSource != nullptr, "null ptr"); + if (! aSource) + return NS_ERROR_NULL_POINTER; + + NS_PRECONDITION(aResult != nullptr, "null ptr"); + if (! aResult) + return NS_ERROR_NULL_POINTER; + + nsISimpleEnumerator* result = + new CompositeArcsInOutEnumeratorImpl(this, aSource, + CompositeArcsInOutEnumeratorImpl::eArcsOut, + mAllowNegativeAssertions, + mCoalesceDuplicateArcs); + + if (! result) + return NS_ERROR_OUT_OF_MEMORY; + + NS_ADDREF(result); + *aResult = result; + return NS_OK; +} + +NS_IMETHODIMP +CompositeDataSourceImpl::GetAllResources(nsISimpleEnumerator** aResult) +{ + NS_NOTYETIMPLEMENTED("CompositeDataSourceImpl::GetAllResources"); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +CompositeDataSourceImpl::GetAllCmds(nsIRDFResource* source, + nsISimpleEnumerator/**/** result) +{ + nsresult rv; + nsCOMPtr set; + + for (int32_t i = 0; i < mDataSources.Count(); i++) + { + nsCOMPtr dsCmds; + + rv = mDataSources[i]->GetAllCmds(source, getter_AddRefs(dsCmds)); + if (NS_SUCCEEDED(rv)) + { + nsCOMPtr tmp; + rv = NS_NewUnionEnumerator(getter_AddRefs(tmp), set, dsCmds); + set.swap(tmp); + if (NS_FAILED(rv)) return(rv); + } + } + + set.forget(result); + return NS_OK; +} + +NS_IMETHODIMP +CompositeDataSourceImpl::IsCommandEnabled(nsISupports/* nsIRDFResource container */* aSources, + nsIRDFResource* aCommand, + nsISupports/* nsIRDFResource container */* aArguments, + bool* aResult) +{ + nsresult rv; + for (int32_t i = mDataSources.Count() - 1; i >= 0; --i) { + bool enabled = true; + rv = mDataSources[i]->IsCommandEnabled(aSources, aCommand, aArguments, &enabled); + if (NS_FAILED(rv) && (rv != NS_ERROR_NOT_IMPLEMENTED)) + { + return(rv); + } + + if (! enabled) { + *aResult = false; + return(NS_OK); + } + } + *aResult = true; + return(NS_OK); +} + +NS_IMETHODIMP +CompositeDataSourceImpl::DoCommand(nsISupports/* nsIRDFResource container */* aSources, + nsIRDFResource* aCommand, + nsISupports/* nsIRDFResource container */* aArguments) +{ + for (int32_t i = mDataSources.Count() - 1; i >= 0; --i) { + nsresult rv = mDataSources[i]->DoCommand(aSources, aCommand, aArguments); + if (NS_FAILED(rv) && (rv != NS_ERROR_NOT_IMPLEMENTED)) + { + return(rv); // all datasources must succeed + } + } + return(NS_OK); +} + +NS_IMETHODIMP +CompositeDataSourceImpl::BeginUpdateBatch() +{ + for (int32_t i = mDataSources.Count() - 1; i >= 0; --i) { + mDataSources[i]->BeginUpdateBatch(); + } + return NS_OK; +} + +NS_IMETHODIMP +CompositeDataSourceImpl::EndUpdateBatch() +{ + for (int32_t i = mDataSources.Count() - 1; i >= 0; --i) { + mDataSources[i]->EndUpdateBatch(); + } + return NS_OK; +} + +//////////////////////////////////////////////////////////////////////// +// nsIRDFCompositeDataSource methods +// XXX rvg We should make this take an additional argument specifying where +// in the sequence of data sources (of the db), the new data source should +// fit in. Right now, the new datasource gets stuck at the end. +// need to add the observers of the CompositeDataSourceImpl to the new data source. + +NS_IMETHODIMP +CompositeDataSourceImpl::GetAllowNegativeAssertions(bool *aAllowNegativeAssertions) +{ + *aAllowNegativeAssertions = mAllowNegativeAssertions; + return(NS_OK); +} + +NS_IMETHODIMP +CompositeDataSourceImpl::SetAllowNegativeAssertions(bool aAllowNegativeAssertions) +{ + mAllowNegativeAssertions = aAllowNegativeAssertions; + return(NS_OK); +} + +NS_IMETHODIMP +CompositeDataSourceImpl::GetCoalesceDuplicateArcs(bool *aCoalesceDuplicateArcs) +{ + *aCoalesceDuplicateArcs = mCoalesceDuplicateArcs; + return(NS_OK); +} + +NS_IMETHODIMP +CompositeDataSourceImpl::SetCoalesceDuplicateArcs(bool aCoalesceDuplicateArcs) +{ + mCoalesceDuplicateArcs = aCoalesceDuplicateArcs; + return(NS_OK); +} + +NS_IMETHODIMP +CompositeDataSourceImpl::AddDataSource(nsIRDFDataSource* aDataSource) +{ + NS_ASSERTION(aDataSource != nullptr, "null ptr"); + if (! aDataSource) + return NS_ERROR_NULL_POINTER; + + mDataSources.AppendObject(aDataSource); + aDataSource->AddObserver(this); + return NS_OK; +} + + + +NS_IMETHODIMP +CompositeDataSourceImpl::RemoveDataSource(nsIRDFDataSource* aDataSource) +{ + NS_ASSERTION(aDataSource != nullptr, "null ptr"); + if (! aDataSource) + return NS_ERROR_NULL_POINTER; + + + if (mDataSources.IndexOf(aDataSource) >= 0) { + aDataSource->RemoveObserver(this); + mDataSources.RemoveObject(aDataSource); + } + return NS_OK; +} + + +NS_IMETHODIMP +CompositeDataSourceImpl::GetDataSources(nsISimpleEnumerator** _result) +{ + // NS_NewArrayEnumerator for an nsCOMArray takes a snapshot of the + // current state. + return NS_NewArrayEnumerator(_result, mDataSources); +} + +NS_IMETHODIMP +CompositeDataSourceImpl::OnAssert(nsIRDFDataSource* aDataSource, + nsIRDFResource* aSource, + nsIRDFResource* aProperty, + nsIRDFNode* aTarget) +{ + // Make sure that the assertion isn't masked by another + // datasource. + // + // XXX We could make this more efficient if we knew _which_ + // datasource actually served up the OnAssert(): we could use + // HasAssertionN() to only search datasources _before_ the + // datasource that coughed up the assertion. + nsresult rv = NS_OK; + + if (mAllowNegativeAssertions) + { + bool hasAssertion; + rv = HasAssertion(aSource, aProperty, aTarget, true, &hasAssertion); + if (NS_FAILED(rv)) return rv; + + if (! hasAssertion) + return(NS_OK); + } + + for (int32_t i = mObservers.Count() - 1; i >= 0; --i) { + mObservers[i]->OnAssert(this, aSource, aProperty, aTarget); + } + return NS_OK; +} + +NS_IMETHODIMP +CompositeDataSourceImpl::OnUnassert(nsIRDFDataSource* aDataSource, + nsIRDFResource* aSource, + nsIRDFResource* aProperty, + nsIRDFNode* aTarget) +{ + // Make sure that the un-assertion doesn't just unmask the + // same assertion in a different datasource. + // + // XXX We could make this more efficient if we knew _which_ + // datasource actually served up the OnAssert(): we could use + // HasAssertionN() to only search datasources _before_ the + // datasource that coughed up the assertion. + nsresult rv; + + if (mAllowNegativeAssertions) + { + bool hasAssertion; + rv = HasAssertion(aSource, aProperty, aTarget, true, &hasAssertion); + if (NS_FAILED(rv)) return rv; + + if (hasAssertion) + return NS_OK; + } + + for (int32_t i = mObservers.Count() - 1; i >= 0; --i) { + mObservers[i]->OnUnassert(this, aSource, aProperty, aTarget); + } + return NS_OK; +} + + +NS_IMETHODIMP +CompositeDataSourceImpl::OnChange(nsIRDFDataSource* aDataSource, + nsIRDFResource* aSource, + nsIRDFResource* aProperty, + nsIRDFNode* aOldTarget, + nsIRDFNode* aNewTarget) +{ + // Make sure that the change is actually visible, and not hidden + // by an assertion in a different datasource. + // + // XXX Because of aggregation, this could actually mutate into a + // variety of OnAssert or OnChange notifications, which we'll + // ignore for now :-/. + for (int32_t i = mObservers.Count() - 1; i >= 0; --i) { + mObservers[i]->OnChange(this, aSource, aProperty, + aOldTarget, aNewTarget); + } + return NS_OK; +} + + +NS_IMETHODIMP +CompositeDataSourceImpl::OnMove(nsIRDFDataSource* aDataSource, + nsIRDFResource* aOldSource, + nsIRDFResource* aNewSource, + nsIRDFResource* aProperty, + nsIRDFNode* aTarget) +{ + // Make sure that the move is actually visible, and not hidden + // by an assertion in a different datasource. + // + // XXX Because of aggregation, this could actually mutate into a + // variety of OnAssert or OnMove notifications, which we'll + // ignore for now :-/. + for (int32_t i = mObservers.Count() - 1; i >= 0; --i) { + mObservers[i]->OnMove(this, aOldSource, aNewSource, + aProperty, aTarget); + } + return NS_OK; +} + + +NS_IMETHODIMP +CompositeDataSourceImpl::OnBeginUpdateBatch(nsIRDFDataSource* aDataSource) +{ + if (mUpdateBatchNest++ == 0) { + for (int32_t i = mObservers.Count() - 1; i >= 0; --i) { + mObservers[i]->OnBeginUpdateBatch(this); + } + } + return NS_OK; +} + + +NS_IMETHODIMP +CompositeDataSourceImpl::OnEndUpdateBatch(nsIRDFDataSource* aDataSource) +{ + NS_ASSERTION(mUpdateBatchNest > 0, "badly nested update batch"); + if (--mUpdateBatchNest == 0) { + for (int32_t i = mObservers.Count() - 1; i >= 0; --i) { + mObservers[i]->OnEndUpdateBatch(this); + } + } + return NS_OK; +} diff --git a/rdf/base/nsContainerEnumerator.cpp b/rdf/base/nsContainerEnumerator.cpp new file mode 100644 index 000000000..f1bfc6454 --- /dev/null +++ b/rdf/base/nsContainerEnumerator.cpp @@ -0,0 +1,263 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + + A simple cursor that enumerates the elements of an RDF container + (RDF:Bag, RDF:Seq, or RDF:Alt). + + Caveats + ------- + + 1. This uses an implementation-specific detail to determine the + index of the last element in the container; specifically, the RDF + utilities maintain a counter attribute on the container that + holds the numeric value of the next value that is to be + assigned. So, this cursor will bust if you use it with a bag that + hasn't been created using the RDF utility routines. + + */ + +#include "nscore.h" +#include "nsCOMPtr.h" +#include "nsIRDFContainerUtils.h" +#include "nsIRDFDataSource.h" +#include "nsIRDFNode.h" +#include "nsIRDFService.h" +#include "nsIServiceManager.h" +#include "nsRDFCID.h" +#include "nsString.h" +#include "nsXPIDLString.h" +#include "mozilla/Logging.h" +#include "rdf.h" +#include "rdfutil.h" + +//////////////////////////////////////////////////////////////////////// + +class ContainerEnumeratorImpl : public nsISimpleEnumerator { +private: + // pseudo-constants + static nsrefcnt gRefCnt; + static nsIRDFResource* kRDF_nextVal; + static nsIRDFContainerUtils* gRDFC; + + nsCOMPtr mDataSource; + nsCOMPtr mContainer; + nsCOMPtr mOrdinalProperty; + + nsCOMPtr mCurrent; + nsCOMPtr mResult; + int32_t mNextIndex; + + virtual ~ContainerEnumeratorImpl(); + +public: + ContainerEnumeratorImpl(nsIRDFDataSource* ds, nsIRDFResource* container); + + nsresult Init(); + + NS_DECL_ISUPPORTS + NS_DECL_NSISIMPLEENUMERATOR +}; + +nsrefcnt ContainerEnumeratorImpl::gRefCnt; +nsIRDFResource* ContainerEnumeratorImpl::kRDF_nextVal; +nsIRDFContainerUtils* ContainerEnumeratorImpl::gRDFC; + + +ContainerEnumeratorImpl::ContainerEnumeratorImpl(nsIRDFDataSource* aDataSource, + nsIRDFResource* aContainer) + : mDataSource(aDataSource), + mContainer(aContainer), + mNextIndex(1) +{ +} + +nsresult +ContainerEnumeratorImpl::Init() +{ + if (gRefCnt++ == 0) { + nsresult rv; + + NS_DEFINE_CID(kRDFServiceCID, NS_RDFSERVICE_CID); + nsCOMPtr rdf = do_GetService(kRDFServiceCID); + NS_ASSERTION(rdf != nullptr, "unable to acquire resource manager"); + if (! rdf) + return NS_ERROR_FAILURE; + + rv = rdf->GetResource(NS_LITERAL_CSTRING(RDF_NAMESPACE_URI "nextVal"), &kRDF_nextVal); + NS_ASSERTION(NS_SUCCEEDED(rv), "unable to get resource"); + if (NS_FAILED(rv)) return rv; + + NS_DEFINE_CID(kRDFContainerUtilsCID, NS_RDFCONTAINERUTILS_CID); + rv = CallGetService(kRDFContainerUtilsCID, &gRDFC); + if (NS_FAILED(rv)) return rv; + } + + return NS_OK; +} + + +ContainerEnumeratorImpl::~ContainerEnumeratorImpl() +{ + if (--gRefCnt == 0) { + NS_IF_RELEASE(kRDF_nextVal); + NS_IF_RELEASE(gRDFC); + } +} + +NS_IMPL_ISUPPORTS(ContainerEnumeratorImpl, nsISimpleEnumerator) + + +NS_IMETHODIMP +ContainerEnumeratorImpl::HasMoreElements(bool* aResult) +{ + NS_PRECONDITION(aResult != nullptr, "null ptr"); + if (! aResult) + return NS_ERROR_NULL_POINTER; + + nsresult rv; + + // If we've already queued up a next value, then we know there are more elements. + if (mResult) { + *aResult = true; + return NS_OK; + } + + // Otherwise, we need to grovel + + // Figure out the upper bound so we'll know when we're done. Since it's + // possible that we're targeting a composite datasource, we'll need to + // "GetTargets()" and take the maximum value of "nextVal" to know the + // upper bound. + // + // Remember that since nextVal is the next index that we'd assign + // to an element in a container, it's *one more* than count of + // elements in the container. + int32_t max = 0; + + nsCOMPtr targets; + rv = mDataSource->GetTargets(mContainer, kRDF_nextVal, true, getter_AddRefs(targets)); + if (NS_FAILED(rv)) return rv; + + while (1) { + bool hasmore; + targets->HasMoreElements(&hasmore); + if (! hasmore) + break; + + nsCOMPtr isupports; + targets->GetNext(getter_AddRefs(isupports)); + + nsCOMPtr nextValLiteral = do_QueryInterface(isupports); + if (! nextValLiteral) + continue; + + const char16_t *nextValStr; + nextValLiteral->GetValueConst(&nextValStr); + + nsresult err; + int32_t nextVal = nsAutoString(nextValStr).ToInteger(&err); + + if (nextVal > max) + max = nextVal; + } + + // Now pre-fetch our next value into mResult. + while (mCurrent || mNextIndex < max) { + + // If mCurrent has been depleted, then conjure up a new one + if (! mCurrent) { + rv = gRDFC->IndexToOrdinalResource(mNextIndex, getter_AddRefs(mOrdinalProperty)); + if (NS_FAILED(rv)) return rv; + + rv = mDataSource->GetTargets(mContainer, mOrdinalProperty, true, getter_AddRefs(mCurrent)); + if (NS_FAILED(rv)) return rv; + + ++mNextIndex; + } + + if (mCurrent) { + bool hasMore; + rv = mCurrent->HasMoreElements(&hasMore); + if (NS_FAILED(rv)) return rv; + + // Is the current enumerator depleted? If so, iterate to + // the next index. + if (! hasMore) { + mCurrent = nullptr; + continue; + } + + // "Peek" ahead and pull out the next target. + nsCOMPtr result; + rv = mCurrent->GetNext(getter_AddRefs(result)); + if (NS_FAILED(rv)) return rv; + + mResult = do_QueryInterface(result, &rv); + if (NS_FAILED(rv)) return rv; + + *aResult = true; + return NS_OK; + } + } + + // If we get here, we ran out of elements. The cursor is empty. + *aResult = false; + return NS_OK; +} + + +NS_IMETHODIMP +ContainerEnumeratorImpl::GetNext(nsISupports** aResult) +{ + nsresult rv; + + bool hasMore; + rv = HasMoreElements(&hasMore); + if (NS_FAILED(rv)) return rv; + + if (! hasMore) + return NS_ERROR_UNEXPECTED; + + NS_ADDREF(*aResult = mResult); + mResult = nullptr; + + return NS_OK; +} + + +//////////////////////////////////////////////////////////////////////// + +nsresult +NS_NewContainerEnumerator(nsIRDFDataSource* aDataSource, + nsIRDFResource* aContainer, + nsISimpleEnumerator** aResult) +{ + NS_PRECONDITION(aDataSource != nullptr, "null ptr"); + if (! aDataSource) + return NS_ERROR_NULL_POINTER; + + NS_PRECONDITION(aContainer != nullptr, "null ptr"); + if (! aContainer) + return NS_ERROR_NULL_POINTER; + + NS_PRECONDITION(aResult != nullptr, "null ptr"); + if (! aResult) + return NS_ERROR_NULL_POINTER; + + ContainerEnumeratorImpl* result = new ContainerEnumeratorImpl(aDataSource, aContainer); + if (! result) + return NS_ERROR_OUT_OF_MEMORY; + + NS_ADDREF(result); + + nsresult rv = result->Init(); + if (NS_FAILED(rv)) + NS_RELEASE(result); + + *aResult = result; + return rv; +} diff --git a/rdf/base/nsDefaultResourceFactory.cpp b/rdf/base/nsDefaultResourceFactory.cpp new file mode 100644 index 000000000..448822a9d --- /dev/null +++ b/rdf/base/nsDefaultResourceFactory.cpp @@ -0,0 +1,30 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + + The default resource factory implementation. This resource factory + produces nsIRDFResource objects for any URI prefix that is not + covered by some other factory. + + */ + +#include "nsRDFResource.h" + +nsresult +NS_NewDefaultResource(nsIRDFResource** aResult) +{ + NS_PRECONDITION(aResult != nullptr, "null ptr"); + if (! aResult) + return NS_ERROR_NULL_POINTER; + + nsRDFResource* resource = new nsRDFResource(); + if (! resource) + return NS_ERROR_OUT_OF_MEMORY; + + NS_ADDREF(resource); + *aResult = resource; + return NS_OK; +} diff --git a/rdf/base/nsIRDFCompositeDataSource.idl b/rdf/base/nsIRDFCompositeDataSource.idl new file mode 100644 index 000000000..071b8f3ac --- /dev/null +++ b/rdf/base/nsIRDFCompositeDataSource.idl @@ -0,0 +1,68 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsIRDFDataSource.idl" + +interface nsISimpleEnumerator; + +/** + * An nsIRDFCompositeDataSource composes individual data sources, providing + * the illusion of a single, coherent RDF graph. + */ +[scriptable, uuid(96343820-307C-11D2-BC15-00805F912FE7)] +interface nsIRDFCompositeDataSource : nsIRDFDataSource { + + /** + * + * Set this value to true if the composite datasource + * may contains at least one datasource that has negative + * assertions. (This is the default.) + * + * Set this value to false if none of the datasources + * being composed contains a negative assertion. This allows the + * composite datasource to perform some query optimizations. + * + * By default, this value is true. + */ + attribute boolean allowNegativeAssertions; + + /** + * Set to true if the composite datasource should + * take care to coalesce duplicate arcs when returning values from + * queries. (This is the default.) + * + * Set to false if the composite datasource shouldn't + * bother to check for duplicates. This allows the composite + * datasource to more efficiently answer queries. + * + * By default, this value is true. + */ + attribute boolean coalesceDuplicateArcs; + + /** + * Add a datasource the the composite data source. + * @param aDataSource the datasource to add to composite + */ + void AddDataSource(in nsIRDFDataSource aDataSource); + + /** + * Remove a datasource from the composite data source. + * @param aDataSource the datasource to remove from the composite + */ + void RemoveDataSource(in nsIRDFDataSource aDataSource); + + /** + * Retrieve the datasources in the composite data source. + * @return an nsISimpleEnumerator that will enumerate each + * of the datasources in the composite + */ + nsISimpleEnumerator GetDataSources(); +}; + +%{C++ +extern nsresult +NS_NewRDFCompositeDataSource(nsIRDFCompositeDataSource** result); +%} + diff --git a/rdf/base/nsIRDFContainer.idl b/rdf/base/nsIRDFContainer.idl new file mode 100644 index 000000000..78352b2b3 --- /dev/null +++ b/rdf/base/nsIRDFContainer.idl @@ -0,0 +1,94 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" +#include "nsIRDFDataSource.idl" +#include "nsIRDFResource.idl" +#include "nsIRDFNode.idl" +#include "nsISimpleEnumerator.idl" + +// A wrapper for manipulating RDF containers +[scriptable, uuid(D4214E90-FB94-11D2-BDD8-00104BDE6048)] +interface nsIRDFContainer : nsISupports { + readonly attribute nsIRDFDataSource DataSource; + readonly attribute nsIRDFResource Resource; + + /** + * Initialize the container wrapper to the specified resource + * using the specified datasource for context. + */ + void Init(in nsIRDFDataSource aDataSource, in nsIRDFResource aContainer); + + /** + * Return the number of elements in the container. Note that this + * may not always be accurate due to aggregation. + */ + long GetCount(); + + /** + * Return an enumerator that can be used to enumerate the contents + * of the container in ascending order. + */ + nsISimpleEnumerator GetElements(); + + /** + * Append an element to the container, assigning it the next + * available ordinal. + */ + void AppendElement(in nsIRDFNode aElement); + + /** + * Remove the first occurence of the specified element from the + * container. If aRenumber is 'true', then the underlying RDF graph + * will be 're-numbered' to account for the removal. + */ + void RemoveElement(in nsIRDFNode aElement, in boolean aRenumber); + + /** + * Insert aElement at the specified index. If aRenumber is 'true', then + * the underlying RDF graph will be 're-numbered' to accomodate the new + * element. + */ + void InsertElementAt(in nsIRDFNode aElement, in long aIndex, in boolean aRenumber); + + /** + * Remove the element at the specified index. If aRenumber is 'true', then + * the underlying RDF graph will be 're-numbered' to account for the + * removal. + * + * @return the element that was removed. + */ + nsIRDFNode RemoveElementAt(in long aIndex, in boolean aRenumber); + + /** + * Determine the index of an element in the container. + * + * @return The index of the specified element in the container. If + * the element is not contained in the container, this function + * returns '-1'. + */ + long IndexOf(in nsIRDFNode aElement); +}; + +%{C++ +nsresult +NS_NewRDFContainer(nsIRDFContainer** aResult); + +nsresult +NS_NewRDFContainer(nsIRDFDataSource* aDataSource, + nsIRDFResource* aResource, + nsIRDFContainer** aResult); + +/** + * Create a cursor on a container that enumerates its contents in + * order + */ +nsresult +NS_NewContainerEnumerator(nsIRDFDataSource* aDataSource, + nsIRDFResource* aContainer, + nsISimpleEnumerator** aResult); + + +%} diff --git a/rdf/base/nsIRDFContainerUtils.idl b/rdf/base/nsIRDFContainerUtils.idl new file mode 100644 index 000000000..21cb0b625 --- /dev/null +++ b/rdf/base/nsIRDFContainerUtils.idl @@ -0,0 +1,82 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" +#include "nsIRDFContainer.idl" +#include "nsIRDFResource.idl" + + +// Container utilities +[scriptable, uuid(D4214E91-FB94-11D2-BDD8-00104BDE6048)] +interface nsIRDFContainerUtils : nsISupports { + /** + * Returns 'true' if the property is an RDF ordinal property. + */ + boolean IsOrdinalProperty(in nsIRDFResource aProperty); + + /** + * Convert the specified index to an ordinal property. + */ + nsIRDFResource IndexToOrdinalResource(in long aIndex); + + /** + * Convert the specified ordinal property into an index + */ + long OrdinalResourceToIndex(in nsIRDFResource aOrdinal); + + /** + * Return 'true' if the specified resource is a container + */ + boolean IsContainer(in nsIRDFDataSource aDataSource, in nsIRDFResource aResource); + + /** + * Return 'true' if the specified resource is a container and it is empty + */ + boolean IsEmpty(in nsIRDFDataSource aDataSource, in nsIRDFResource aResource); + + /** + * Return 'true' if the specified resource is a bag + */ + boolean IsBag(in nsIRDFDataSource aDataSource, in nsIRDFResource aResource); + + /** + * Return 'true' if the specified resource is a sequence + */ + boolean IsSeq(in nsIRDFDataSource aDataSource, in nsIRDFResource aResource); + + /** + * Return 'true' if the specified resource is an alternation + */ + boolean IsAlt(in nsIRDFDataSource aDataSource, in nsIRDFResource aResource); + + /** + * Decorates the specified resource appropriately to make it + * usable as an empty bag in the specified data source. + */ + nsIRDFContainer MakeBag(in nsIRDFDataSource aDataSource, in nsIRDFResource aResource); + + /** + * Decorates the specified resource appropriately to make it + * usable as an empty sequence in the specified data source. + */ + nsIRDFContainer MakeSeq(in nsIRDFDataSource aDataSource, in nsIRDFResource aResource); + + /** + * Decorates the specified resource appropriately to make it + * usable as an empty alternation in the specified data source. + */ + nsIRDFContainer MakeAlt(in nsIRDFDataSource aDataSource, in nsIRDFResource aResource); + + /** + * Retrieve the index of element in the container. Returns -1 if + * the element is not in the container. + */ + long indexOf(in nsIRDFDataSource aDataSource, in nsIRDFResource aContainer, in nsIRDFNode aElement); +}; + +%{C++ +extern nsresult +NS_NewRDFContainerUtils(nsIRDFContainerUtils** aResult); +%} diff --git a/rdf/base/nsIRDFContentSink.h b/rdf/base/nsIRDFContentSink.h new file mode 100644 index 000000000..5abb536de --- /dev/null +++ b/rdf/base/nsIRDFContentSink.h @@ -0,0 +1,62 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + + An RDF-specific content sink. The content sink is targeted by the + parser for building the RDF content model. + + */ + +#ifndef nsIRDFContentSink_h___ +#define nsIRDFContentSink_h___ + +#include "nsIXMLContentSink.h" +class nsIRDFDataSource; +class nsIURI; + +// {3a7459d7-d723-483c-aef0-404fc48e09b8} +#define NS_IRDFCONTENTSINK_IID \ +{ 0x3a7459d7, 0xd723, 0x483c, { 0xae, 0xf0, 0x40, 0x4f, 0xc4, 0x8e, 0x09, 0xb8 } } + +/** + * This interface represents a content sink for RDF files. + */ + +class nsIRDFContentSink : public nsIXMLContentSink { +public: + NS_DECLARE_STATIC_IID_ACCESSOR(NS_IRDFCONTENTSINK_IID) + + /** + * Initialize the content sink. + */ + NS_IMETHOD Init(nsIURI* aURL) = 0; + + /** + * Set the content sink's RDF Data source + */ + NS_IMETHOD SetDataSource(nsIRDFDataSource* aDataSource) = 0; + + /** + * Retrieve the content sink's RDF data source. + */ + NS_IMETHOD GetDataSource(nsIRDFDataSource*& rDataSource) = 0; +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(nsIRDFContentSink, NS_IRDFCONTENTSINK_IID) + +/** + * This constructs a content sink that can be used without a + * document, say, to create a stand-alone in-memory graph. + */ +nsresult +NS_NewRDFContentSink(nsIRDFContentSink** aResult); + +class nsRDFAtoms { +public: + static void RegisterAtoms(); +}; + +#endif // nsIRDFContentSink_h___ diff --git a/rdf/base/nsIRDFDataSource.idl b/rdf/base/nsIRDFDataSource.idl new file mode 100644 index 000000000..2915425c8 --- /dev/null +++ b/rdf/base/nsIRDFDataSource.idl @@ -0,0 +1,181 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" +#include "nsIRDFResource.idl" +#include "nsIRDFNode.idl" +#include "nsISimpleEnumerator.idl" +#include "nsIRDFObserver.idl" + +[scriptable, uuid(0F78DA58-8321-11d2-8EAC-00805F29F370)] +interface nsIRDFDataSource : nsISupports +{ + /** The "URI" of the data source. This used by the RDF service's + * |GetDataSource()| method to cache datasources. + */ + readonly attribute string URI; + + /** Find an RDF resource that points to a given node over the + * specified arc & truth value + * + * @throws NS_RDF_NO_VALUE if there is no source that leads + * to the target with the specified property. + */ + nsIRDFResource GetSource(in nsIRDFResource aProperty, + in nsIRDFNode aTarget, + in boolean aTruthValue); + + /** + * Find all RDF resources that point to a given node over the + * specified arc & truth value + */ + nsISimpleEnumerator GetSources(in nsIRDFResource aProperty, + in nsIRDFNode aTarget, + in boolean aTruthValue); + + /** + * Find a child of that is related to the source by the given arc + * arc and truth value + * + * @throws NS_RDF_NO_VALUE if there is no target accessible from the + * source via the specified property. + */ + nsIRDFNode GetTarget(in nsIRDFResource aSource, + in nsIRDFResource aProperty, + in boolean aTruthValue); + + /** + * Find all children of that are related to the source by the given arc + * arc and truth value. + */ + nsISimpleEnumerator GetTargets(in nsIRDFResource aSource, + in nsIRDFResource aProperty, + in boolean aTruthValue); + + /** + * Add an assertion to the graph. + */ + void Assert(in nsIRDFResource aSource, + in nsIRDFResource aProperty, + in nsIRDFNode aTarget, + in boolean aTruthValue); + + /** + * Remove an assertion from the graph. + */ + void Unassert(in nsIRDFResource aSource, + in nsIRDFResource aProperty, + in nsIRDFNode aTarget); + + /** + * Change an assertion from + * + * [aSource]--[aProperty]-->[aOldTarget] + * + * to + * + * [aSource]--[aProperty]-->[aNewTarget] + */ + void Change(in nsIRDFResource aSource, + in nsIRDFResource aProperty, + in nsIRDFNode aOldTarget, + in nsIRDFNode aNewTarget); + + /** + * 'Move' an assertion from + * + * [aOldSource]--[aProperty]-->[aTarget] + * + * to + * + * [aNewSource]--[aProperty]-->[aTarget] + */ + void Move(in nsIRDFResource aOldSource, + in nsIRDFResource aNewSource, + in nsIRDFResource aProperty, + in nsIRDFNode aTarget); + + /** + * Query whether an assertion exists in this graph. + */ + boolean HasAssertion(in nsIRDFResource aSource, + in nsIRDFResource aProperty, + in nsIRDFNode aTarget, + in boolean aTruthValue); + + /** + * Add an observer to this data source. If the datasource + * supports observers, the datasource source should hold a strong + * reference to the observer. + */ + void AddObserver(in nsIRDFObserver aObserver); + + /** + * Remove an observer from this data source. + */ + void RemoveObserver(in nsIRDFObserver aObserver); + + /** + * Get a cursor to iterate over all the arcs that point into a node. + */ + nsISimpleEnumerator ArcLabelsIn(in nsIRDFNode aNode); + + /** + * Get a cursor to iterate over all the arcs that originate in + * a resource. + */ + nsISimpleEnumerator ArcLabelsOut(in nsIRDFResource aSource); + + /** + * Retrieve all of the resources that the data source currently + * refers to. + */ + nsISimpleEnumerator GetAllResources(); + + /** + * Returns whether a given command is enabled for a set of sources. + */ + boolean IsCommandEnabled(in nsISupports aSources, + in nsIRDFResource aCommand, + in nsISupports aArguments); + + /** + * Perform the specified command on set of sources. + */ + void DoCommand(in nsISupports aSources, + in nsIRDFResource aCommand, + in nsISupports aArguments); + + /** + * Returns the set of all commands defined for a given source. + */ + nsISimpleEnumerator GetAllCmds(in nsIRDFResource aSource); + + /** + * Returns true if the specified node is pointed to by the specified arc. + * Equivalent to enumerating ArcLabelsIn and comparing for the specified arc. + */ + boolean hasArcIn(in nsIRDFNode aNode, in nsIRDFResource aArc); + + /** + * Returns true if the specified node has the specified outward arc. + * Equivalent to enumerating ArcLabelsOut and comparing for the specified arc. + */ + boolean hasArcOut(in nsIRDFResource aSource, in nsIRDFResource aArc); + + /** + * Notify observers that the datasource is about to send several + * notifications at once. + * This must be followed by calling endUpdateBatch(), otherwise + * viewers will get out of sync. + */ + void beginUpdateBatch(); + + /** + * Notify observers that the datasource has completed issuing + * a notification group. + */ + void endUpdateBatch(); +}; diff --git a/rdf/base/nsIRDFDelegateFactory.idl b/rdf/base/nsIRDFDelegateFactory.idl new file mode 100644 index 000000000..6fbc42fd5 --- /dev/null +++ b/rdf/base/nsIRDFDelegateFactory.idl @@ -0,0 +1,40 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + + An interface used for runtime pseudo-aggregation of RDF delegate + objects. + +*/ + +#include "nsrootidl.idl" +#include "nsISupports.idl" +interface nsIRDFResource; + +/** + * This interface should be implemented by an XPCOM factory that + * is registered to handle "@mozilla.org/rdf/delegate-factory/[key]/[scheme];1" + * ContractIDs. + * + * The factory will be invoked to create delegate objects from + * nsIRDFResource::GetDelegate(). + */ +[scriptable, uuid(A1B89470-A124-11d3-BE59-0020A6361667)] +interface nsIRDFDelegateFactory : nsISupports +{ + /** + * Create a delegate for the specified RDF resource. + * + * The created delegate should forward AddRef() and Release() + * calls to the aOuter object. + */ + void CreateDelegate(in nsIRDFResource aOuter, + in string aKey, + in nsIIDRef aIID, + [retval, iid_is(aIID)] out nsQIResult aResult); +}; + + diff --git a/rdf/base/nsIRDFInMemoryDataSource.idl b/rdf/base/nsIRDFInMemoryDataSource.idl new file mode 100644 index 000000000..35734efe6 --- /dev/null +++ b/rdf/base/nsIRDFInMemoryDataSource.idl @@ -0,0 +1,14 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" +#include "nsIRDFResource.idl" +#include "nsIRDFNode.idl" + +[scriptable, uuid(17C4E0AA-1DD2-11B2-8029-BF6F668DE500)] +interface nsIRDFInMemoryDataSource : nsISupports +{ + void EnsureFastContainment(in nsIRDFResource aSource); +}; diff --git a/rdf/base/nsIRDFInferDataSource.idl b/rdf/base/nsIRDFInferDataSource.idl new file mode 100644 index 000000000..07bafcfff --- /dev/null +++ b/rdf/base/nsIRDFInferDataSource.idl @@ -0,0 +1,28 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsIRDFDataSource.idl" + +/** + * An nsIRDFInferDataSource is implemented by a infer engine. This + * engine mimics assertions in addition to those in the baseDataSource + * according to a particular vocabulary. + * Infer engines have contract IDs in the form of + * "@mozilla.org/rdf/infer-datasource;1?engine=" + */ + +[scriptable, uuid(2b04860f-4017-40f6-8a57-784a1e35077a)] +interface nsIRDFInferDataSource : nsIRDFDataSource { + /** + * + * The wrapped datasource. + * + * The InferDataSource contains all arcs from the wrapped + * datasource plus those infered by the vocabulary implemented + * by the InferDataSource. + */ + attribute nsIRDFDataSource baseDataSource; +}; + diff --git a/rdf/base/nsIRDFLiteral.idl b/rdf/base/nsIRDFLiteral.idl new file mode 100644 index 000000000..3d289c4fe --- /dev/null +++ b/rdf/base/nsIRDFLiteral.idl @@ -0,0 +1,68 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsIRDFNode.idl" + +%{C++ +#include "nscore.h" // for char16_t +%} + +[ptr] native const_octet_ptr(const uint8_t); + +/** + * A literal node in the graph, whose value is a string. + */ +[scriptable, uuid(E0C493D2-9542-11d2-8EB8-00805F29F370)] +interface nsIRDFLiteral : nsIRDFNode { + /** + * The Unicode string value of the literal. + */ + readonly attribute wstring Value; + + /** + * An unscriptable version used to avoid a string copy. Meant + * for use as a performance optimization. + */ + [noscript] void GetValueConst([shared] out wstring aConstValue); +}; + +/** + * A literal node in the graph, whose value is a date + */ +[scriptable, uuid(E13A24E1-C77A-11d2-80BE-006097B76B8E)] +interface nsIRDFDate : nsIRDFNode { + /** + * The date value of the literal + */ + readonly attribute PRTime Value; +}; + +/** + * A literal node in the graph, whose value is an integer + */ +[scriptable, uuid(E13A24E3-C77A-11d2-80BE-006097B76B8E)] +interface nsIRDFInt : nsIRDFNode { + /** + * The integer value of the literal + */ + readonly attribute long Value; +}; + +/** + * A literal node in the graph, whose value is arbitrary + * binary data. + */ +[scriptable, uuid(237f85a2-1dd2-11b2-94af-8122582fc45e)] +interface nsIRDFBlob : nsIRDFNode { + /** + * The binary data. + */ + [noscript] readonly attribute const_octet_ptr value; + + /** + * The data's length. + */ + readonly attribute long length; +}; diff --git a/rdf/base/nsIRDFNode.idl b/rdf/base/nsIRDFNode.idl new file mode 100644 index 000000000..2bd7d4967 --- /dev/null +++ b/rdf/base/nsIRDFNode.idl @@ -0,0 +1,15 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +// An nsIRDFNode object is an abstract placeholder for a node in the +// RDF data model. Its concreted implementations (e.g., nsIRDFResource +// or nsIRDFLiteral) are the actual objects that populate the graph. +[scriptable, uuid(0F78DA50-8321-11d2-8EAC-00805F29F370)] +interface nsIRDFNode : nsISupports { + // Determine if two nodes are identical + boolean EqualsNode(in nsIRDFNode aNode); +}; diff --git a/rdf/base/nsIRDFObserver.idl b/rdf/base/nsIRDFObserver.idl new file mode 100644 index 000000000..1923c5b38 --- /dev/null +++ b/rdf/base/nsIRDFObserver.idl @@ -0,0 +1,94 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" +#include "nsIRDFResource.idl" +#include "nsIRDFNode.idl" + +interface nsIRDFDataSource; + +// An nsIRDFObserver object is an observer that will be notified +// when assertions are made or removed from a datasource +[scriptable, uuid(3CC75360-484A-11D2-BC16-00805F912FE7)] +interface nsIRDFObserver : nsISupports { + /** + * This method is called whenever a new assertion is made + * in the data source + * @param aDataSource the datasource that is issuing + * the notification. + * @param aSource the subject of the assertion + * @param aProperty the predicate of the assertion + * @param aTarget the object of the assertion + */ + void onAssert(in nsIRDFDataSource aDataSource, + in nsIRDFResource aSource, + in nsIRDFResource aProperty, + in nsIRDFNode aTarget); + + /** + * This method is called whenever an assertion is removed + * from the data source + * @param aDataSource the datasource that is issuing + * the notification. + * @param aSource the subject of the assertion + * @param aProperty the predicate of the assertion + * @param aTarget the object of the assertion + */ + void onUnassert(in nsIRDFDataSource aDataSource, + in nsIRDFResource aSource, + in nsIRDFResource aProperty, + in nsIRDFNode aTarget); + + /** + * This method is called when the object of an assertion + * changes from one value to another. + * @param aDataSource the datasource that is issuing + * the notification. + * @param aSource the subject of the assertion + * @param aProperty the predicate of the assertion + * @param aOldTarget the old object of the assertion + * @param aNewTarget the new object of the assertion + */ + void onChange(in nsIRDFDataSource aDataSource, + in nsIRDFResource aSource, + in nsIRDFResource aProperty, + in nsIRDFNode aOldTarget, + in nsIRDFNode aNewTarget); + + /** + * This method is called when the subject of an assertion + * changes from one value to another. + * @param aDataSource the datasource that is issuing + * the notification. + * @param aOldSource the old subject of the assertion + * @param aNewSource the new subject of the assertion + * @param aProperty the predicate of the assertion + * @param aTarget the object of the assertion + */ + void onMove(in nsIRDFDataSource aDataSource, + in nsIRDFResource aOldSource, + in nsIRDFResource aNewSource, + in nsIRDFResource aProperty, + in nsIRDFNode aTarget); + + /** + * This method is called when a datasource is about to + * send several notifications at once. The observer can + * use this as a cue to optimize its behavior. The observer + * can expect the datasource to call endUpdateBatch() when + * the group of notifications has completed. + * @param aDataSource the datasource that is going to + * be issuing the notifications. + */ + void onBeginUpdateBatch(in nsIRDFDataSource aDataSource); + + /** + * This method is called when a datasource has completed + * issuing a notification group. + * @param aDataSource the datasource that has finished + * issuing a group of notifications + */ + void onEndUpdateBatch(in nsIRDFDataSource aDataSource); +}; diff --git a/rdf/base/nsIRDFPropagatableDataSource.idl b/rdf/base/nsIRDFPropagatableDataSource.idl new file mode 100644 index 000000000..dfe510e9c --- /dev/null +++ b/rdf/base/nsIRDFPropagatableDataSource.idl @@ -0,0 +1,27 @@ +/* -*- Mode: idl; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + + +#include "nsISupports.idl" + +/** + * An nsIRDFPropagatableDataSource provides an ability to suppress + * synchronization notifications. + */ +[scriptable, uuid(5a9b4770-9fcb-4307-a12e-4b6708e78b97)] +interface nsIRDFPropagatableDataSource: nsISupports { + + /** + * Set this value to true to enable synchronization + * notifications. + * + * Set this value to false to disable synchronization + * notifications. + * + * By default, this value is true. + */ + attribute boolean propagateChanges; + +}; diff --git a/rdf/base/nsIRDFPurgeableDataSource.idl b/rdf/base/nsIRDFPurgeableDataSource.idl new file mode 100644 index 000000000..05973df0e --- /dev/null +++ b/rdf/base/nsIRDFPurgeableDataSource.idl @@ -0,0 +1,19 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" +#include "nsIRDFResource.idl" +#include "nsIRDFNode.idl" + +[scriptable, uuid(951700F0-FED0-11D2-BDD9-00104BDE6048)] +interface nsIRDFPurgeableDataSource : nsISupports +{ + boolean Mark(in nsIRDFResource aSource, + in nsIRDFResource aProperty, + in nsIRDFNode aTarget, + in boolean aTruthValue); + + void Sweep(); +}; diff --git a/rdf/base/nsIRDFRemoteDataSource.idl b/rdf/base/nsIRDFRemoteDataSource.idl new file mode 100644 index 000000000..fd2cd1b0b --- /dev/null +++ b/rdf/base/nsIRDFRemoteDataSource.idl @@ -0,0 +1,44 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +/** + * A datasource that may load asynchronously + */ +[scriptable, uuid(1D297320-27F7-11d3-BE01-000064657374)] +interface nsIRDFRemoteDataSource : nsISupports +{ + /** + * This value is true when the datasource has + * fully loaded itself. + */ + readonly attribute boolean loaded; + + /** + * Specify the URI for the data source: this is the prefix + * that will be used to register the data source in the + * data source registry. + * @param aURI the URI to load + */ + void Init(in string aURI); + + /** + * Refresh the remote datasource, re-loading its contents + * from the URI. + * + * @param aBlocking If true, the call will block + * until the datasource has completely reloaded. + */ + void Refresh(in boolean aBlocking); + + /** + * Request that a data source write its contents out to + * permanent storage, if applicable. + */ + void Flush(); + void FlushTo(in string aURI); +}; + diff --git a/rdf/base/nsIRDFResource.idl b/rdf/base/nsIRDFResource.idl new file mode 100644 index 000000000..df4d4b8ff --- /dev/null +++ b/rdf/base/nsIRDFResource.idl @@ -0,0 +1,81 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsrootidl.idl" +#include "nsIRDFNode.idl" + + +/** + * An nsIRDFResource is an object that has unique identity in the + * RDF data model. The object's identity is determined by its URI. + */ +[scriptable, uuid(fb9686a7-719a-49dc-9107-10dea5739341)] +interface nsIRDFResource : nsIRDFNode { + /** + * The single-byte string value of the resource. + * @note THIS IS OBSOLETE. C++ should use GetValueConst and script + * should use .valueUTF8 + */ + readonly attribute string Value; + + /** + * The UTF-8 URI of the resource. + */ + readonly attribute AUTF8String ValueUTF8; + + /** + * An unscriptable version used to avoid a string copy. Meant + * for use as a performance optimization. The string is encoded + * in UTF-8. + */ + [noscript] void GetValueConst([shared] out string aConstValue); + + /** + * This method is called by the nsIRDFService after constructing + * a resource object to initialize its URI. You would not normally + * call this method directly + */ + void Init(in string uri); + + /** + * Determine if the resource has the given URI. + */ + boolean EqualsString(in string aURI); + + /** + * Retrieve the "delegate" object for this resource. A resource + * may have several delegate objects, each of whose lifetimes is + * bound to the life of the resource object. + * + * This method will return the delegate for the given key after + * QueryInterface()-ing it to the requested IID. + * + * If no delegate exists for the specified key, this method will + * attempt to create one using the component manager. Specifically, + * it will combine aKey with the resource's URI scheme to produce + * a ContractID as follows: + * + * component:/rdf/delegate-factory/[key]/[scheme] + * + * This ContractID will be used to locate a factory using the + * FindFactory() method of nsIComponentManager. If the nsIFactory + * exists, it will be used to create a "delegate factory"; that + * is, an object that supports nsIRDFDelegateFactory. The delegate + * factory will be used to construct the delegate object. + */ + void GetDelegate(in string aKey, in nsIIDRef aIID, + [iid_is(aIID),retval] out nsQIResult aResult); + + /** + * Force a delegate to be "unbound" from the resource. + * + * Normally, a delegate object's lifetime will be identical to + * that of the resource to which it is bound; this method allows a + * delegate to unlink itself from an RDF resource prematurely. + */ + void ReleaseDelegate(in string aKey); +}; + + diff --git a/rdf/base/nsIRDFService.idl b/rdf/base/nsIRDFService.idl new file mode 100644 index 000000000..08023feab --- /dev/null +++ b/rdf/base/nsIRDFService.idl @@ -0,0 +1,146 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" +#include "nsIRDFResource.idl" +#include "nsIRDFLiteral.idl" +#include "nsIRDFDataSource.idl" + + +/** + * The RDF service interface. This is a singleton object which should be + * obtained from the nsServiceManager. + */ +[scriptable, uuid(BFD05261-834C-11d2-8EAC-00805F29F370)] +interface nsIRDFService : nsISupports { + /** + * Construct an RDF resource from a single-byte URI. nsIRDFService + * caches resources that are in-use, so multiple calls to GetResource() + * for the same uri will return identical pointers. FindResource + * is used to find out whether there already exists a resource corresponding to that url. + */ + nsIRDFResource GetResource(in AUTF8String aURI); + + /** + * Construct an RDF resource from a Unicode URI. This is provided + * as a convenience method, allowing automatic, in-line C++ + * conversion from nsString objects. The uri will + * be converted to a single-byte representation internally. + */ + nsIRDFResource GetUnicodeResource(in AString aURI); + + nsIRDFResource GetAnonymousResource(); + + /** + * Construct an RDF literal from a Unicode string. + */ + nsIRDFLiteral GetLiteral(in wstring aValue); + + /** + * Construct an RDF literal from a PRTime. + */ + nsIRDFDate GetDateLiteral(in PRTime aValue); + + /** + * Construct an RDF literal from an int. + */ + nsIRDFInt GetIntLiteral(in long aValue); + + /** + * Construct an RDF literal from a data blob + */ + [noscript] nsIRDFBlob getBlobLiteral(in const_octet_ptr aValue, in long aLength); + + boolean IsAnonymousResource(in nsIRDFResource aResource); + + /** + * Registers a resource with the RDF system, making it unique w.r.t. + * GetResource. + * + * An implementation of nsIRDFResource should call this in its + * Init() method if it wishes the resource to be globally unique + * (which is usually the case). + * + * @note that the resource will not be ref-counted by the + * RDF service: the assumption is that the resource implementation + * will call nsIRDFService::UnregisterResource() when the last + * reference to the resource is released. + * + * @note that the nsIRDFService implementation may choose to + * maintain a reference to the resource's URI; therefore, the + * resource implementation should ensure that the resource's URI + * (accessible via nsIRDFResource::GetValue(const char* *aURI)) is + * valid before calling RegisterResource(). Furthermore, the + * resource implementation should ensure that this pointer + * remains valid for the lifetime of the resource. (The + * implementation of the resource cache in nsIRDFService uses the + * URI maintained "internally" in the resource as a key into the + * cache rather than copying the resource URI itself.) + */ + void RegisterResource(in nsIRDFResource aResource, in boolean aReplace); + + /** + * Called to notify the resource manager that a resource is no + * longer in use. This method should only be called from the + * destructor of a "custom" resource implementation to notify the + * RDF service that the last reference to the resource has been + * released, so the resource is no longer valid. + * + * @note As mentioned in nsIRDFResourceFactory::CreateResource(), + * the RDF service will use the result of + * nsIRDFResource::GetValue() as a key into its cache. For this + * reason, you must always un-cache the resource before + * releasing the storage for the const char* URI. + */ + void UnregisterResource(in nsIRDFResource aResource); + + /** + * Register a named data source. The RDF service will call + * nsIRDFDataSource::GetURI() to determine the URI under + * which to register the data source. + * + * @note that the data source will not be refcounted by the + * RDF service! The assumption is that an RDF data source + * registers with the service once it is initialized (via + * nsIRDFDataSource::Init()), and unregisters when the + * last reference to the data source is released. + */ + void RegisterDataSource(in nsIRDFDataSource aDataSource, + in boolean aReplace); + + /** + * Unregister a named data source. The RDF service will call + * nsIRDFDataSource::GetURI() to determine the URI under which the + * data source was registered. + */ + void UnregisterDataSource(in nsIRDFDataSource aDataSource); + + /** + * Get the named data source corresponding to the URI. If a data + * source has been registered via RegisterDataSource(), that + * data source will be returned. + * + * If no data source is currently + * registered for the specified URI, and a data source constructor + * function has been registered via RegisterDatasourceConstructor(), + * the RDF service will call the constructor to attempt to construct a + * new data source. If construction is successful, the data source will + * be initialized via nsIRDFDataSource::Init(). + */ + nsIRDFDataSource GetDataSource(in string aURI); + + /** + * Same as GetDataSource, but if a remote/XML data source needs to be + * constructed, then this method will issue a blocking Refresh + * call on that data source. + */ + nsIRDFDataSource GetDataSourceBlocking(in string aURI); +}; + +%{C++ +extern nsresult +NS_NewRDFService(nsIRDFService** result); +%} + diff --git a/rdf/base/nsIRDFXMLParser.idl b/rdf/base/nsIRDFXMLParser.idl new file mode 100644 index 000000000..1e7dde51f --- /dev/null +++ b/rdf/base/nsIRDFXMLParser.idl @@ -0,0 +1,33 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" +#include "nsIRDFDataSource.idl" +#include "nsIStreamListener.idl" +#include "nsIURI.idl" + +[scriptable, uuid(1831dd2e-1dd2-11b2-bdb3-86b7b50b70b5)] +interface nsIRDFXMLParser : nsISupports +{ + /** + * Create a stream listener that can be used to asynchronously + * parse RDF/XML. + * @param aSink the RDF datasource the will receive the data + * @param aBaseURI the base URI used to resolve relative + * references in the RDF/XML + * @return an nsIStreamListener object to handle the data + */ + nsIStreamListener parseAsync(in nsIRDFDataSource aSink, in nsIURI aBaseURI); + + /** + * Parse a string of RDF/XML + * @param aSink the RDF datasource that will receive the data + * @param aBaseURI the base URI used to resolve relative + * references in the RDF/XML + * @param aSource a UTF8 string containing RDF/XML data. + */ + void parseString(in nsIRDFDataSource aSink, in nsIURI aBaseURI, in AUTF8String aSource); +}; diff --git a/rdf/base/nsIRDFXMLSerializer.idl b/rdf/base/nsIRDFXMLSerializer.idl new file mode 100644 index 000000000..e16ef2a2a --- /dev/null +++ b/rdf/base/nsIRDFXMLSerializer.idl @@ -0,0 +1,27 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsIAtom.idl" +#include "nsISupports.idl" +#include "nsIRDFDataSource.idl" + +[scriptable, uuid(8ae1fbf8-1dd2-11b2-bd21-d728069cca92)] +interface nsIRDFXMLSerializer : nsISupports +{ + /** + * Initialize the serializer with the specified datasource. + * @param aDataSource the datasource from which data will be + * serialized + */ + void init(in nsIRDFDataSource aDataSource); + + /** + * Add the specified namespace to the serializer. + * @param aPrefix the attribute namespace prefix + * @param aURI the namespace URI + */ + void addNameSpace(in nsIAtom aPrefix, in DOMString aURI); +}; diff --git a/rdf/base/nsIRDFXMLSink.idl b/rdf/base/nsIRDFXMLSink.idl new file mode 100644 index 000000000..8c9d9461c --- /dev/null +++ b/rdf/base/nsIRDFXMLSink.idl @@ -0,0 +1,130 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + + Interfaces for the RDF/XML sink, which parses RDF/XML into + a graph representation. + +*/ + +#include "nsISupports.idl" + +// XXX Until these get scriptable. See nsIRDFXMLSink::AddNameSpace() +[ptr] native nsIAtomPtr(nsIAtom); +[ref] native nsStringRef(nsString); +%{C++ +class nsIAtom; +class nsString; +%} + +interface nsIRDFXMLSink; + +/** + * An observer that is notified as progress is made on the load + * of an RDF/XML document in an nsIRDFXMLSink. + */ +[scriptable, uuid(EB1A5D30-AB33-11D2-8EC6-00805F29F370)] +interface nsIRDFXMLSinkObserver : nsISupports +{ + /** + * Called when the load begins. + * @param aSink the RDF/XML sink on which the load is beginning. + */ + void onBeginLoad(in nsIRDFXMLSink aSink); + + /** + * Called when the load is suspended (e.g., for network quantization). + * @param aSink the RDF/XML sink that is being interrupted. + */ + void onInterrupt(in nsIRDFXMLSink aSink); + + /** + * Called when a suspended load is resuming. + * @param aSink the RDF/XML sink that is resuming. + */ + void onResume(in nsIRDFXMLSink aSink); + + /** + * Called when an RDF/XML load completes successfully. + * @param aSink the RDF/XML sink that has finished loading. + */ + void onEndLoad(in nsIRDFXMLSink aSink); + + /** + * Called when an error occurs during the load + * @param aSink the RDF/XML sink in which the error occurred + * @param aStatus the networking result code + * @param aErrorMsg an error message, if applicable + */ + void onError(in nsIRDFXMLSink aSink, in nsresult aStatus, in wstring aErrorMsg); +}; + + + +/** + * A "sink" that receives and processes RDF/XML. This interface is used + * by the RDF/XML parser. + */ +[scriptable, uuid(EB1A5D31-AB33-11D2-8EC6-00805F29F370)] +interface nsIRDFXMLSink : nsISupports +{ + /** + * Set to true if the sink is read-only and cannot + * be modified + */ + attribute boolean readOnly; + + /** + * Initiate the RDF/XML load. + */ + void beginLoad(); + + /** + * Suspend the RDF/XML load. + */ + void interrupt(); + + /** + * Resume the RDF/XML load. + */ + void resume(); + + /** + * Complete the RDF/XML load. + */ + void endLoad(); + + /** + * Add namespace information to the RDF/XML sink. + * @param aPrefix the namespace prefix + * @param aURI the namespace URI + */ + [noscript] void addNameSpace(in nsIAtomPtr aPrefix, + [const] in nsStringRef aURI); + + /** + * Add an observer that will be notified as the RDF/XML load + * progresses. + *

+ * + * Note that the sink will acquire a strong reference to the + * observer, so care should be taken to avoid cyclical references + * that cannot be released (i.e., if the observer holds a + * reference to the sink, it should be sure that it eventually + * clears the reference). + * + * @param aObserver the observer to add to the sink's set of + * load observers. + */ + void addXMLSinkObserver(in nsIRDFXMLSinkObserver aObserver); + + /** + * Remove an observer from the sink's set of observers. + * @param aObserver the observer to remove. + */ + void removeXMLSinkObserver(in nsIRDFXMLSinkObserver aObserver); +}; + diff --git a/rdf/base/nsIRDFXMLSource.idl b/rdf/base/nsIRDFXMLSource.idl new file mode 100644 index 000000000..794d8b662 --- /dev/null +++ b/rdf/base/nsIRDFXMLSource.idl @@ -0,0 +1,20 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" +#include "nsIOutputStream.idl" + +[scriptable, uuid(4DA56F10-99FE-11d2-8EBB-00805F29F370)] +interface nsIRDFXMLSource : nsISupports +{ + /** + * Serialize the contents of the datasource to aStream. + * @param aStream the output stream the will receive the + * RDF/XML. Currently, the output stream need only + * implement the |write()| method. + */ + void Serialize(in nsIOutputStream aStream); +}; + diff --git a/rdf/base/nsInMemoryDataSource.cpp b/rdf/base/nsInMemoryDataSource.cpp new file mode 100644 index 000000000..9bdd6b4fb --- /dev/null +++ b/rdf/base/nsInMemoryDataSource.cpp @@ -0,0 +1,1964 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * + * This Original Code has been modified by IBM Corporation. + * Modifications made by IBM described herein are + * Copyright (c) International Business Machines + * Corporation, 2000 + * + * Modifications to Mozilla code or documentation + * identified per MPL Section 3.3 + * + * Date Modified by Description of modification + * 03/27/2000 IBM Corp. Added PR_CALLBACK for Optlink + * use in OS2 + */ + +/* + + Implementation for an in-memory RDF data store. + + TO DO + + 1) Instrument this code to gather space and time performance + characteristics. + + 2) Optimize lookups for datasources which have a small number + of properties + fanning out to a large number of targets. + + 3) Complete implementation of thread-safety; specifically, make + assertions be reference counted objects (so that a cursor can + still refer to an assertion that gets removed from the graph). + + */ + +#include "nsAgg.h" +#include "nsCOMPtr.h" +#include "nscore.h" +#include "nsArrayEnumerator.h" +#include "nsIOutputStream.h" +#include "nsIRDFDataSource.h" +#include "nsIRDFLiteral.h" +#include "nsIRDFNode.h" +#include "nsIRDFObserver.h" +#include "nsIRDFInMemoryDataSource.h" +#include "nsIRDFPropagatableDataSource.h" +#include "nsIRDFPurgeableDataSource.h" +#include "nsIRDFService.h" +#include "nsIServiceManager.h" +#include "nsCOMArray.h" +#include "nsEnumeratorUtils.h" +#include "nsTArray.h" +#include "nsCRT.h" +#include "nsRDFCID.h" +#include "nsRDFBaseDataSources.h" +#include "nsString.h" +#include "nsReadableUtils.h" +#include "nsXPIDLString.h" +#include "rdfutil.h" +#include "PLDHashTable.h" +#include "plstr.h" +#include "mozilla/Logging.h" +#include "rdf.h" + +#include "rdfIDataSource.h" +#include "rdfITripleVisitor.h" + +using mozilla::LogLevel; + +// This struct is used as the slot value in the forward and reverse +// arcs hash tables. +// +// Assertion objects are reference counted, because each Assertion's +// ownership is shared between the datasource and any enumerators that +// are currently iterating over the datasource. +// +class Assertion +{ +public: + Assertion(nsIRDFResource* aSource, // normal assertion + nsIRDFResource* aProperty, + nsIRDFNode* aTarget, + bool aTruthValue); + explicit Assertion(nsIRDFResource* aSource); // PLDHashTable assertion variant + +private: + ~Assertion(); + +public: + void AddRef() { + if (mRefCnt == UINT16_MAX) { + NS_WARNING("refcount overflow, leaking Assertion"); + return; + } + ++mRefCnt; + } + + void Release() { + if (mRefCnt == UINT16_MAX) { + NS_WARNING("refcount overflow, leaking Assertion"); + return; + } + if (--mRefCnt == 0) + delete this; + } + + // For nsIRDFPurgeableDataSource + inline void Mark() { u.as.mMarked = true; } + inline bool IsMarked() { return u.as.mMarked; } + inline void Unmark() { u.as.mMarked = false; } + + // public for now, because I'm too lazy to go thru and clean this up. + + // These are shared between hash/as (see the union below) + nsIRDFResource* mSource; + Assertion* mNext; + + union + { + struct hash + { + PLDHashTable* mPropertyHash; + } hash; + struct as + { + nsIRDFResource* mProperty; + nsIRDFNode* mTarget; + Assertion* mInvNext; + // make sure bool are final elements + bool mTruthValue; + bool mMarked; + } as; + } u; + + // also shared between hash/as (see the union above) + // but placed after union definition to ensure that + // all 32-bit entries are long aligned + uint16_t mRefCnt; + bool mHashEntry; +}; + + +struct Entry : PLDHashEntryHdr { + nsIRDFNode* mNode; + Assertion* mAssertions; +}; + + +Assertion::Assertion(nsIRDFResource* aSource) + : mSource(aSource), + mNext(nullptr), + mRefCnt(0), + mHashEntry(true) +{ + MOZ_COUNT_CTOR(Assertion); + + NS_ADDREF(mSource); + + u.hash.mPropertyHash = + new PLDHashTable(PLDHashTable::StubOps(), sizeof(Entry)); +} + +Assertion::Assertion(nsIRDFResource* aSource, + nsIRDFResource* aProperty, + nsIRDFNode* aTarget, + bool aTruthValue) + : mSource(aSource), + mNext(nullptr), + mRefCnt(0), + mHashEntry(false) +{ + MOZ_COUNT_CTOR(Assertion); + + u.as.mProperty = aProperty; + u.as.mTarget = aTarget; + + NS_ADDREF(mSource); + NS_ADDREF(u.as.mProperty); + NS_ADDREF(u.as.mTarget); + + u.as.mInvNext = nullptr; + u.as.mTruthValue = aTruthValue; + u.as.mMarked = false; +} + +Assertion::~Assertion() +{ + if (mHashEntry && u.hash.mPropertyHash) { + for (auto i = u.hash.mPropertyHash->Iter(); !i.Done(); i.Next()) { + auto entry = static_cast(i.Get()); + Assertion* as = entry->mAssertions; + while (as) { + Assertion* doomed = as; + as = as->mNext; + + // Unlink, and release the datasource's reference. + doomed->mNext = doomed->u.as.mInvNext = nullptr; + doomed->Release(); + } + } + delete u.hash.mPropertyHash; + u.hash.mPropertyHash = nullptr; + } + + MOZ_COUNT_DTOR(Assertion); +#ifdef DEBUG_REFS + --gInstanceCount; + fprintf(stdout, "%d - RDF: Assertion\n", gInstanceCount); +#endif + + NS_RELEASE(mSource); + if (!mHashEntry) + { + NS_RELEASE(u.as.mProperty); + NS_RELEASE(u.as.mTarget); + } +} + +//////////////////////////////////////////////////////////////////////// +// InMemoryDataSource +class InMemoryArcsEnumeratorImpl; +class InMemoryAssertionEnumeratorImpl; +class InMemoryResourceEnumeratorImpl; + +class InMemoryDataSource : public nsIRDFDataSource, + public nsIRDFInMemoryDataSource, + public nsIRDFPropagatableDataSource, + public nsIRDFPurgeableDataSource, + public rdfIDataSource +{ +protected: + // These hash tables are keyed on pointers to nsIRDFResource + // objects (the nsIRDFService ensures that there is only ever one + // nsIRDFResource object per unique URI). The value of an entry is + // an Assertion struct, which is a linked list of (subject + // predicate object) triples. + PLDHashTable mForwardArcs; + PLDHashTable mReverseArcs; + + nsCOMArray mObservers; + uint32_t mNumObservers; + + // VisitFoo needs to block writes, [Un]Assert only allowed + // during mReadCount == 0 + uint32_t mReadCount; + + friend class InMemoryArcsEnumeratorImpl; + friend class InMemoryAssertionEnumeratorImpl; + friend class InMemoryResourceEnumeratorImpl; // b/c it needs to enumerate mForwardArcs + + // Thread-safe writer implementation methods. + nsresult + LockedAssert(nsIRDFResource* source, + nsIRDFResource* property, + nsIRDFNode* target, + bool tv); + + nsresult + LockedUnassert(nsIRDFResource* source, + nsIRDFResource* property, + nsIRDFNode* target); + + explicit InMemoryDataSource(nsISupports* aOuter); + virtual ~InMemoryDataSource(); + + friend nsresult + NS_NewRDFInMemoryDataSource(nsISupports* aOuter, const nsIID& aIID, void** aResult); + +public: + NS_DECL_CYCLE_COLLECTING_AGGREGATED + NS_DECL_AGGREGATED_CYCLE_COLLECTION_CLASS(InMemoryDataSource) + + // nsIRDFDataSource methods + NS_DECL_NSIRDFDATASOURCE + + // nsIRDFInMemoryDataSource methods + NS_DECL_NSIRDFINMEMORYDATASOURCE + + // nsIRDFPropagatableDataSource methods + NS_DECL_NSIRDFPROPAGATABLEDATASOURCE + + // nsIRDFPurgeableDataSource methods + NS_DECL_NSIRDFPURGEABLEDATASOURCE + + // rdfIDataSource methods + NS_DECL_RDFIDATASOURCE + +protected: + struct SweepInfo { + Assertion* mUnassertList; + PLDHashTable* mReverseArcs; + }; + + static void + SweepForwardArcsEntries(PLDHashTable* aTable, SweepInfo* aArg); + +public: + // Implementation methods + Assertion* + GetForwardArcs(nsIRDFResource* u) { + PLDHashEntryHdr* hdr = mForwardArcs.Search(u); + return hdr ? static_cast(hdr)->mAssertions : nullptr; + } + + Assertion* + GetReverseArcs(nsIRDFNode* v) { + PLDHashEntryHdr* hdr = mReverseArcs.Search(v); + return hdr ? static_cast(hdr)->mAssertions : nullptr; + } + + void + SetForwardArcs(nsIRDFResource* u, Assertion* as) { + if (as) { + auto entry = + static_cast(mForwardArcs.Add(u, mozilla::fallible)); + if (entry) { + entry->mNode = u; + entry->mAssertions = as; + } + } + else { + mForwardArcs.Remove(u); + } + } + + void + SetReverseArcs(nsIRDFNode* v, Assertion* as) { + if (as) { + auto entry = + static_cast(mReverseArcs.Add(v, mozilla::fallible)); + if (entry) { + entry->mNode = v; + entry->mAssertions = as; + } + } + else { + mReverseArcs.Remove(v); + } + } + + void + LogOperation(const char* aOperation, + nsIRDFResource* asource, + nsIRDFResource* aProperty, + nsIRDFNode* aTarget, + bool aTruthValue = true); + + bool mPropagateChanges; + +private: + static mozilla::LazyLogModule gLog; +}; + +mozilla::LazyLogModule InMemoryDataSource::gLog("InMemoryDataSource"); + +//---------------------------------------------------------------------- +// +// InMemoryAssertionEnumeratorImpl +// + +/** + * InMemoryAssertionEnumeratorImpl + */ +class InMemoryAssertionEnumeratorImpl : public nsISimpleEnumerator +{ +private: + InMemoryDataSource* mDataSource; + nsIRDFResource* mSource; + nsIRDFResource* mProperty; + nsIRDFNode* mTarget; + nsIRDFNode* mValue; + bool mTruthValue; + Assertion* mNextAssertion; + + virtual ~InMemoryAssertionEnumeratorImpl(); + +public: + InMemoryAssertionEnumeratorImpl(InMemoryDataSource* aDataSource, + nsIRDFResource* aSource, + nsIRDFResource* aProperty, + nsIRDFNode* aTarget, + bool aTruthValue); + + // nsISupports interface + NS_DECL_ISUPPORTS + + // nsISimpleEnumerator interface + NS_DECL_NSISIMPLEENUMERATOR +}; + +//////////////////////////////////////////////////////////////////////// + + +InMemoryAssertionEnumeratorImpl::InMemoryAssertionEnumeratorImpl( + InMemoryDataSource* aDataSource, + nsIRDFResource* aSource, + nsIRDFResource* aProperty, + nsIRDFNode* aTarget, + bool aTruthValue) + : mDataSource(aDataSource), + mSource(aSource), + mProperty(aProperty), + mTarget(aTarget), + mValue(nullptr), + mTruthValue(aTruthValue), + mNextAssertion(nullptr) +{ + NS_ADDREF(mDataSource); + NS_IF_ADDREF(mSource); + NS_ADDREF(mProperty); + NS_IF_ADDREF(mTarget); + + if (mSource) { + mNextAssertion = mDataSource->GetForwardArcs(mSource); + + if (mNextAssertion && mNextAssertion->mHashEntry) { + // its our magical HASH_ENTRY forward hash for assertions + PLDHashEntryHdr* hdr = + mNextAssertion->u.hash.mPropertyHash->Search(aProperty); + mNextAssertion = + hdr ? static_cast(hdr)->mAssertions : nullptr; + } + } + else { + mNextAssertion = mDataSource->GetReverseArcs(mTarget); + } + + // Add an owning reference from the enumerator + if (mNextAssertion) + mNextAssertion->AddRef(); +} + +InMemoryAssertionEnumeratorImpl::~InMemoryAssertionEnumeratorImpl() +{ +#ifdef DEBUG_REFS + --gInstanceCount; + fprintf(stdout, "%d - RDF: InMemoryAssertionEnumeratorImpl\n", gInstanceCount); +#endif + + if (mNextAssertion) + mNextAssertion->Release(); + + NS_IF_RELEASE(mDataSource); + NS_IF_RELEASE(mSource); + NS_IF_RELEASE(mProperty); + NS_IF_RELEASE(mTarget); + NS_IF_RELEASE(mValue); +} + +NS_IMPL_ADDREF(InMemoryAssertionEnumeratorImpl) +NS_IMPL_RELEASE(InMemoryAssertionEnumeratorImpl) +NS_IMPL_QUERY_INTERFACE(InMemoryAssertionEnumeratorImpl, nsISimpleEnumerator) + +NS_IMETHODIMP +InMemoryAssertionEnumeratorImpl::HasMoreElements(bool* aResult) +{ + if (mValue) { + *aResult = true; + return NS_OK; + } + + while (mNextAssertion) { + bool foundIt = false; + if ((mProperty == mNextAssertion->u.as.mProperty) && + (mTruthValue == mNextAssertion->u.as.mTruthValue)) { + if (mSource) { + mValue = mNextAssertion->u.as.mTarget; + NS_ADDREF(mValue); + } + else { + mValue = mNextAssertion->mSource; + NS_ADDREF(mValue); + } + foundIt = true; + } + + // Remember the last assertion we were holding on to + Assertion* as = mNextAssertion; + + // iterate + mNextAssertion = (mSource) ? mNextAssertion->mNext : mNextAssertion->u.as.mInvNext; + + // grab an owning reference from the enumerator to the next assertion + if (mNextAssertion) + mNextAssertion->AddRef(); + + // ...and release the reference from the enumerator to the old one. + as->Release(); + + if (foundIt) { + *aResult = true; + return NS_OK; + } + } + + *aResult = false; + return NS_OK; +} + + +NS_IMETHODIMP +InMemoryAssertionEnumeratorImpl::GetNext(nsISupports** aResult) +{ + nsresult rv; + + bool hasMore; + rv = HasMoreElements(&hasMore); + if (NS_FAILED(rv)) return rv; + + if (! hasMore) + return NS_ERROR_UNEXPECTED; + + // Don't AddRef: we "transfer" ownership to the caller + *aResult = mValue; + mValue = nullptr; + + return NS_OK; +} + +//////////////////////////////////////////////////////////////////////// +// + +/** + * This class is a little bit bizarre in that it implements both the + * nsIRDFArcsOutCursor and nsIRDFArcsInCursor interfaces. + * Because the structure of the in-memory graph is pretty flexible, it's + * fairly easy to parameterize this class. The only funky thing to watch + * out for is the multiple inheritance clashes. + */ + +class InMemoryArcsEnumeratorImpl : public nsISimpleEnumerator +{ +private: + InMemoryDataSource* mDataSource; + nsIRDFResource* mSource; + nsIRDFNode* mTarget; + AutoTArray, 8> mAlreadyReturned; + nsIRDFResource* mCurrent; + Assertion* mAssertion; + nsCOMArray* mHashArcs; + + virtual ~InMemoryArcsEnumeratorImpl(); + +public: + InMemoryArcsEnumeratorImpl(InMemoryDataSource* aDataSource, + nsIRDFResource* aSource, + nsIRDFNode* aTarget); + + // nsISupports interface + NS_DECL_ISUPPORTS + + // nsISimpleEnumerator interface + NS_DECL_NSISIMPLEENUMERATOR +}; + + +InMemoryArcsEnumeratorImpl::InMemoryArcsEnumeratorImpl(InMemoryDataSource* aDataSource, + nsIRDFResource* aSource, + nsIRDFNode* aTarget) + : mDataSource(aDataSource), + mSource(aSource), + mTarget(aTarget), + mCurrent(nullptr), + mHashArcs(nullptr) +{ + NS_ADDREF(mDataSource); + NS_IF_ADDREF(mSource); + NS_IF_ADDREF(mTarget); + + if (mSource) { + // cast okay because it's a closed system + mAssertion = mDataSource->GetForwardArcs(mSource); + + if (mAssertion && mAssertion->mHashEntry) { + // its our magical HASH_ENTRY forward hash for assertions + mHashArcs = new nsCOMArray(); + for (auto i = mAssertion->u.hash.mPropertyHash->Iter(); + !i.Done(); + i.Next()) { + auto entry = static_cast(i.Get()); + mHashArcs->AppendElement(entry->mNode); + } + mAssertion = nullptr; + } + } + else { + mAssertion = mDataSource->GetReverseArcs(mTarget); + } +} + +InMemoryArcsEnumeratorImpl::~InMemoryArcsEnumeratorImpl() +{ +#ifdef DEBUG_REFS + --gInstanceCount; + fprintf(stdout, "%d - RDF: InMemoryArcsEnumeratorImpl\n", gInstanceCount); +#endif + + NS_RELEASE(mDataSource); + NS_IF_RELEASE(mSource); + NS_IF_RELEASE(mTarget); + NS_IF_RELEASE(mCurrent); + delete mHashArcs; +} + +NS_IMPL_ADDREF(InMemoryArcsEnumeratorImpl) +NS_IMPL_RELEASE(InMemoryArcsEnumeratorImpl) +NS_IMPL_QUERY_INTERFACE(InMemoryArcsEnumeratorImpl, nsISimpleEnumerator) + +NS_IMETHODIMP +InMemoryArcsEnumeratorImpl::HasMoreElements(bool* aResult) +{ + NS_PRECONDITION(aResult != nullptr, "null ptr"); + if (! aResult) + return NS_ERROR_NULL_POINTER; + + if (mCurrent) { + *aResult = true; + return NS_OK; + } + + if (mHashArcs) { + if (!mHashArcs->IsEmpty()) { + const uint32_t last = mHashArcs->Length() - 1; + nsCOMPtr tmp(do_QueryInterface(mHashArcs->ObjectAt(last))); + tmp.forget(&mCurrent); + mHashArcs->RemoveElementAt(last); + *aResult = true; + return NS_OK; + } + } + else + while (mAssertion) { + nsIRDFResource* next = mAssertion->u.as.mProperty; + + // "next" is the property arc we are tentatively going to return + // in a subsequent GetNext() call. It is important to do two + // things, however, before that can happen: + // 1) Make sure it's not an arc we've already returned. + // 2) Make sure that |mAssertion| is not left pointing to + // another assertion that has the same property as this one. + // The first is a practical concern; the second a defense against + // an obscure crash and other erratic behavior. To ensure the + // second condition, skip down the chain until we find the next + // assertion with a property that doesn't match the current one. + // (All these assertions would be skipped via mAlreadyReturned + // checks anyways; this is even a bit faster.) + + do { + mAssertion = (mSource ? mAssertion->mNext : + mAssertion->u.as.mInvNext); + } + while (mAssertion && (next == mAssertion->u.as.mProperty)); + + bool alreadyReturned = false; + for (int32_t i = mAlreadyReturned.Length() - 1; i >= 0; --i) { + if (mAlreadyReturned[i] == next) { + alreadyReturned = true; + break; + } + } + + if (! alreadyReturned) { + mCurrent = next; + NS_ADDREF(mCurrent); + *aResult = true; + return NS_OK; + } + } + + *aResult = false; + return NS_OK; +} + + +NS_IMETHODIMP +InMemoryArcsEnumeratorImpl::GetNext(nsISupports** aResult) +{ + nsresult rv; + + bool hasMore; + rv = HasMoreElements(&hasMore); + if (NS_FAILED(rv)) return rv; + + if (! hasMore) + return NS_ERROR_UNEXPECTED; + + // Add this to the set of things we've already returned so that we + // can ensure uniqueness + mAlreadyReturned.AppendElement(mCurrent); + + // Don't AddRef: we "transfer" ownership to the caller + *aResult = mCurrent; + mCurrent = nullptr; + + return NS_OK; +} + + +//////////////////////////////////////////////////////////////////////// +// InMemoryDataSource + +nsresult +NS_NewRDFInMemoryDataSource(nsISupports* aOuter, const nsIID& aIID, void** aResult) +{ + NS_PRECONDITION(aResult != nullptr, "null ptr"); + if (! aResult) + return NS_ERROR_NULL_POINTER; + *aResult = nullptr; + + if (aOuter && !aIID.Equals(NS_GET_IID(nsISupports))) { + NS_ERROR("aggregation requires nsISupports"); + return NS_ERROR_ILLEGAL_VALUE; + } + + InMemoryDataSource* datasource = new InMemoryDataSource(aOuter); + NS_ADDREF(datasource); + + datasource->fAggregated.AddRef(); + nsresult rv = datasource->AggregatedQueryInterface(aIID, aResult); // This'll AddRef() + datasource->fAggregated.Release(); + + NS_RELEASE(datasource); + return rv; +} + + +InMemoryDataSource::InMemoryDataSource(nsISupports* aOuter) + : mForwardArcs(PLDHashTable::StubOps(), sizeof(Entry)) + , mReverseArcs(PLDHashTable::StubOps(), sizeof(Entry)) + , mNumObservers(0) + , mReadCount(0) +{ + NS_INIT_AGGREGATED(aOuter); + + mPropagateChanges = true; + MOZ_COUNT_CTOR(InMemoryDataSource); +} + + +InMemoryDataSource::~InMemoryDataSource() +{ +#ifdef DEBUG_REFS + --gInstanceCount; + fprintf(stdout, "%d - RDF: InMemoryDataSource\n", gInstanceCount); +#endif + + if (mForwardArcs.EntryCount() > 0) { + // This'll release all of the Assertion objects that are + // associated with this data source. We only need to do this + // for the forward arcs, because the reverse arcs table + // indexes the exact same set of resources. + for (auto iter = mForwardArcs.Iter(); !iter.Done(); iter.Next()) { + auto entry = static_cast(iter.Get()); + Assertion* as = entry->mAssertions; + while (as) { + Assertion* doomed = as; + as = as->mNext; + + // Unlink, and release the datasource's reference. + doomed->mNext = doomed->u.as.mInvNext = nullptr; + doomed->Release(); + } + } + } + + MOZ_LOG(gLog, LogLevel::Debug, + ("InMemoryDataSource(%p): destroyed.", this)); + + MOZ_COUNT_DTOR(InMemoryDataSource); +} + + +//////////////////////////////////////////////////////////////////////// + +NS_IMPL_CYCLE_COLLECTION_CLASS(InMemoryDataSource) + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(InMemoryDataSource) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mObservers) +NS_IMPL_CYCLE_COLLECTION_UNLINK_END +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_AGGREGATED(InMemoryDataSource) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mObservers) +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +NS_IMPL_CYCLE_COLLECTING_AGGREGATED(InMemoryDataSource) +NS_INTERFACE_MAP_BEGIN_AGGREGATED(InMemoryDataSource) + NS_INTERFACE_MAP_ENTRIES_CYCLE_COLLECTION_AGGREGATED(InMemoryDataSource) + NS_INTERFACE_MAP_ENTRY(nsIRDFDataSource) + NS_INTERFACE_MAP_ENTRY(nsIRDFInMemoryDataSource) + NS_INTERFACE_MAP_ENTRY(nsIRDFPropagatableDataSource) + NS_INTERFACE_MAP_ENTRY(nsIRDFPurgeableDataSource) + NS_INTERFACE_MAP_ENTRY(rdfIDataSource) +NS_INTERFACE_MAP_END + +//////////////////////////////////////////////////////////////////////// + + +void +InMemoryDataSource::LogOperation(const char* aOperation, + nsIRDFResource* aSource, + nsIRDFResource* aProperty, + nsIRDFNode* aTarget, + bool aTruthValue) +{ + if (! MOZ_LOG_TEST(gLog, LogLevel::Debug)) + return; + + nsXPIDLCString uri; + aSource->GetValue(getter_Copies(uri)); + PR_LogPrint + ("InMemoryDataSource(%p): %s", this, aOperation); + + PR_LogPrint + (" [(%p)%s]--", aSource, (const char*) uri); + + aProperty->GetValue(getter_Copies(uri)); + + char tv = (aTruthValue ? '-' : '!'); + PR_LogPrint + (" --%c[(%p)%s]--", tv, aProperty, (const char*) uri); + + nsCOMPtr resource; + nsCOMPtr literal; + + if ((resource = do_QueryInterface(aTarget)) != nullptr) { + resource->GetValue(getter_Copies(uri)); + PR_LogPrint + (" -->[(%p)%s]", aTarget, (const char*) uri); + } + else if ((literal = do_QueryInterface(aTarget)) != nullptr) { + nsXPIDLString value; + literal->GetValue(getter_Copies(value)); + nsAutoString valueStr(value); + char* valueCStr = ToNewCString(valueStr); + + PR_LogPrint + (" -->(\"%s\")\n", valueCStr); + + free(valueCStr); + } + else { + PR_LogPrint + (" -->(unknown-type)\n"); + } +} + + +NS_IMETHODIMP +InMemoryDataSource::GetURI(char* *uri) +{ + NS_PRECONDITION(uri != nullptr, "null ptr"); + if (! uri) + return NS_ERROR_NULL_POINTER; + + *uri = nullptr; + return NS_OK; +} + +NS_IMETHODIMP +InMemoryDataSource::GetSource(nsIRDFResource* property, + nsIRDFNode* target, + bool tv, + nsIRDFResource** source) +{ + NS_PRECONDITION(source != nullptr, "null ptr"); + if (! source) + return NS_ERROR_NULL_POINTER; + + NS_PRECONDITION(property != nullptr, "null ptr"); + if (! property) + return NS_ERROR_NULL_POINTER; + + NS_PRECONDITION(target != nullptr, "null ptr"); + if (! target) + return NS_ERROR_NULL_POINTER; + + for (Assertion* as = GetReverseArcs(target); as; as = as->u.as.mInvNext) { + if ((property == as->u.as.mProperty) && (tv == as->u.as.mTruthValue)) { + *source = as->mSource; + NS_ADDREF(*source); + return NS_OK; + } + } + *source = nullptr; + return NS_RDF_NO_VALUE; +} + +NS_IMETHODIMP +InMemoryDataSource::GetTarget(nsIRDFResource* source, + nsIRDFResource* property, + bool tv, + nsIRDFNode** target) +{ + NS_PRECONDITION(source != nullptr, "null ptr"); + if (! source) + return NS_ERROR_NULL_POINTER; + + NS_PRECONDITION(property != nullptr, "null ptr"); + if (! property) + return NS_ERROR_NULL_POINTER; + + NS_PRECONDITION(target != nullptr, "null ptr"); + if (! target) + return NS_ERROR_NULL_POINTER; + + Assertion *as = GetForwardArcs(source); + if (as && as->mHashEntry) { + PLDHashEntryHdr* hdr = as->u.hash.mPropertyHash->Search(property); + Assertion* val = hdr ? static_cast(hdr)->mAssertions : nullptr; + while (val) { + if (tv == val->u.as.mTruthValue) { + *target = val->u.as.mTarget; + NS_IF_ADDREF(*target); + return NS_OK; + } + val = val->mNext; + } + } + else + for (; as != nullptr; as = as->mNext) { + if ((property == as->u.as.mProperty) && (tv == (as->u.as.mTruthValue))) { + *target = as->u.as.mTarget; + NS_ADDREF(*target); + return NS_OK; + } + } + + // If we get here, then there was no target with for the specified + // property & truth value. + *target = nullptr; + return NS_RDF_NO_VALUE; +} + +NS_IMETHODIMP +InMemoryDataSource::HasAssertion(nsIRDFResource* source, + nsIRDFResource* property, + nsIRDFNode* target, + bool tv, + bool* hasAssertion) +{ + if (! source) + return NS_ERROR_NULL_POINTER; + + if (! property) + return NS_ERROR_NULL_POINTER; + + if (! target) + return NS_ERROR_NULL_POINTER; + + Assertion *as = GetForwardArcs(source); + if (as && as->mHashEntry) { + PLDHashEntryHdr* hdr = as->u.hash.mPropertyHash->Search(property); + Assertion* val = hdr ? static_cast(hdr)->mAssertions : nullptr; + while (val) { + if ((val->u.as.mTarget == target) && (tv == (val->u.as.mTruthValue))) { + *hasAssertion = true; + return NS_OK; + } + val = val->mNext; + } + } + else + for (; as != nullptr; as = as->mNext) { + // check target first as its most unique + if (target != as->u.as.mTarget) + continue; + + if (property != as->u.as.mProperty) + continue; + + if (tv != (as->u.as.mTruthValue)) + continue; + + // found it! + *hasAssertion = true; + return NS_OK; + } + + // If we get here, we couldn't find the assertion + *hasAssertion = false; + return NS_OK; +} + +NS_IMETHODIMP +InMemoryDataSource::GetSources(nsIRDFResource* aProperty, + nsIRDFNode* aTarget, + bool aTruthValue, + nsISimpleEnumerator** aResult) +{ + NS_PRECONDITION(aProperty != nullptr, "null ptr"); + if (! aProperty) + return NS_ERROR_NULL_POINTER; + + NS_PRECONDITION(aTarget != nullptr, "null ptr"); + if (! aTarget) + return NS_ERROR_NULL_POINTER; + + NS_PRECONDITION(aResult != nullptr, "null ptr"); + if (! aResult) + return NS_ERROR_NULL_POINTER; + + InMemoryAssertionEnumeratorImpl* result = + new InMemoryAssertionEnumeratorImpl(this, nullptr, aProperty, + aTarget, aTruthValue); + + if (! result) + return NS_ERROR_OUT_OF_MEMORY; + + NS_ADDREF(result); + *aResult = result; + + return NS_OK; +} + +NS_IMETHODIMP +InMemoryDataSource::GetTargets(nsIRDFResource* aSource, + nsIRDFResource* aProperty, + bool aTruthValue, + nsISimpleEnumerator** aResult) +{ + NS_PRECONDITION(aSource != nullptr, "null ptr"); + if (! aSource) + return NS_ERROR_NULL_POINTER; + + NS_PRECONDITION(aProperty != nullptr, "null ptr"); + if (! aProperty) + return NS_ERROR_NULL_POINTER; + + NS_PRECONDITION(aResult != nullptr, "null ptr"); + if (! aResult) + return NS_ERROR_NULL_POINTER; + + InMemoryAssertionEnumeratorImpl* result = + new InMemoryAssertionEnumeratorImpl(this, aSource, aProperty, + nullptr, aTruthValue); + + if (! result) + return NS_ERROR_OUT_OF_MEMORY; + + NS_ADDREF(result); + *aResult = result; + + return NS_OK; +} + + +nsresult +InMemoryDataSource::LockedAssert(nsIRDFResource* aSource, + nsIRDFResource* aProperty, + nsIRDFNode* aTarget, + bool aTruthValue) +{ + LogOperation("ASSERT", aSource, aProperty, aTarget, aTruthValue); + + Assertion* next = GetForwardArcs(aSource); + Assertion* prev = next; + Assertion* as = nullptr; + + bool haveHash = (next) ? next->mHashEntry : false; + if (haveHash) { + PLDHashEntryHdr* hdr = next->u.hash.mPropertyHash->Search(aProperty); + Assertion* val = hdr ? static_cast(hdr)->mAssertions : nullptr; + while (val) { + if (val->u.as.mTarget == aTarget) { + // Wow, we already had the assertion. Make sure that the + // truth values are correct and bail. + val->u.as.mTruthValue = aTruthValue; + return NS_OK; + } + val = val->mNext; + } + } + else + { + while (next) { + // check target first as its most unique + if (aTarget == next->u.as.mTarget) { + if (aProperty == next->u.as.mProperty) { + // Wow, we already had the assertion. Make sure that the + // truth values are correct and bail. + next->u.as.mTruthValue = aTruthValue; + return NS_OK; + } + } + + prev = next; + next = next->mNext; + } + } + + as = new Assertion(aSource, aProperty, aTarget, aTruthValue); + if (! as) + return NS_ERROR_OUT_OF_MEMORY; + + // Add the datasource's owning reference. + as->AddRef(); + + if (haveHash) + { + PLDHashEntryHdr* hdr = next->u.hash.mPropertyHash->Search(aProperty); + Assertion *asRef = + hdr ? static_cast(hdr)->mAssertions : nullptr; + if (asRef) + { + as->mNext = asRef->mNext; + asRef->mNext = as; + } + else + { + hdr = next->u.hash.mPropertyHash->Add(aProperty, mozilla::fallible); + if (hdr) + { + Entry* entry = static_cast(hdr); + entry->mNode = aProperty; + entry->mAssertions = as; + } + } + } + else + { + // Link it in to the "forward arcs" table + if (!prev) { + SetForwardArcs(aSource, as); + } else { + prev->mNext = as; + } + } + + // Link it in to the "reverse arcs" table + + next = GetReverseArcs(aTarget); + as->u.as.mInvNext = next; + next = as; + SetReverseArcs(aTarget, next); + + return NS_OK; +} + +NS_IMETHODIMP +InMemoryDataSource::Assert(nsIRDFResource* aSource, + nsIRDFResource* aProperty, + nsIRDFNode* aTarget, + bool aTruthValue) +{ + NS_PRECONDITION(aSource != nullptr, "null ptr"); + if (! aSource) + return NS_ERROR_NULL_POINTER; + + NS_PRECONDITION(aProperty != nullptr, "null ptr"); + if (! aProperty) + return NS_ERROR_NULL_POINTER; + + NS_PRECONDITION(aTarget != nullptr, "null ptr"); + if (! aTarget) + return NS_ERROR_NULL_POINTER; + + if (mReadCount) { + NS_WARNING("Writing to InMemoryDataSource during read\n"); + return NS_RDF_ASSERTION_REJECTED; + } + + nsresult rv; + rv = LockedAssert(aSource, aProperty, aTarget, aTruthValue); + if (NS_FAILED(rv)) return rv; + + // notify observers + for (int32_t i = (int32_t)mNumObservers - 1; mPropagateChanges && i >= 0; --i) { + nsIRDFObserver* obs = mObservers[i]; + + // XXX this should never happen, but it does, and we can't figure out why. + NS_ASSERTION(obs, "observer array corrupted!"); + if (! obs) + continue; + + obs->OnAssert(this, aSource, aProperty, aTarget); + // XXX ignore return value? + } + + return NS_RDF_ASSERTION_ACCEPTED; +} + + +nsresult +InMemoryDataSource::LockedUnassert(nsIRDFResource* aSource, + nsIRDFResource* aProperty, + nsIRDFNode* aTarget) +{ + LogOperation("UNASSERT", aSource, aProperty, aTarget); + + Assertion* next = GetForwardArcs(aSource); + Assertion* prev = next; + Assertion* root = next; + Assertion* as = nullptr; + + bool haveHash = (next) ? next->mHashEntry : false; + if (haveHash) { + PLDHashEntryHdr* hdr = next->u.hash.mPropertyHash->Search(aProperty); + prev = next = hdr ? static_cast(hdr)->mAssertions : nullptr; + bool first = true; + while (next) { + if (aTarget == next->u.as.mTarget) { + break; + } + first = false; + prev = next; + next = next->mNext; + } + // We don't even have the assertion, so just bail. + if (!next) + return NS_OK; + + as = next; + + if (first) { + root->u.hash.mPropertyHash->RawRemove(hdr); + + if (next && next->mNext) { + PLDHashEntryHdr* hdr = + root->u.hash.mPropertyHash->Add(aProperty, + mozilla::fallible); + if (hdr) { + Entry* entry = static_cast(hdr); + entry->mNode = aProperty; + entry->mAssertions = next->mNext; + } + } + else { + // If this second-level hash empties out, clean it up. + if (!root->u.hash.mPropertyHash->EntryCount()) { + root->Release(); + SetForwardArcs(aSource, nullptr); + } + } + } + else { + prev->mNext = next->mNext; + } + } + else + { + while (next) { + // check target first as its most unique + if (aTarget == next->u.as.mTarget) { + if (aProperty == next->u.as.mProperty) { + if (prev == next) { + SetForwardArcs(aSource, next->mNext); + } else { + prev->mNext = next->mNext; + } + as = next; + break; + } + } + + prev = next; + next = next->mNext; + } + } + // We don't even have the assertion, so just bail. + if (!as) + return NS_OK; + +#ifdef DEBUG + bool foundReverseArc = false; +#endif + + next = prev = GetReverseArcs(aTarget); + while (next) { + if (next == as) { + if (prev == next) { + SetReverseArcs(aTarget, next->u.as.mInvNext); + } else { + prev->u.as.mInvNext = next->u.as.mInvNext; + } +#ifdef DEBUG + foundReverseArc = true; +#endif + break; + } + prev = next; + next = next->u.as.mInvNext; + } + +#ifdef DEBUG + NS_ASSERTION(foundReverseArc, "in-memory db corrupted: unable to find reverse arc"); +#endif + + // Unlink, and release the datasource's reference + as->mNext = as->u.as.mInvNext = nullptr; + as->Release(); + + return NS_OK; +} + +NS_IMETHODIMP +InMemoryDataSource::Unassert(nsIRDFResource* aSource, + nsIRDFResource* aProperty, + nsIRDFNode* aTarget) +{ + NS_PRECONDITION(aSource != nullptr, "null ptr"); + if (! aSource) + return NS_ERROR_NULL_POINTER; + + NS_PRECONDITION(aProperty != nullptr, "null ptr"); + if (! aProperty) + return NS_ERROR_NULL_POINTER; + + NS_PRECONDITION(aTarget != nullptr, "null ptr"); + if (! aTarget) + return NS_ERROR_NULL_POINTER; + + if (mReadCount) { + NS_WARNING("Writing to InMemoryDataSource during read\n"); + return NS_RDF_ASSERTION_REJECTED; + } + + nsresult rv; + + rv = LockedUnassert(aSource, aProperty, aTarget); + if (NS_FAILED(rv)) return rv; + + // Notify the world + for (int32_t i = int32_t(mNumObservers) - 1; mPropagateChanges && i >= 0; --i) { + nsIRDFObserver* obs = mObservers[i]; + + // XXX this should never happen, but it does, and we can't figure out why. + NS_ASSERTION(obs, "observer array corrupted!"); + if (! obs) + continue; + + obs->OnUnassert(this, aSource, aProperty, aTarget); + // XXX ignore return value? + } + + return NS_RDF_ASSERTION_ACCEPTED; +} + + +NS_IMETHODIMP +InMemoryDataSource::Change(nsIRDFResource* aSource, + nsIRDFResource* aProperty, + nsIRDFNode* aOldTarget, + nsIRDFNode* aNewTarget) +{ + NS_PRECONDITION(aSource != nullptr, "null ptr"); + if (! aSource) + return NS_ERROR_NULL_POINTER; + + NS_PRECONDITION(aProperty != nullptr, "null ptr"); + if (! aProperty) + return NS_ERROR_NULL_POINTER; + + NS_PRECONDITION(aOldTarget != nullptr, "null ptr"); + if (! aOldTarget) + return NS_ERROR_NULL_POINTER; + + NS_PRECONDITION(aNewTarget != nullptr, "null ptr"); + if (! aNewTarget) + return NS_ERROR_NULL_POINTER; + + if (mReadCount) { + NS_WARNING("Writing to InMemoryDataSource during read\n"); + return NS_RDF_ASSERTION_REJECTED; + } + + nsresult rv; + + // XXX We can implement LockedChange() if we decide that this + // is a performance bottleneck. + + rv = LockedUnassert(aSource, aProperty, aOldTarget); + if (NS_FAILED(rv)) return rv; + + rv = LockedAssert(aSource, aProperty, aNewTarget, true); + if (NS_FAILED(rv)) return rv; + + // Notify the world + for (int32_t i = int32_t(mNumObservers) - 1; mPropagateChanges && i >= 0; --i) { + nsIRDFObserver* obs = mObservers[i]; + + // XXX this should never happen, but it does, and we can't figure out why. + NS_ASSERTION(obs, "observer array corrupted!"); + if (! obs) + continue; + + obs->OnChange(this, aSource, aProperty, aOldTarget, aNewTarget); + // XXX ignore return value? + } + + return NS_RDF_ASSERTION_ACCEPTED; +} + + +NS_IMETHODIMP +InMemoryDataSource::Move(nsIRDFResource* aOldSource, + nsIRDFResource* aNewSource, + nsIRDFResource* aProperty, + nsIRDFNode* aTarget) +{ + NS_PRECONDITION(aOldSource != nullptr, "null ptr"); + if (! aOldSource) + return NS_ERROR_NULL_POINTER; + + NS_PRECONDITION(aNewSource != nullptr, "null ptr"); + if (! aNewSource) + return NS_ERROR_NULL_POINTER; + + NS_PRECONDITION(aProperty != nullptr, "null ptr"); + if (! aProperty) + return NS_ERROR_NULL_POINTER; + + NS_PRECONDITION(aTarget != nullptr, "null ptr"); + if (! aTarget) + return NS_ERROR_NULL_POINTER; + + if (mReadCount) { + NS_WARNING("Writing to InMemoryDataSource during read\n"); + return NS_RDF_ASSERTION_REJECTED; + } + + nsresult rv; + + // XXX We can implement LockedMove() if we decide that this + // is a performance bottleneck. + + rv = LockedUnassert(aOldSource, aProperty, aTarget); + if (NS_FAILED(rv)) return rv; + + rv = LockedAssert(aNewSource, aProperty, aTarget, true); + if (NS_FAILED(rv)) return rv; + + // Notify the world + for (int32_t i = int32_t(mNumObservers) - 1; mPropagateChanges && i >= 0; --i) { + nsIRDFObserver* obs = mObservers[i]; + + // XXX this should never happen, but it does, and we can't figure out why. + NS_ASSERTION(obs, "observer array corrupted!"); + if (! obs) + continue; + + obs->OnMove(this, aOldSource, aNewSource, aProperty, aTarget); + // XXX ignore return value? + } + + return NS_RDF_ASSERTION_ACCEPTED; +} + + +NS_IMETHODIMP +InMemoryDataSource::AddObserver(nsIRDFObserver* aObserver) +{ + NS_PRECONDITION(aObserver != nullptr, "null ptr"); + if (! aObserver) + return NS_ERROR_NULL_POINTER; + + mObservers.AppendObject(aObserver); + mNumObservers = mObservers.Count(); + + return NS_OK; +} + +NS_IMETHODIMP +InMemoryDataSource::RemoveObserver(nsIRDFObserver* aObserver) +{ + NS_PRECONDITION(aObserver != nullptr, "null ptr"); + if (! aObserver) + return NS_ERROR_NULL_POINTER; + + mObservers.RemoveObject(aObserver); + // note: use Count() instead of just decrementing + // in case aObserver wasn't in list, for example + mNumObservers = mObservers.Count(); + + return NS_OK; +} + +NS_IMETHODIMP +InMemoryDataSource::HasArcIn(nsIRDFNode *aNode, nsIRDFResource *aArc, bool *result) +{ + Assertion* ass = GetReverseArcs(aNode); + while (ass) { + nsIRDFResource* elbow = ass->u.as.mProperty; + if (elbow == aArc) { + *result = true; + return NS_OK; + } + ass = ass->u.as.mInvNext; + } + *result = false; + return NS_OK; +} + +NS_IMETHODIMP +InMemoryDataSource::HasArcOut(nsIRDFResource *aSource, nsIRDFResource *aArc, bool *result) +{ + Assertion* ass = GetForwardArcs(aSource); + if (ass && ass->mHashEntry) { + PLDHashEntryHdr* hdr = ass->u.hash.mPropertyHash->Search(aArc); + Assertion* val = hdr ? static_cast(hdr)->mAssertions : nullptr; + if (val) { + *result = true; + return NS_OK; + } + ass = ass->mNext; + } + while (ass) { + nsIRDFResource* elbow = ass->u.as.mProperty; + if (elbow == aArc) { + *result = true; + return NS_OK; + } + ass = ass->mNext; + } + *result = false; + return NS_OK; +} + +NS_IMETHODIMP +InMemoryDataSource::ArcLabelsIn(nsIRDFNode* aTarget, nsISimpleEnumerator** aResult) +{ + NS_PRECONDITION(aTarget != nullptr, "null ptr"); + if (! aTarget) + return NS_ERROR_NULL_POINTER; + + InMemoryArcsEnumeratorImpl* result = + new InMemoryArcsEnumeratorImpl(this, nullptr, aTarget); + + if (! result) + return NS_ERROR_OUT_OF_MEMORY; + + NS_ADDREF(result); + *aResult = result; + + return NS_OK; +} + +NS_IMETHODIMP +InMemoryDataSource::ArcLabelsOut(nsIRDFResource* aSource, nsISimpleEnumerator** aResult) +{ + NS_PRECONDITION(aSource != nullptr, "null ptr"); + if (! aSource) + return NS_ERROR_NULL_POINTER; + + InMemoryArcsEnumeratorImpl* result = + new InMemoryArcsEnumeratorImpl(this, aSource, nullptr); + + if (! result) + return NS_ERROR_OUT_OF_MEMORY; + + NS_ADDREF(result); + *aResult = result; + + return NS_OK; +} + + +NS_IMETHODIMP +InMemoryDataSource::GetAllResources(nsISimpleEnumerator** aResult) +{ + nsCOMArray nodes; + nodes.SetCapacity(mForwardArcs.EntryCount()); + + // Get all of our entries into an nsCOMArray + for (auto iter = mForwardArcs.Iter(); !iter.Done(); iter.Next()) { + auto entry = static_cast(iter.Get()); + nodes.AppendObject(entry->mNode); + } + return NS_NewArrayEnumerator(aResult, nodes); +} + +NS_IMETHODIMP +InMemoryDataSource::GetAllCmds(nsIRDFResource* source, + nsISimpleEnumerator/**/** commands) +{ + return(NS_NewEmptyEnumerator(commands)); +} + +NS_IMETHODIMP +InMemoryDataSource::IsCommandEnabled(nsISupports* aSources, + nsIRDFResource* aCommand, + nsISupports* aArguments, + bool* aResult) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +InMemoryDataSource::DoCommand(nsISupports* aSources, + nsIRDFResource* aCommand, + nsISupports* aArguments) +{ + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +InMemoryDataSource::BeginUpdateBatch() +{ + for (int32_t i = int32_t(mNumObservers) - 1; mPropagateChanges && i >= 0; --i) { + nsIRDFObserver* obs = mObservers[i]; + obs->OnBeginUpdateBatch(this); + } + return NS_OK; +} + +NS_IMETHODIMP +InMemoryDataSource::EndUpdateBatch() +{ + for (int32_t i = int32_t(mNumObservers) - 1; mPropagateChanges && i >= 0; --i) { + nsIRDFObserver* obs = mObservers[i]; + obs->OnEndUpdateBatch(this); + } + return NS_OK; +} + + + +//////////////////////////////////////////////////////////////////////// +// nsIRDFInMemoryDataSource methods + +NS_IMETHODIMP +InMemoryDataSource::EnsureFastContainment(nsIRDFResource* aSource) +{ + Assertion *as = GetForwardArcs(aSource); + bool haveHash = (as) ? as->mHashEntry : false; + + // if its already a hash, then nothing to do + if (haveHash) return(NS_OK); + + // convert aSource in forward hash into a hash + Assertion *hashAssertion = new Assertion(aSource); + NS_ASSERTION(hashAssertion, "unable to create Assertion"); + if (!hashAssertion) return(NS_ERROR_OUT_OF_MEMORY); + + // Add the datasource's owning reference. + hashAssertion->AddRef(); + + Assertion *first = GetForwardArcs(aSource); + SetForwardArcs(aSource, hashAssertion); + + // mutate references of existing forward assertions into this hash + PLDHashTable *table = hashAssertion->u.hash.mPropertyHash; + Assertion *nextRef; + while(first) { + nextRef = first->mNext; + nsIRDFResource *prop = first->u.as.mProperty; + + PLDHashEntryHdr* hdr = table->Search(prop); + Assertion* val = hdr ? static_cast(hdr)->mAssertions : nullptr; + if (val) { + first->mNext = val->mNext; + val->mNext = first; + } + else { + PLDHashEntryHdr* hdr = table->Add(prop, mozilla::fallible); + if (hdr) { + Entry* entry = static_cast(hdr); + entry->mNode = prop; + entry->mAssertions = first; + first->mNext = nullptr; + } + } + first = nextRef; + } + return(NS_OK); +} + + +//////////////////////////////////////////////////////////////////////// +// nsIRDFPropagatableDataSource methods +NS_IMETHODIMP +InMemoryDataSource::GetPropagateChanges(bool* aPropagateChanges) +{ + *aPropagateChanges = mPropagateChanges; + return NS_OK; +} + +NS_IMETHODIMP +InMemoryDataSource::SetPropagateChanges(bool aPropagateChanges) +{ + mPropagateChanges = aPropagateChanges; + return NS_OK; +} + + +//////////////////////////////////////////////////////////////////////// +// nsIRDFPurgeableDataSource methods + +NS_IMETHODIMP +InMemoryDataSource::Mark(nsIRDFResource* aSource, + nsIRDFResource* aProperty, + nsIRDFNode* aTarget, + bool aTruthValue, + bool* aDidMark) +{ + NS_PRECONDITION(aSource != nullptr, "null ptr"); + if (! aSource) + return NS_ERROR_NULL_POINTER; + + NS_PRECONDITION(aProperty != nullptr, "null ptr"); + if (! aProperty) + return NS_ERROR_NULL_POINTER; + + NS_PRECONDITION(aTarget != nullptr, "null ptr"); + if (! aTarget) + return NS_ERROR_NULL_POINTER; + + Assertion *as = GetForwardArcs(aSource); + if (as && as->mHashEntry) { + PLDHashEntryHdr* hdr = as->u.hash.mPropertyHash->Search(aProperty); + Assertion* val = hdr ? static_cast(hdr)->mAssertions : nullptr; + while (val) { + if ((val->u.as.mTarget == aTarget) && + (aTruthValue == (val->u.as.mTruthValue))) { + + // found it! so mark it. + as->Mark(); + *aDidMark = true; + + LogOperation("MARK", aSource, aProperty, aTarget, aTruthValue); + + return NS_OK; + } + val = val->mNext; + } + } + else for (; as != nullptr; as = as->mNext) { + // check target first as its most unique + if (aTarget != as->u.as.mTarget) + continue; + + if (aProperty != as->u.as.mProperty) + continue; + + if (aTruthValue != (as->u.as.mTruthValue)) + continue; + + // found it! so mark it. + as->Mark(); + *aDidMark = true; + + LogOperation("MARK", aSource, aProperty, aTarget, aTruthValue); + + return NS_OK; + } + + // If we get here, we couldn't find the assertion + *aDidMark = false; + return NS_OK; +} + +NS_IMETHODIMP +InMemoryDataSource::Sweep() +{ + SweepInfo info = { nullptr, &mReverseArcs }; + + // Remove all the assertions, but don't notify anyone. + SweepForwardArcsEntries(&mForwardArcs, &info); + + // Now do the notification. + Assertion* as = info.mUnassertList; + while (as) { + LogOperation("SWEEP", as->mSource, as->u.as.mProperty, as->u.as.mTarget, as->u.as.mTruthValue); + if (!(as->mHashEntry)) + { + for (int32_t i = int32_t(mNumObservers) - 1; mPropagateChanges && i >= 0; --i) { + nsIRDFObserver* obs = mObservers[i]; + // XXXbz other loops over mObservers null-check |obs| here! + obs->OnUnassert(this, as->mSource, as->u.as.mProperty, as->u.as.mTarget); + // XXX ignore return value? + } + } + + Assertion* doomed = as; + as = as->mNext; + + // Unlink, and release the datasource's reference + doomed->mNext = doomed->u.as.mInvNext = nullptr; + doomed->Release(); + } + + return NS_OK; +} + + +void +InMemoryDataSource::SweepForwardArcsEntries(PLDHashTable* aTable, + SweepInfo* aInfo) +{ + for (auto iter = aTable->Iter(); !iter.Done(); iter.Next()) { + auto entry = static_cast(iter.Get()); + + Assertion* as = entry->mAssertions; + if (as && (as->mHashEntry)) { + // Stuff in sub-hashes must be swept recursively (max depth: 1) + SweepForwardArcsEntries(as->u.hash.mPropertyHash, aInfo); + + // If the sub-hash is now empty, clean it up. + if (!as->u.hash.mPropertyHash->EntryCount()) { + as->Release(); + iter.Remove(); + } + continue; + } + + Assertion* prev = nullptr; + while (as) { + if (as->IsMarked()) { + prev = as; + as->Unmark(); + as = as->mNext; + } + else { + // remove from the list of assertions in the datasource + Assertion* next = as->mNext; + if (prev) { + prev->mNext = next; + } + else { + // it's the first one. update the hashtable entry. + entry->mAssertions = next; + } + + // remove from the reverse arcs + PLDHashEntryHdr* hdr = + aInfo->mReverseArcs->Search(as->u.as.mTarget); + NS_ASSERTION(hdr, "no assertion in reverse arcs"); + + Entry* rentry = static_cast(hdr); + Assertion* ras = rentry->mAssertions; + Assertion* rprev = nullptr; + while (ras) { + if (ras == as) { + if (rprev) { + rprev->u.as.mInvNext = ras->u.as.mInvNext; + } + else { + // it's the first one. update the hashtable entry. + rentry->mAssertions = ras->u.as.mInvNext; + } + as->u.as.mInvNext = nullptr; // for my sanity. + break; + } + rprev = ras; + ras = ras->u.as.mInvNext; + } + + // Wow, it was the _only_ one. Unhash it. + if (! rentry->mAssertions) { + aInfo->mReverseArcs->RawRemove(hdr); + } + + // add to the list of assertions to unassert + as->mNext = aInfo->mUnassertList; + aInfo->mUnassertList = as; + + // Advance to the next assertion + as = next; + } + } + + // if no more assertions exist for this resource, then unhash it. + if (! entry->mAssertions) { + iter.Remove(); + } + } +} + +//////////////////////////////////////////////////////////////////////// +// rdfIDataSource methods + +NS_IMETHODIMP +InMemoryDataSource::VisitAllSubjects(rdfITripleVisitor *aVisitor) +{ + // Lock datasource against writes + ++mReadCount; + + // Enumerate all of our entries. + nsresult rv = NS_OK; + for (auto iter = mForwardArcs.Iter(); !iter.Done(); iter.Next()) { + auto entry = static_cast(iter.Get()); + nsresult rv2; + nsCOMPtr subject = do_QueryInterface(entry->mNode, &rv2); + if (NS_FAILED(rv2)) { + NS_WARNING("QI to nsIRDFNode failed"); + continue; + } + rv = aVisitor->Visit(subject, nullptr, nullptr, true); + if (NS_FAILED(rv) || rv == NS_RDF_STOP_VISIT) { + break; + } + } + + // Unlock datasource + --mReadCount; + + return rv; +} + +NS_IMETHODIMP +InMemoryDataSource::VisitAllTriples(rdfITripleVisitor *aVisitor) +{ + // Lock datasource against writes + ++mReadCount; + + // Enumerate all of our entries. + nsresult rv = NS_OK; + for (auto iter = mForwardArcs.Iter(); !iter.Done(); iter.Next()) { + auto entry = static_cast(iter.Get()); + + nsresult rv2; + nsCOMPtr subject = do_QueryInterface(entry->mNode, &rv2); + if (NS_FAILED(rv2)) { + NS_WARNING("QI to nsIRDFNode failed"); + + } else if (entry->mAssertions->mHashEntry) { + for (auto iter = entry->mAssertions->u.hash.mPropertyHash->Iter(); + !iter.Done(); + iter.Next()) { + auto entry = static_cast(iter.Get()); + Assertion* assertion = entry->mAssertions; + while (assertion) { + NS_ASSERTION(!assertion->mHashEntry, "shouldn't have to hashes"); + rv = aVisitor->Visit(subject, assertion->u.as.mProperty, + assertion->u.as.mTarget, + assertion->u.as.mTruthValue); + if (NS_FAILED(rv)) { + goto end; + } + if (rv == NS_RDF_STOP_VISIT) { + goto inner_end; + } + assertion = assertion->mNext; + } + } + + } else { + Assertion* assertion = entry->mAssertions; + while (assertion) { + NS_ASSERTION(!assertion->mHashEntry, "shouldn't have to hashes"); + rv = aVisitor->Visit(subject, assertion->u.as.mProperty, + assertion->u.as.mTarget, + assertion->u.as.mTruthValue); + if (NS_FAILED(rv) || rv == NS_RDF_STOP_VISIT) { + goto end; + } + assertion = assertion->mNext; + } + } + + inner_end: + (void) 0; + } + + end: + // Unlock datasource + --mReadCount; + + return rv; +} + +//////////////////////////////////////////////////////////////////////// + diff --git a/rdf/base/nsNameSpaceMap.cpp b/rdf/base/nsNameSpaceMap.cpp new file mode 100644 index 000000000..b486a233d --- /dev/null +++ b/rdf/base/nsNameSpaceMap.cpp @@ -0,0 +1,64 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsNameSpaceMap.h" +#include "nsReadableUtils.h" + +nsNameSpaceMap::nsNameSpaceMap() + : mEntries(nullptr) +{ + MOZ_COUNT_CTOR(nsNameSpaceMap); +} + +nsNameSpaceMap::~nsNameSpaceMap() +{ + MOZ_COUNT_DTOR(nsNameSpaceMap); + + while (mEntries) { + Entry* doomed = mEntries; + mEntries = mEntries->mNext; + delete doomed; + } +} + +nsresult +nsNameSpaceMap::Put(const nsAString& aURI, nsIAtom* aPrefix) +{ + nsCString uriUTF8; + AppendUTF16toUTF8(aURI, uriUTF8); + return Put(uriUTF8, aPrefix); +} + +nsresult +nsNameSpaceMap::Put(const nsCSubstring& aURI, nsIAtom* aPrefix) +{ + Entry* entry; + + // Make sure we're not adding a duplicate + for (entry = mEntries; entry != nullptr; entry = entry->mNext) { + if (entry->mURI == aURI || entry->mPrefix == aPrefix) + return NS_ERROR_FAILURE; + } + + entry = new Entry(aURI, aPrefix); + if (! entry) + return NS_ERROR_OUT_OF_MEMORY; + + entry->mNext = mEntries; + mEntries = entry; + return NS_OK; +} + +nsNameSpaceMap::const_iterator +nsNameSpaceMap::GetNameSpaceOf(const nsCSubstring& aURI) const +{ + for (Entry* entry = mEntries; entry != nullptr; entry = entry->mNext) { + if (StringBeginsWith(aURI, entry->mURI)) + return const_iterator(entry); + } + + return last(); +} diff --git a/rdf/base/nsNameSpaceMap.h b/rdf/base/nsNameSpaceMap.h new file mode 100644 index 000000000..bc7c02029 --- /dev/null +++ b/rdf/base/nsNameSpaceMap.h @@ -0,0 +1,98 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsNameSpaceMap_h__ +#define nsNameSpaceMap_h__ + +#include "nsCOMPtr.h" +#include "nsString.h" +#include "nsIAtom.h" + +class nsNameSpaceMap +{ +public: + class Entry { + public: + Entry(const nsCSubstring& aURI, nsIAtom* aPrefix) + : mURI(aURI), mPrefix(aPrefix), mNext(nullptr) { + MOZ_COUNT_CTOR(nsNameSpaceMap::Entry); } + + ~Entry() { MOZ_COUNT_DTOR(nsNameSpaceMap::Entry); } + + nsCString mURI; + nsCOMPtr mPrefix; + + Entry* mNext; + }; + + nsNameSpaceMap(); + ~nsNameSpaceMap(); + + nsresult + Put(const nsAString& aURI, nsIAtom* aPrefix); + + nsresult + Put(const nsCSubstring& aURI, nsIAtom* aPrefix); + + class const_iterator { + protected: + friend class nsNameSpaceMap; + + explicit const_iterator(const Entry* aCurrent) + : mCurrent(aCurrent) {} + + const Entry* mCurrent; + + public: + const_iterator() + : mCurrent(nullptr) {} + + const_iterator(const const_iterator& iter) + : mCurrent(iter.mCurrent) {} + + const_iterator& + operator=(const const_iterator& iter) { + mCurrent = iter.mCurrent; + return *this; } + + const_iterator& + operator++() { + mCurrent = mCurrent->mNext; + return *this; } + + const_iterator + operator++(int) { + const_iterator tmp(*this); + mCurrent = mCurrent->mNext; + return tmp; } + + const Entry* operator->() const { return mCurrent; } + + const Entry& operator*() const { return *mCurrent; } + + bool + operator==(const const_iterator& iter) const { + return mCurrent == iter.mCurrent; } + + bool + operator!=(const const_iterator& iter) const { + return ! iter.operator==(*this); } + }; + + const_iterator first() const { + return const_iterator(mEntries); } + + const_iterator last() const { + return const_iterator(nullptr); } + + const_iterator GetNameSpaceOf(const nsCSubstring& aURI) const; + +protected: + Entry* mEntries; +}; + + +#endif // nsNameSpaceMap_h__ diff --git a/rdf/base/nsRDFBaseDataSources.h b/rdf/base/nsRDFBaseDataSources.h new file mode 100644 index 000000000..0243e1359 --- /dev/null +++ b/rdf/base/nsRDFBaseDataSources.h @@ -0,0 +1,32 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + + This header file just contains prototypes for the factory methods + for "builtin" data sources that are included in rdf.dll. + + Each of these data sources is exposed to the external world via its + CID in ../include/nsRDFCID.h. + +*/ + +#ifndef nsBaseDataSources_h__ +#define nsBaseDataSources_h__ + +#include "nsError.h" +class nsIRDFDataSource; + +// in nsInMemoryDataSource.cpp +nsresult +NS_NewRDFInMemoryDataSource(nsISupports* aOuter, const nsIID& aIID, void** aResult); + +// in nsRDFXMLDataSource.cpp +extern nsresult +NS_NewRDFXMLDataSource(nsIRDFDataSource** aResult); + +#endif // nsBaseDataSources_h__ + + diff --git a/rdf/base/nsRDFContainer.cpp b/rdf/base/nsRDFContainer.cpp new file mode 100644 index 000000000..6000c70d5 --- /dev/null +++ b/rdf/base/nsRDFContainer.cpp @@ -0,0 +1,726 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + + Implementation for the RDF container. + + Notes + ----- + + 1. RDF containers are one-indexed. This means that a lot of the loops + that you'd normally think you'd write like this: + + for (i = 0; i < count; ++i) {} + + You've gotta write like this: + + for (i = 1; i <= count; ++i) {} + + "Sure, right, yeah, of course.", you say. Well maybe I'm just + thick, but it's easy to slip up. + + 2. The RDF:nextVal property on the container is an + implementation-level hack that is used to quickly compute the + next value for appending to the container. It will no doubt + become royally screwed up in the case of aggregation. + + 3. The RDF:nextVal property is also used to retrieve the count of + elements in the container. + + */ + + +#include "nsCOMPtr.h" +#include "nsIRDFContainer.h" +#include "nsIRDFContainerUtils.h" +#include "nsIRDFInMemoryDataSource.h" +#include "nsIRDFPropagatableDataSource.h" +#include "nsIRDFService.h" +#include "nsIServiceManager.h" +#include "nsRDFCID.h" +#include "nsString.h" +#include "nsXPIDLString.h" +#include "rdf.h" + +#define RDF_SEQ_LIST_LIMIT 8 + +class RDFContainerImpl : public nsIRDFContainer +{ +public: + + // nsISupports interface + NS_DECL_ISUPPORTS + + // nsIRDFContainer interface + NS_DECL_NSIRDFCONTAINER + +private: + friend nsresult NS_NewRDFContainer(nsIRDFContainer** aResult); + + RDFContainerImpl(); + virtual ~RDFContainerImpl(); + + nsresult Init(); + + nsresult Renumber(int32_t aStartIndex, int32_t aIncrement); + nsresult SetNextValue(int32_t aIndex); + nsresult GetNextValue(nsIRDFResource** aResult); + + nsIRDFDataSource* mDataSource; + nsIRDFResource* mContainer; + + // pseudo constants + static int32_t gRefCnt; + static nsIRDFService* gRDFService; + static nsIRDFContainerUtils* gRDFContainerUtils; + static nsIRDFResource* kRDF_nextVal; +}; + + +int32_t RDFContainerImpl::gRefCnt = 0; +nsIRDFService* RDFContainerImpl::gRDFService; +nsIRDFContainerUtils* RDFContainerImpl::gRDFContainerUtils; +nsIRDFResource* RDFContainerImpl::kRDF_nextVal; + +//////////////////////////////////////////////////////////////////////// +// nsISupports interface + +NS_IMPL_ISUPPORTS(RDFContainerImpl, nsIRDFContainer) + + + +//////////////////////////////////////////////////////////////////////// +// nsIRDFContainer interface + +NS_IMETHODIMP +RDFContainerImpl::GetDataSource(nsIRDFDataSource** _retval) +{ + *_retval = mDataSource; + NS_IF_ADDREF(*_retval); + return NS_OK; +} + + +NS_IMETHODIMP +RDFContainerImpl::GetResource(nsIRDFResource** _retval) +{ + *_retval = mContainer; + NS_IF_ADDREF(*_retval); + return NS_OK; +} + + +NS_IMETHODIMP +RDFContainerImpl::Init(nsIRDFDataSource *aDataSource, nsIRDFResource *aContainer) +{ + NS_PRECONDITION(aDataSource != nullptr, "null ptr"); + if (! aDataSource) + return NS_ERROR_NULL_POINTER; + + NS_PRECONDITION(aContainer != nullptr, "null ptr"); + if (! aContainer) + return NS_ERROR_NULL_POINTER; + + nsresult rv; + bool isContainer; + rv = gRDFContainerUtils->IsContainer(aDataSource, aContainer, &isContainer); + if (NS_FAILED(rv)) return rv; + + // ``throw'' if we can't create a container on the specified + // datasource/resource combination. + if (! isContainer) + return NS_ERROR_FAILURE; + + NS_IF_RELEASE(mDataSource); + mDataSource = aDataSource; + NS_ADDREF(mDataSource); + + NS_IF_RELEASE(mContainer); + mContainer = aContainer; + NS_ADDREF(mContainer); + + return NS_OK; +} + + +NS_IMETHODIMP +RDFContainerImpl::GetCount(int32_t *aCount) +{ + if (!mDataSource || !mContainer) + return NS_ERROR_NOT_INITIALIZED; + + nsresult rv; + + // Get the next value, which hangs off of the bag via the + // RDF:nextVal property. This is the _next value_ that will get + // assigned in a one-indexed array. So, it's actually _one more_ + // than the actual count of elements in the container. + // + // XXX To handle aggregation, this should probably be a + // GetTargets() that enumerates all of the values and picks the + // largest one. + nsCOMPtr nextValNode; + rv = mDataSource->GetTarget(mContainer, kRDF_nextVal, true, getter_AddRefs(nextValNode)); + if (NS_FAILED(rv)) return rv; + + if (rv == NS_RDF_NO_VALUE) + return NS_ERROR_UNEXPECTED; + + nsCOMPtr nextValLiteral; + rv = nextValNode->QueryInterface(NS_GET_IID(nsIRDFLiteral), getter_AddRefs(nextValLiteral)); + if (NS_FAILED(rv)) return rv; + + const char16_t *s; + rv = nextValLiteral->GetValueConst( &s ); + if (NS_FAILED(rv)) return rv; + + nsAutoString nextValStr(s); + + int32_t nextVal; + nsresult err; + nextVal = nextValStr.ToInteger(&err); + if (NS_FAILED(err)) + return NS_ERROR_UNEXPECTED; + + *aCount = nextVal - 1; + return NS_OK; +} + + +NS_IMETHODIMP +RDFContainerImpl::GetElements(nsISimpleEnumerator **_retval) +{ + if (!mDataSource || !mContainer) + return NS_ERROR_NOT_INITIALIZED; + + return NS_NewContainerEnumerator(mDataSource, mContainer, _retval); +} + + +NS_IMETHODIMP +RDFContainerImpl::AppendElement(nsIRDFNode *aElement) +{ + if (!mDataSource || !mContainer) + return NS_ERROR_NOT_INITIALIZED; + + NS_PRECONDITION(aElement != nullptr, "null ptr"); + if (! aElement) + return NS_ERROR_NULL_POINTER; + + nsresult rv; + + nsCOMPtr nextVal; + rv = GetNextValue(getter_AddRefs(nextVal)); + if (NS_FAILED(rv)) return rv; + + rv = mDataSource->Assert(mContainer, nextVal, aElement, true); + if (NS_FAILED(rv)) return rv; + + return NS_OK; +} + + +NS_IMETHODIMP +RDFContainerImpl::RemoveElement(nsIRDFNode *aElement, bool aRenumber) +{ + if (!mDataSource || !mContainer) + return NS_ERROR_NOT_INITIALIZED; + + NS_PRECONDITION(aElement != nullptr, "null ptr"); + if (! aElement) + return NS_ERROR_NULL_POINTER; + + nsresult rv; + + int32_t idx; + rv = IndexOf(aElement, &idx); + if (NS_FAILED(rv)) return rv; + + if (idx < 0) + return NS_OK; + + // Remove the element. + nsCOMPtr ordinal; + rv = gRDFContainerUtils->IndexToOrdinalResource(idx, + getter_AddRefs(ordinal)); + if (NS_FAILED(rv)) return rv; + + rv = mDataSource->Unassert(mContainer, ordinal, aElement); + if (NS_FAILED(rv)) return rv; + + if (aRenumber) { + // Now slide the rest of the collection backwards to fill in + // the gap. This will have the side effect of completely + // renumber the container from index to the end. + rv = Renumber(idx + 1, -1); + if (NS_FAILED(rv)) return rv; + } + + return NS_OK; +} + + +NS_IMETHODIMP +RDFContainerImpl::InsertElementAt(nsIRDFNode *aElement, int32_t aIndex, bool aRenumber) +{ + if (!mDataSource || !mContainer) + return NS_ERROR_NOT_INITIALIZED; + + NS_PRECONDITION(aElement != nullptr, "null ptr"); + if (! aElement) + return NS_ERROR_NULL_POINTER; + + NS_PRECONDITION(aIndex >= 1, "illegal value"); + if (aIndex < 1) + return NS_ERROR_ILLEGAL_VALUE; + + nsresult rv; + + int32_t count; + rv = GetCount(&count); + if (NS_FAILED(rv)) return rv; + + NS_ASSERTION(aIndex <= count + 1, "illegal value"); + if (aIndex > count + 1) + return NS_ERROR_ILLEGAL_VALUE; + + if (aRenumber) { + // Make a hole for the element. This will have the side effect of + // completely renumbering the container from 'aIndex' to 'count', + // and will spew assertions. + rv = Renumber(aIndex, +1); + if (NS_FAILED(rv)) return rv; + } + + nsCOMPtr ordinal; + rv = gRDFContainerUtils->IndexToOrdinalResource(aIndex, getter_AddRefs(ordinal)); + if (NS_FAILED(rv)) return rv; + + rv = mDataSource->Assert(mContainer, ordinal, aElement, true); + if (NS_FAILED(rv)) return rv; + + return NS_OK; +} + +NS_IMETHODIMP +RDFContainerImpl::RemoveElementAt(int32_t aIndex, bool aRenumber, nsIRDFNode** _retval) +{ + if (!mDataSource || !mContainer) + return NS_ERROR_NOT_INITIALIZED; + + *_retval = nullptr; + + if (aIndex< 1) + return NS_ERROR_ILLEGAL_VALUE; + + nsresult rv; + + int32_t count; + rv = GetCount(&count); + if (NS_FAILED(rv)) return rv; + + if (aIndex > count) + return NS_ERROR_ILLEGAL_VALUE; + + nsCOMPtr ordinal; + rv = gRDFContainerUtils->IndexToOrdinalResource(aIndex, getter_AddRefs(ordinal)); + if (NS_FAILED(rv)) return rv; + + nsCOMPtr old; + rv = mDataSource->GetTarget(mContainer, ordinal, true, getter_AddRefs(old)); + if (NS_FAILED(rv)) return rv; + + if (rv == NS_OK) { + rv = mDataSource->Unassert(mContainer, ordinal, old); + if (NS_FAILED(rv)) return rv; + + if (aRenumber) { + // Now slide the rest of the collection backwards to fill in + // the gap. This will have the side effect of completely + // renumber the container from index to the end. + rv = Renumber(aIndex + 1, -1); + if (NS_FAILED(rv)) return rv; + } + } + + old.swap(*_retval); + + return NS_OK; +} + +NS_IMETHODIMP +RDFContainerImpl::IndexOf(nsIRDFNode *aElement, int32_t *aIndex) +{ + if (!mDataSource || !mContainer) + return NS_ERROR_NOT_INITIALIZED; + + return gRDFContainerUtils->IndexOf(mDataSource, mContainer, + aElement, aIndex); +} + + +//////////////////////////////////////////////////////////////////////// + + +RDFContainerImpl::RDFContainerImpl() + : mDataSource(nullptr), mContainer(nullptr) +{ +} + + +nsresult +RDFContainerImpl::Init() +{ + if (gRefCnt++ == 0) { + nsresult rv; + + NS_DEFINE_CID(kRDFServiceCID, NS_RDFSERVICE_CID); + rv = CallGetService(kRDFServiceCID, &gRDFService); + if (NS_FAILED(rv)) { + NS_ERROR("unable to get RDF service"); + return rv; + } + + rv = gRDFService->GetResource(NS_LITERAL_CSTRING(RDF_NAMESPACE_URI "nextVal"), + &kRDF_nextVal); + if (NS_FAILED(rv)) return rv; + + NS_DEFINE_CID(kRDFContainerUtilsCID, NS_RDFCONTAINERUTILS_CID); + rv = CallGetService(kRDFContainerUtilsCID, &gRDFContainerUtils); + if (NS_FAILED(rv)) { + NS_ERROR("unable to get RDF container utils service"); + return rv; + } + } + + return NS_OK; +} + + +RDFContainerImpl::~RDFContainerImpl() +{ +#ifdef DEBUG_REFS + --gInstanceCount; + fprintf(stdout, "%d - RDF: RDFContainerImpl\n", gInstanceCount); +#endif + + NS_IF_RELEASE(mContainer); + NS_IF_RELEASE(mDataSource); + + if (--gRefCnt == 0) { + NS_IF_RELEASE(gRDFContainerUtils); + NS_IF_RELEASE(gRDFService); + NS_IF_RELEASE(kRDF_nextVal); + } +} + + +nsresult +NS_NewRDFContainer(nsIRDFContainer** aResult) +{ + RDFContainerImpl* result = new RDFContainerImpl(); + if (! result) + return NS_ERROR_OUT_OF_MEMORY; + + nsresult rv; + rv = result->Init(); + if (NS_FAILED(rv)) { + delete result; + return rv; + } + + NS_ADDREF(result); + *aResult = result; + return NS_OK; +} + + +nsresult +NS_NewRDFContainer(nsIRDFDataSource* aDataSource, + nsIRDFResource* aResource, + nsIRDFContainer** aResult) +{ + nsresult rv; + rv = NS_NewRDFContainer(aResult); + if (NS_FAILED(rv)) return rv; + + rv = (*aResult)->Init(aDataSource, aResource); + if (NS_FAILED(rv)) { + NS_RELEASE(*aResult); + } + return rv; +} + + +nsresult +RDFContainerImpl::Renumber(int32_t aStartIndex, int32_t aIncrement) +{ + if (!mDataSource || !mContainer) + return NS_ERROR_NOT_INITIALIZED; + + // Renumber the elements in the container starting with + // aStartIndex, updating each element's index by aIncrement. For + // example, + // + // (1:a 2:b 3:c) + // Renumber(2, +1); + // (1:a 3:b 4:c) + // Renumber(3, -1); + // (1:a 2:b 3:c) + // + nsresult rv; + + if (! aIncrement) + return NS_OK; + + int32_t count; + rv = GetCount(&count); + if (NS_FAILED(rv)) return rv; + + if (aIncrement > 0) { + // Update the container's nextVal to reflect the + // renumbering. We do this now if aIncrement > 0 because we'll + // want to be able to acknowledge that new elements are in the + // container. + rv = SetNextValue(count + aIncrement + 1); + if (NS_FAILED(rv)) return rv; + } + + int32_t i; + if (aIncrement < 0) { + i = aStartIndex; + } + else { + i = count; // we're one-indexed. + } + + // Note: once we disable notifications, don't exit this method until + // enabling notifications + nsCOMPtr propagatable = + do_QueryInterface(mDataSource); + if (propagatable) { + propagatable->SetPropagateChanges(false); + } + + bool err = false; + while (!err && ((aIncrement < 0) ? (i <= count) : (i >= aStartIndex))) + { + nsCOMPtr oldOrdinal; + rv = gRDFContainerUtils->IndexToOrdinalResource(i, getter_AddRefs(oldOrdinal)); + if (NS_FAILED(rv)) + { + err = true; + continue; + } + + nsCOMPtr newOrdinal; + rv = gRDFContainerUtils->IndexToOrdinalResource(i + aIncrement, getter_AddRefs(newOrdinal)); + if (NS_FAILED(rv)) + { + err = true; + continue; + } + + // Because of aggregation, we need to be paranoid about the + // possibility that >1 element may be present per ordinal. If + // there _is_ in fact more than one element, they'll all get + // assigned to the same new ordinal; i.e., we don't make any + // attempt to "clean up" the duplicate numbering. (Doing so + // would require two passes.) + nsCOMPtr targets; + rv = mDataSource->GetTargets(mContainer, oldOrdinal, true, getter_AddRefs(targets)); + if (NS_FAILED(rv)) + { + err = true; + continue; + } + + while (1) { + bool hasMore; + rv = targets->HasMoreElements(&hasMore); + if (NS_FAILED(rv)) + { + err = true; + break; + } + + if (! hasMore) + break; + + nsCOMPtr isupports; + rv = targets->GetNext(getter_AddRefs(isupports)); + if (NS_FAILED(rv)) + { + err = true; + break; + } + + nsCOMPtr element( do_QueryInterface(isupports) ); + NS_ASSERTION(element != nullptr, "something funky in the enumerator"); + if (! element) + { + err = true; + rv = NS_ERROR_UNEXPECTED; + break; + } + + rv = mDataSource->Unassert(mContainer, oldOrdinal, element); + if (NS_FAILED(rv)) + { + err = true; + break; + } + + rv = mDataSource->Assert(mContainer, newOrdinal, element, true); + if (NS_FAILED(rv)) + { + err = true; + break; + } + } + + i -= aIncrement; + } + + if (!err && (aIncrement < 0)) + { + // Update the container's nextVal to reflect the + // renumbering. We do this now if aIncrement < 0 because, up + // until this point, we'll want people to be able to find + // things that are still "at the end". + rv = SetNextValue(count + aIncrement + 1); + if (NS_FAILED(rv)) + { + err = true; + } + } + + // Note: MUST enable notifications before exiting this method + if (propagatable) { + propagatable->SetPropagateChanges(true); + } + + if (err) return(rv); + + return NS_OK; +} + + + +nsresult +RDFContainerImpl::SetNextValue(int32_t aIndex) +{ + if (!mDataSource || !mContainer) + return NS_ERROR_NOT_INITIALIZED; + + nsresult rv; + + // Remove the current value of nextVal, if there is one. + nsCOMPtr nextValNode; + if (NS_SUCCEEDED(rv = mDataSource->GetTarget(mContainer, + kRDF_nextVal, + true, + getter_AddRefs(nextValNode)))) { + if (NS_FAILED(rv = mDataSource->Unassert(mContainer, kRDF_nextVal, nextValNode))) { + NS_ERROR("unable to update nextVal"); + return rv; + } + } + + nsAutoString s; + s.AppendInt(aIndex, 10); + + nsCOMPtr nextVal; + if (NS_FAILED(rv = gRDFService->GetLiteral(s.get(), getter_AddRefs(nextVal)))) { + NS_ERROR("unable to get nextVal literal"); + return rv; + } + + rv = mDataSource->Assert(mContainer, kRDF_nextVal, nextVal, true); + if (rv != NS_RDF_ASSERTION_ACCEPTED) { + NS_ERROR("unable to update nextVal"); + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + + +nsresult +RDFContainerImpl::GetNextValue(nsIRDFResource** aResult) +{ + if (!mDataSource || !mContainer) + return NS_ERROR_NOT_INITIALIZED; + + nsresult rv; + + // Get the next value, which hangs off of the bag via the + // RDF:nextVal property. + nsCOMPtr nextValNode; + rv = mDataSource->GetTarget(mContainer, kRDF_nextVal, true, getter_AddRefs(nextValNode)); + if (NS_FAILED(rv)) return rv; + + if (rv == NS_RDF_NO_VALUE) + return NS_ERROR_UNEXPECTED; + + nsCOMPtr nextValLiteral; + rv = nextValNode->QueryInterface(NS_GET_IID(nsIRDFLiteral), getter_AddRefs(nextValLiteral)); + if (NS_FAILED(rv)) return rv; + + const char16_t* s; + rv = nextValLiteral->GetValueConst(&s); + if (NS_FAILED(rv)) return rv; + + int32_t nextVal = 0; + { + for (const char16_t* p = s; *p != 0; ++p) { + NS_ASSERTION(*p >= '0' && *p <= '9', "not a digit"); + if (*p < '0' || *p > '9') + break; + + nextVal *= 10; + nextVal += *p - '0'; + } + } + + static const char kRDFNameSpaceURI[] = RDF_NAMESPACE_URI; + char buf[sizeof(kRDFNameSpaceURI) + 16]; + nsFixedCString nextValStr(buf, sizeof(buf), 0); + nextValStr = kRDFNameSpaceURI; + nextValStr.Append('_'); + nextValStr.AppendInt(nextVal, 10); + + rv = gRDFService->GetResource(nextValStr, aResult); + if (NS_FAILED(rv)) return rv; + + // Now increment the RDF:nextVal property. + rv = mDataSource->Unassert(mContainer, kRDF_nextVal, nextValLiteral); + if (NS_FAILED(rv)) return rv; + + ++nextVal; + nextValStr.Truncate(); + nextValStr.AppendInt(nextVal, 10); + + rv = gRDFService->GetLiteral(NS_ConvertASCIItoUTF16(nextValStr).get(), getter_AddRefs(nextValLiteral)); + if (NS_FAILED(rv)) return rv; + + rv = mDataSource->Assert(mContainer, kRDF_nextVal, nextValLiteral, true); + if (NS_FAILED(rv)) return rv; + + if (RDF_SEQ_LIST_LIMIT == nextVal) + { + // focal point for RDF container mutation; + // basically, provide a hint to allow for fast access + nsCOMPtr inMem = do_QueryInterface(mDataSource); + if (inMem) + { + // ignore error; failure just means slower access + (void)inMem->EnsureFastContainment(mContainer); + } + } + + return NS_OK; +} diff --git a/rdf/base/nsRDFContainerUtils.cpp b/rdf/base/nsRDFContainerUtils.cpp new file mode 100644 index 000000000..299722d4b --- /dev/null +++ b/rdf/base/nsRDFContainerUtils.cpp @@ -0,0 +1,515 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + + Implementation for the RDF container utils. + + */ + + +#include "nsCOMPtr.h" +#include "nsIServiceManager.h" +#include "nsIRDFContainer.h" +#include "nsIRDFContainerUtils.h" +#include "nsIRDFService.h" +#include "nsRDFCID.h" +#include "nsString.h" +#include "nsXPIDLString.h" +#include "plstr.h" +#include "prprf.h" +#include "rdf.h" +#include "rdfutil.h" + +class RDFContainerUtilsImpl : public nsIRDFContainerUtils +{ +public: + // nsISupports interface + NS_DECL_ISUPPORTS + + // nsIRDFContainerUtils interface + NS_DECL_NSIRDFCONTAINERUTILS + +private: + friend nsresult NS_NewRDFContainerUtils(nsIRDFContainerUtils** aResult); + + RDFContainerUtilsImpl(); + virtual ~RDFContainerUtilsImpl(); + + nsresult MakeContainer(nsIRDFDataSource* aDataSource, + nsIRDFResource* aResource, + nsIRDFResource* aType, + nsIRDFContainer** aResult); + + bool IsA(nsIRDFDataSource* aDataSource, nsIRDFResource* aResource, nsIRDFResource* aType); + + // pseudo constants + static int32_t gRefCnt; + static nsIRDFService* gRDFService; + static nsIRDFResource* kRDF_instanceOf; + static nsIRDFResource* kRDF_nextVal; + static nsIRDFResource* kRDF_Bag; + static nsIRDFResource* kRDF_Seq; + static nsIRDFResource* kRDF_Alt; + static nsIRDFLiteral* kOne; + static const char kRDFNameSpaceURI[]; +}; + +int32_t RDFContainerUtilsImpl::gRefCnt = 0; +nsIRDFService* RDFContainerUtilsImpl::gRDFService; +nsIRDFResource* RDFContainerUtilsImpl::kRDF_instanceOf; +nsIRDFResource* RDFContainerUtilsImpl::kRDF_nextVal; +nsIRDFResource* RDFContainerUtilsImpl::kRDF_Bag; +nsIRDFResource* RDFContainerUtilsImpl::kRDF_Seq; +nsIRDFResource* RDFContainerUtilsImpl::kRDF_Alt; +nsIRDFLiteral* RDFContainerUtilsImpl::kOne; +const char RDFContainerUtilsImpl::kRDFNameSpaceURI[] = RDF_NAMESPACE_URI; + +//////////////////////////////////////////////////////////////////////// +// nsISupports interface + +NS_IMPL_ISUPPORTS(RDFContainerUtilsImpl, nsIRDFContainerUtils) + +//////////////////////////////////////////////////////////////////////// +// nsIRDFContainerUtils interface + +NS_IMETHODIMP +RDFContainerUtilsImpl::IsOrdinalProperty(nsIRDFResource *aProperty, bool *_retval) +{ + NS_PRECONDITION(aProperty != nullptr, "null ptr"); + if (! aProperty) + return NS_ERROR_NULL_POINTER; + + nsresult rv; + + const char *propertyStr; + rv = aProperty->GetValueConst( &propertyStr ); + if (NS_FAILED(rv)) return rv; + + if (PL_strncmp(propertyStr, kRDFNameSpaceURI, sizeof(kRDFNameSpaceURI) - 1) != 0) { + *_retval = false; + return NS_OK; + } + + const char* s = propertyStr; + s += sizeof(kRDFNameSpaceURI) - 1; + if (*s != '_') { + *_retval = false; + return NS_OK; + } + + ++s; + while (*s) { + if (*s < '0' || *s > '9') { + *_retval = false; + return NS_OK; + } + + ++s; + } + + *_retval = true; + return NS_OK; +} + + +NS_IMETHODIMP +RDFContainerUtilsImpl::IndexToOrdinalResource(int32_t aIndex, nsIRDFResource **aOrdinal) +{ + NS_PRECONDITION(aIndex > 0, "illegal value"); + if (aIndex <= 0) + return NS_ERROR_ILLEGAL_VALUE; + + nsAutoCString uri(kRDFNameSpaceURI); + uri.Append('_'); + uri.AppendInt(aIndex); + + nsresult rv = gRDFService->GetResource(uri, aOrdinal); + NS_ASSERTION(NS_SUCCEEDED(rv), "unable to get ordinal resource"); + if (NS_FAILED(rv)) return rv; + + return NS_OK; +} + + +NS_IMETHODIMP +RDFContainerUtilsImpl::OrdinalResourceToIndex(nsIRDFResource *aOrdinal, int32_t *aIndex) +{ + NS_PRECONDITION(aOrdinal != nullptr, "null ptr"); + if (! aOrdinal) + return NS_ERROR_NULL_POINTER; + + const char *ordinalStr; + if (NS_FAILED(aOrdinal->GetValueConst( &ordinalStr ))) + return NS_ERROR_FAILURE; + + const char* s = ordinalStr; + if (PL_strncmp(s, kRDFNameSpaceURI, sizeof(kRDFNameSpaceURI) - 1) != 0) { + NS_ERROR("not an ordinal"); + return NS_ERROR_UNEXPECTED; + } + + s += sizeof(kRDFNameSpaceURI) - 1; + if (*s != '_') { + NS_ERROR("not an ordinal"); + return NS_ERROR_UNEXPECTED; + } + + int32_t idx = 0; + + ++s; + while (*s) { + if (*s < '0' || *s > '9') { + NS_ERROR("not an ordinal"); + return NS_ERROR_UNEXPECTED; + } + + idx *= 10; + idx += (*s - '0'); + + ++s; + } + + *aIndex = idx; + return NS_OK; +} + +NS_IMETHODIMP +RDFContainerUtilsImpl::IsContainer(nsIRDFDataSource *aDataSource, nsIRDFResource *aResource, bool *_retval) +{ + NS_PRECONDITION(aDataSource != nullptr, "null ptr"); + if (! aDataSource) + return NS_ERROR_NULL_POINTER; + + NS_PRECONDITION(aResource != nullptr, "null ptr"); + if (! aResource) + return NS_ERROR_NULL_POINTER; + + NS_PRECONDITION(_retval != nullptr, "null ptr"); + if (! _retval) + return NS_ERROR_NULL_POINTER; + + if (IsA(aDataSource, aResource, kRDF_Seq) || + IsA(aDataSource, aResource, kRDF_Bag) || + IsA(aDataSource, aResource, kRDF_Alt)) { + *_retval = true; + } + else { + *_retval = false; + } + return NS_OK; +} + + +NS_IMETHODIMP +RDFContainerUtilsImpl::IsEmpty(nsIRDFDataSource* aDataSource, nsIRDFResource* aResource, bool* _retval) +{ + if (! aDataSource) + return NS_ERROR_NULL_POINTER; + + nsresult rv; + + // By default, say that we're an empty container. Even if we're not + // really even a container. + *_retval = true; + + nsCOMPtr nextValNode; + rv = aDataSource->GetTarget(aResource, kRDF_nextVal, true, getter_AddRefs(nextValNode)); + if (NS_FAILED(rv)) return rv; + + if (rv == NS_RDF_NO_VALUE) + return NS_OK; + + nsCOMPtr nextValLiteral; + rv = nextValNode->QueryInterface(NS_GET_IID(nsIRDFLiteral), getter_AddRefs(nextValLiteral)); + if (NS_FAILED(rv)) return rv; + + if (nextValLiteral.get() != kOne) + *_retval = false; + + return NS_OK; +} + + +NS_IMETHODIMP +RDFContainerUtilsImpl::IsBag(nsIRDFDataSource *aDataSource, nsIRDFResource *aResource, bool *_retval) +{ + NS_PRECONDITION(aDataSource != nullptr, "null ptr"); + if (! aDataSource) + return NS_ERROR_NULL_POINTER; + + NS_PRECONDITION(aResource != nullptr, "null ptr"); + if (! aResource) + return NS_ERROR_NULL_POINTER; + + NS_PRECONDITION(_retval != nullptr, "null ptr"); + if (! _retval) + return NS_ERROR_NULL_POINTER; + + *_retval = IsA(aDataSource, aResource, kRDF_Bag); + return NS_OK; +} + + +NS_IMETHODIMP +RDFContainerUtilsImpl::IsSeq(nsIRDFDataSource *aDataSource, nsIRDFResource *aResource, bool *_retval) +{ + NS_PRECONDITION(aDataSource != nullptr, "null ptr"); + if (! aDataSource) + return NS_ERROR_NULL_POINTER; + + NS_PRECONDITION(aResource != nullptr, "null ptr"); + if (! aResource) + return NS_ERROR_NULL_POINTER; + + NS_PRECONDITION(_retval != nullptr, "null ptr"); + if (! _retval) + return NS_ERROR_NULL_POINTER; + + *_retval = IsA(aDataSource, aResource, kRDF_Seq); + return NS_OK; +} + + +NS_IMETHODIMP +RDFContainerUtilsImpl::IsAlt(nsIRDFDataSource *aDataSource, nsIRDFResource *aResource, bool *_retval) +{ + NS_PRECONDITION(aDataSource != nullptr, "null ptr"); + if (! aDataSource) + return NS_ERROR_NULL_POINTER; + + NS_PRECONDITION(aResource != nullptr, "null ptr"); + if (! aResource) + return NS_ERROR_NULL_POINTER; + + NS_PRECONDITION(_retval != nullptr, "null ptr"); + if (! _retval) + return NS_ERROR_NULL_POINTER; + + *_retval = IsA(aDataSource, aResource, kRDF_Alt); + return NS_OK; +} + + +NS_IMETHODIMP +RDFContainerUtilsImpl::MakeBag(nsIRDFDataSource *aDataSource, nsIRDFResource *aResource, nsIRDFContainer **_retval) +{ + return MakeContainer(aDataSource, aResource, kRDF_Bag, _retval); +} + + +NS_IMETHODIMP +RDFContainerUtilsImpl::MakeSeq(nsIRDFDataSource *aDataSource, nsIRDFResource *aResource, nsIRDFContainer **_retval) +{ + return MakeContainer(aDataSource, aResource, kRDF_Seq, _retval); +} + + +NS_IMETHODIMP +RDFContainerUtilsImpl::MakeAlt(nsIRDFDataSource *aDataSource, nsIRDFResource *aResource, nsIRDFContainer **_retval) +{ + return MakeContainer(aDataSource, aResource, kRDF_Alt, _retval); +} + + + +//////////////////////////////////////////////////////////////////////// + + +RDFContainerUtilsImpl::RDFContainerUtilsImpl() +{ + if (gRefCnt++ == 0) { + nsresult rv; + + NS_DEFINE_CID(kRDFServiceCID, NS_RDFSERVICE_CID); + rv = CallGetService(kRDFServiceCID, &gRDFService); + + NS_ASSERTION(NS_SUCCEEDED(rv), "unable to get RDF service"); + if (NS_SUCCEEDED(rv)) { + gRDFService->GetResource(NS_LITERAL_CSTRING(RDF_NAMESPACE_URI "instanceOf"), + &kRDF_instanceOf); + gRDFService->GetResource(NS_LITERAL_CSTRING(RDF_NAMESPACE_URI "nextVal"), + &kRDF_nextVal); + gRDFService->GetResource(NS_LITERAL_CSTRING(RDF_NAMESPACE_URI "Bag"), + &kRDF_Bag); + gRDFService->GetResource(NS_LITERAL_CSTRING(RDF_NAMESPACE_URI "Seq"), + &kRDF_Seq); + gRDFService->GetResource(NS_LITERAL_CSTRING(RDF_NAMESPACE_URI "Alt"), + &kRDF_Alt); + gRDFService->GetLiteral(u"1", &kOne); + } + } +} + + +RDFContainerUtilsImpl::~RDFContainerUtilsImpl() +{ +#ifdef DEBUG_REFS + --gInstanceCount; + fprintf(stdout, "%d - RDF: RDFContainerUtilsImpl\n", gInstanceCount); +#endif + + if (--gRefCnt == 0) { + NS_IF_RELEASE(gRDFService); + NS_IF_RELEASE(kRDF_instanceOf); + NS_IF_RELEASE(kRDF_nextVal); + NS_IF_RELEASE(kRDF_Bag); + NS_IF_RELEASE(kRDF_Seq); + NS_IF_RELEASE(kRDF_Alt); + NS_IF_RELEASE(kOne); + } +} + + + +nsresult +NS_NewRDFContainerUtils(nsIRDFContainerUtils** aResult) +{ + NS_PRECONDITION(aResult != nullptr, "null ptr"); + if (! aResult) + return NS_ERROR_NULL_POINTER; + + RDFContainerUtilsImpl* result = + new RDFContainerUtilsImpl(); + + if (! result) + return NS_ERROR_OUT_OF_MEMORY; + + NS_ADDREF(result); + *aResult = result; + return NS_OK; +} + + +nsresult +RDFContainerUtilsImpl::MakeContainer(nsIRDFDataSource* aDataSource, nsIRDFResource* aResource, nsIRDFResource* aType, nsIRDFContainer** aResult) +{ + NS_PRECONDITION(aDataSource != nullptr, "null ptr"); + if (! aDataSource) return NS_ERROR_NULL_POINTER; + + NS_PRECONDITION(aResource != nullptr, "null ptr"); + if (! aResource) return NS_ERROR_NULL_POINTER; + + NS_PRECONDITION(aType != nullptr, "null ptr"); + if (! aType) return NS_ERROR_NULL_POINTER; + + if (aResult) *aResult = nullptr; + + nsresult rv; + + // Check to see if somebody has already turned it into a container; if so + // don't try to do it again. + bool isContainer; + rv = IsContainer(aDataSource, aResource, &isContainer); + if (NS_FAILED(rv)) return rv; + + if (!isContainer) + { + rv = aDataSource->Assert(aResource, kRDF_instanceOf, aType, true); + if (NS_FAILED(rv)) return rv; + + rv = aDataSource->Assert(aResource, kRDF_nextVal, kOne, true); + if (NS_FAILED(rv)) return rv; + } + + if (aResult) { + rv = NS_NewRDFContainer(aResult); + if (NS_FAILED(rv)) return rv; + + rv = (*aResult)->Init(aDataSource, aResource); + if (NS_FAILED(rv)) return rv; + } + + return NS_OK; +} + +bool +RDFContainerUtilsImpl::IsA(nsIRDFDataSource* aDataSource, nsIRDFResource* aResource, nsIRDFResource* aType) +{ + if (!aDataSource || !aResource || !aType) { + NS_WARNING("Unexpected null argument"); + return false; + } + + nsresult rv; + + bool result; + rv = aDataSource->HasAssertion(aResource, kRDF_instanceOf, aType, true, &result); + if (NS_FAILED(rv)) + return false; + + return result; +} + +NS_IMETHODIMP +RDFContainerUtilsImpl::IndexOf(nsIRDFDataSource* aDataSource, nsIRDFResource* aContainer, nsIRDFNode* aElement, int32_t* aIndex) +{ + if (!aDataSource || !aContainer) + return NS_ERROR_NULL_POINTER; + + // Assume we can't find it. + *aIndex = -1; + + // If the resource is null, bail quietly + if (! aElement) + return NS_OK; + + // We'll assume that fan-out is much higher than fan-in, so grovel + // through the inbound arcs, look for an ordinal resource, and + // decode it. + nsCOMPtr arcsIn; + aDataSource->ArcLabelsIn(aElement, getter_AddRefs(arcsIn)); + if (! arcsIn) + return NS_OK; + + while (1) { + bool hasMoreArcs = false; + arcsIn->HasMoreElements(&hasMoreArcs); + if (! hasMoreArcs) + break; + + nsCOMPtr isupports; + arcsIn->GetNext(getter_AddRefs(isupports)); + if (! isupports) + break; + + nsCOMPtr property = + do_QueryInterface(isupports); + + if (! property) + continue; + + bool isOrdinal; + IsOrdinalProperty(property, &isOrdinal); + if (! isOrdinal) + continue; + + nsCOMPtr sources; + aDataSource->GetSources(property, aElement, true, getter_AddRefs(sources)); + if (! sources) + continue; + + while (1) { + bool hasMoreSources = false; + sources->HasMoreElements(&hasMoreSources); + if (! hasMoreSources) + break; + + nsCOMPtr isupports2; + sources->GetNext(getter_AddRefs(isupports2)); + if (! isupports2) + break; + + nsCOMPtr source = + do_QueryInterface(isupports2); + + if (source == aContainer) + // Found it. + return OrdinalResourceToIndex(property, aIndex); + } + } + + return NS_OK; +} diff --git a/rdf/base/nsRDFContentSink.cpp b/rdf/base/nsRDFContentSink.cpp new file mode 100644 index 000000000..ae05a9381 --- /dev/null +++ b/rdf/base/nsRDFContentSink.cpp @@ -0,0 +1,1476 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + + An implementation for an NGLayout-style content sink that knows how + to build an RDF content model from XML-serialized RDF. + + For more information on the RDF/XML syntax, + see http://www.w3.org/TR/REC-rdf-syntax/ + + This code is based on the final W3C Recommendation, + http://www.w3.org/TR/1999/REC-rdf-syntax-19990222. + + Open Issues ------------------ + + 1) factoring code with nsXMLContentSink - There's some amount of + common code between this and the HTML content sink. This will + increase as we support more and more HTML elements. How can code + from XML/HTML be factored? + + 2) We don't support the `parseType' attribute on the Description + tag; therefore, it is impossible to "inline" raw XML in this + implemenation. + + 3) We don't build the reifications at parse time due to the + footprint overhead it would incur for large RDF documents. (It + may be possible to attach a "reification" wrapper datasource that + would present this information at query-time.) Because of this, + the `bagID' attribute is not processed correctly. + + 4) No attempt is made to `resolve URIs' to a canonical form (the + specification hints that an implementation should do this). This + is omitted for the obvious reason that we can ill afford to + resolve each URI reference. + +*/ + +#include "nsCOMPtr.h" +#include "nsInterfaceHashtable.h" +#include "nsIContentSink.h" +#include "nsIRDFContainer.h" +#include "nsIRDFContainerUtils.h" +#include "nsIRDFContentSink.h" +#include "nsIRDFNode.h" +#include "nsIRDFService.h" +#include "nsIRDFXMLSink.h" +#include "nsIServiceManager.h" +#include "nsIURL.h" +#include "nsIXMLContentSink.h" +#include "nsRDFCID.h" +#include "nsTArray.h" +#include "nsXPIDLString.h" +#include "mozilla/Logging.h" +#include "rdf.h" +#include "rdfutil.h" +#include "nsReadableUtils.h" +#include "nsIExpatSink.h" +#include "nsCRT.h" +#include "nsIAtom.h" +#include "nsStaticAtom.h" +#include "nsIScriptError.h" +#include "nsIDTD.h" + +using namespace mozilla; + +/////////////////////////////////////////////////////////////////////// + +enum RDFContentSinkState { + eRDFContentSinkState_InProlog, + eRDFContentSinkState_InDocumentElement, + eRDFContentSinkState_InDescriptionElement, + eRDFContentSinkState_InContainerElement, + eRDFContentSinkState_InPropertyElement, + eRDFContentSinkState_InMemberElement, + eRDFContentSinkState_InEpilog +}; + +enum RDFContentSinkParseMode { + eRDFContentSinkParseMode_Resource, + eRDFContentSinkParseMode_Literal, + eRDFContentSinkParseMode_Int, + eRDFContentSinkParseMode_Date +}; + +typedef +NS_STDCALL_FUNCPROTO(nsresult, + nsContainerTestFn, + nsIRDFContainerUtils, IsAlt, + (nsIRDFDataSource*, nsIRDFResource*, bool*)); + +typedef +NS_STDCALL_FUNCPROTO(nsresult, + nsMakeContainerFn, + nsIRDFContainerUtils, MakeAlt, + (nsIRDFDataSource*, nsIRDFResource*, nsIRDFContainer**)); + +class RDFContentSinkImpl : public nsIRDFContentSink, + public nsIExpatSink +{ +public: + RDFContentSinkImpl(); + + // nsISupports + NS_DECL_ISUPPORTS + NS_DECL_NSIEXPATSINK + + // nsIContentSink + NS_IMETHOD WillParse(void) override; + NS_IMETHOD WillBuildModel(nsDTDMode aDTDMode) override; + NS_IMETHOD DidBuildModel(bool aTerminated) override; + NS_IMETHOD WillInterrupt(void) override; + NS_IMETHOD WillResume(void) override; + NS_IMETHOD SetParser(nsParserBase* aParser) override; + virtual void FlushPendingNotifications(mozFlushType aType) override { } + NS_IMETHOD SetDocumentCharset(nsACString& aCharset) override { return NS_OK; } + virtual nsISupports *GetTarget() override { return nullptr; } + + // nsIRDFContentSink + NS_IMETHOD Init(nsIURI* aURL) override; + NS_IMETHOD SetDataSource(nsIRDFDataSource* aDataSource) override; + NS_IMETHOD GetDataSource(nsIRDFDataSource*& aDataSource) override; + + // pseudo constants + static int32_t gRefCnt; + static nsIRDFService* gRDFService; + static nsIRDFContainerUtils* gRDFContainerUtils; + static nsIRDFResource* kRDF_type; + static nsIRDFResource* kRDF_instanceOf; // XXX should be RDF:type + static nsIRDFResource* kRDF_Alt; + static nsIRDFResource* kRDF_Bag; + static nsIRDFResource* kRDF_Seq; + static nsIRDFResource* kRDF_nextVal; + +#define RDF_ATOM(name_, value_) static nsIAtom* name_; +#include "nsRDFContentSinkAtomList.h" +#undef RDF_ATOM + + typedef struct ContainerInfo { + nsIRDFResource** mType; + nsContainerTestFn mTestFn; + nsMakeContainerFn mMakeFn; + } ContainerInfo; + +protected: + virtual ~RDFContentSinkImpl(); + + // Text management + void ParseText(nsIRDFNode **aResult); + + nsresult FlushText(); + nsresult AddText(const char16_t* aText, int32_t aLength); + + // RDF-specific parsing + nsresult OpenRDF(const char16_t* aName); + nsresult OpenObject(const char16_t* aName ,const char16_t** aAttributes); + nsresult OpenProperty(const char16_t* aName, const char16_t** aAttributes); + nsresult OpenMember(const char16_t* aName, const char16_t** aAttributes); + nsresult OpenValue(const char16_t* aName, const char16_t** aAttributes); + + nsresult GetIdAboutAttribute(const char16_t** aAttributes, nsIRDFResource** aResource, bool* aIsAnonymous = nullptr); + nsresult GetResourceAttribute(const char16_t** aAttributes, nsIRDFResource** aResource); + nsresult AddProperties(const char16_t** aAttributes, nsIRDFResource* aSubject, int32_t* aCount = nullptr); + void SetParseMode(const char16_t **aAttributes); + + char16_t* mText; + int32_t mTextLength; + int32_t mTextSize; + + /** + * From the set of given attributes, this method extracts the + * namespace definitions and feeds them to the datasource. + * These can then be suggested to the serializer to be used again. + * Hopefully, this will keep namespace definitions intact in a + * parse - serialize cycle. + */ + void RegisterNamespaces(const char16_t **aAttributes); + + /** + * Extracts the localname from aExpatName, the name that the Expat parser + * passes us. + * aLocalName will contain the localname in aExpatName. + * The return value is a dependent string containing just the namespace. + */ + const nsDependentSubstring SplitExpatName(const char16_t *aExpatName, + nsIAtom **aLocalName); + + enum eContainerType { eBag, eSeq, eAlt }; + nsresult InitContainer(nsIRDFResource* aContainerType, nsIRDFResource* aContainer); + nsresult ReinitContainer(nsIRDFResource* aContainerType, nsIRDFResource* aContainer); + + // The datasource in which we're assigning assertions + nsCOMPtr mDataSource; + + // A hash of all the node IDs referred to + nsInterfaceHashtable mNodeIDMap; + + // The current state of the content sink + RDFContentSinkState mState; + RDFContentSinkParseMode mParseMode; + + // content stack management + int32_t + PushContext(nsIRDFResource *aContext, + RDFContentSinkState aState, + RDFContentSinkParseMode aParseMode); + + nsresult + PopContext(nsIRDFResource *&aContext, + RDFContentSinkState &aState, + RDFContentSinkParseMode &aParseMode); + + nsIRDFResource* GetContextElement(int32_t ancestor = 0); + + + struct RDFContextStackElement { + nsCOMPtr mResource; + RDFContentSinkState mState; + RDFContentSinkParseMode mParseMode; + }; + + AutoTArray* mContextStack; + + nsCOMPtr mDocumentURL; + +private: + static mozilla::LazyLogModule gLog; +}; + +int32_t RDFContentSinkImpl::gRefCnt = 0; +nsIRDFService* RDFContentSinkImpl::gRDFService; +nsIRDFContainerUtils* RDFContentSinkImpl::gRDFContainerUtils; +nsIRDFResource* RDFContentSinkImpl::kRDF_type; +nsIRDFResource* RDFContentSinkImpl::kRDF_instanceOf; +nsIRDFResource* RDFContentSinkImpl::kRDF_Alt; +nsIRDFResource* RDFContentSinkImpl::kRDF_Bag; +nsIRDFResource* RDFContentSinkImpl::kRDF_Seq; +nsIRDFResource* RDFContentSinkImpl::kRDF_nextVal; + +mozilla::LazyLogModule RDFContentSinkImpl::gLog("nsRDFContentSink"); + +//////////////////////////////////////////////////////////////////////// + +#define RDF_ATOM(name_, value_) nsIAtom* RDFContentSinkImpl::name_; +#include "nsRDFContentSinkAtomList.h" +#undef RDF_ATOM + +#define RDF_ATOM(name_, value_) NS_STATIC_ATOM_BUFFER(name_##_buffer, value_) +#include "nsRDFContentSinkAtomList.h" +#undef RDF_ATOM + +static const nsStaticAtom rdf_atoms[] = { +#define RDF_ATOM(name_, value_) NS_STATIC_ATOM(name_##_buffer, &RDFContentSinkImpl::name_), +#include "nsRDFContentSinkAtomList.h" +#undef RDF_ATOM +}; + +// static +void +nsRDFAtoms::RegisterAtoms() +{ + NS_RegisterStaticAtoms(rdf_atoms); +} + +RDFContentSinkImpl::RDFContentSinkImpl() + : mText(nullptr), + mTextLength(0), + mTextSize(0), + mState(eRDFContentSinkState_InProlog), + mParseMode(eRDFContentSinkParseMode_Literal), + mContextStack(nullptr) +{ + if (gRefCnt++ == 0) { + NS_DEFINE_CID(kRDFServiceCID, NS_RDFSERVICE_CID); + nsresult rv = CallGetService(kRDFServiceCID, &gRDFService); + + NS_ASSERTION(NS_SUCCEEDED(rv), "unable to get RDF service"); + if (NS_SUCCEEDED(rv)) { + rv = gRDFService->GetResource(NS_LITERAL_CSTRING(RDF_NAMESPACE_URI "type"), + &kRDF_type); + rv = gRDFService->GetResource(NS_LITERAL_CSTRING(RDF_NAMESPACE_URI "instanceOf"), + &kRDF_instanceOf); + rv = gRDFService->GetResource(NS_LITERAL_CSTRING(RDF_NAMESPACE_URI "Alt"), + &kRDF_Alt); + rv = gRDFService->GetResource(NS_LITERAL_CSTRING(RDF_NAMESPACE_URI "Bag"), + &kRDF_Bag); + rv = gRDFService->GetResource(NS_LITERAL_CSTRING(RDF_NAMESPACE_URI "Seq"), + &kRDF_Seq); + rv = gRDFService->GetResource(NS_LITERAL_CSTRING(RDF_NAMESPACE_URI "nextVal"), + &kRDF_nextVal); + } + + NS_DEFINE_CID(kRDFContainerUtilsCID, NS_RDFCONTAINERUTILS_CID); + rv = CallGetService(kRDFContainerUtilsCID, &gRDFContainerUtils); + } +} + + +RDFContentSinkImpl::~RDFContentSinkImpl() +{ +#ifdef DEBUG_REFS + --gInstanceCount; + fprintf(stdout, "%d - RDF: RDFContentSinkImpl\n", gInstanceCount); +#endif + + if (mContextStack) { + MOZ_LOG(gLog, LogLevel::Warning, + ("rdfxml: warning! unclosed tag")); + + // XXX we should never need to do this, but, we'll write the + // code all the same. If someone left the content stack dirty, + // pop all the elements off the stack and release them. + int32_t i = mContextStack->Length(); + while (0 < i--) { + nsIRDFResource* resource = nullptr; + RDFContentSinkState state; + RDFContentSinkParseMode parseMode; + PopContext(resource, state, parseMode); + + // print some fairly useless debugging info + // XXX we should save line numbers on the context stack: this'd + // be about 1000x more helpful. + if (resource && MOZ_LOG_TEST(gLog, LogLevel::Debug)) { + nsXPIDLCString uri; + resource->GetValue(getter_Copies(uri)); + MOZ_LOG(gLog, LogLevel::Debug, + ("rdfxml: uri=%s", (const char*) uri)); + } + + NS_IF_RELEASE(resource); + } + + delete mContextStack; + } + free(mText); + + + if (--gRefCnt == 0) { + NS_IF_RELEASE(gRDFService); + NS_IF_RELEASE(gRDFContainerUtils); + NS_IF_RELEASE(kRDF_type); + NS_IF_RELEASE(kRDF_instanceOf); + NS_IF_RELEASE(kRDF_Alt); + NS_IF_RELEASE(kRDF_Bag); + NS_IF_RELEASE(kRDF_Seq); + NS_IF_RELEASE(kRDF_nextVal); + } +} + +//////////////////////////////////////////////////////////////////////// +// nsISupports interface + +NS_IMPL_ADDREF(RDFContentSinkImpl) +NS_IMPL_RELEASE(RDFContentSinkImpl) + +NS_IMETHODIMP +RDFContentSinkImpl::QueryInterface(REFNSIID iid, void** result) +{ + NS_PRECONDITION(result, "null ptr"); + if (! result) + return NS_ERROR_NULL_POINTER; + + NS_DEFINE_IID(kIContentSinkIID, NS_ICONTENT_SINK_IID); + NS_DEFINE_IID(kIExpatSinkIID, NS_IEXPATSINK_IID); + NS_DEFINE_IID(kISupportsIID, NS_ISUPPORTS_IID); + NS_DEFINE_IID(kIXMLContentSinkIID, NS_IXMLCONTENT_SINK_IID); + NS_DEFINE_IID(kIRDFContentSinkIID, NS_IRDFCONTENTSINK_IID); + + *result = nullptr; + if (iid.Equals(kIRDFContentSinkIID) || + iid.Equals(kIXMLContentSinkIID) || + iid.Equals(kIContentSinkIID) || + iid.Equals(kISupportsIID)) { + *result = static_cast(this); + AddRef(); + return NS_OK; + } + else if (iid.Equals(kIExpatSinkIID)) { + *result = static_cast(this); + AddRef(); + return NS_OK; + } + return NS_NOINTERFACE; +} + +NS_IMETHODIMP +RDFContentSinkImpl::HandleStartElement(const char16_t *aName, + const char16_t **aAtts, + uint32_t aAttsCount, + uint32_t aLineNumber) +{ + FlushText(); + + nsresult rv = NS_ERROR_UNEXPECTED; // XXX + + RegisterNamespaces(aAtts); + + switch (mState) { + case eRDFContentSinkState_InProlog: + rv = OpenRDF(aName); + break; + + case eRDFContentSinkState_InDocumentElement: + rv = OpenObject(aName,aAtts); + break; + + case eRDFContentSinkState_InDescriptionElement: + rv = OpenProperty(aName,aAtts); + break; + + case eRDFContentSinkState_InContainerElement: + rv = OpenMember(aName,aAtts); + break; + + case eRDFContentSinkState_InPropertyElement: + case eRDFContentSinkState_InMemberElement: + rv = OpenValue(aName,aAtts); + break; + + case eRDFContentSinkState_InEpilog: + MOZ_LOG(gLog, LogLevel::Warning, + ("rdfxml: unexpected content in epilog at line %d", + aLineNumber)); + break; + } + + return rv; +} + +NS_IMETHODIMP +RDFContentSinkImpl::HandleEndElement(const char16_t *aName) +{ + FlushText(); + + nsIRDFResource* resource; + if (NS_FAILED(PopContext(resource, mState, mParseMode))) { + // XXX parser didn't catch unmatched tags? + if (MOZ_LOG_TEST(gLog, LogLevel::Warning)) { + nsAutoString tagStr(aName); + char* tagCStr = ToNewCString(tagStr); + + PR_LogPrint + ("rdfxml: extra close tag '%s' at line %d", + tagCStr, 0/*XXX fix me */); + + free(tagCStr); + } + + return NS_ERROR_UNEXPECTED; // XXX + } + + // If we've just popped a member or property element, _now_ is the + // time to add that element to the graph. + switch (mState) { + case eRDFContentSinkState_InMemberElement: + { + nsCOMPtr container; + NS_NewRDFContainer(getter_AddRefs(container)); + container->Init(mDataSource, GetContextElement(1)); + container->AppendElement(resource); + } + break; + + case eRDFContentSinkState_InPropertyElement: + { + mDataSource->Assert(GetContextElement(1), GetContextElement(0), resource, true); + } break; + default: + break; + } + + if (mContextStack->IsEmpty()) + mState = eRDFContentSinkState_InEpilog; + + NS_IF_RELEASE(resource); + return NS_OK; +} + +NS_IMETHODIMP +RDFContentSinkImpl::HandleComment(const char16_t *aName) +{ + return NS_OK; +} + +NS_IMETHODIMP +RDFContentSinkImpl::HandleCDataSection(const char16_t *aData, + uint32_t aLength) +{ + return aData ? AddText(aData, aLength) : NS_OK; +} + +NS_IMETHODIMP +RDFContentSinkImpl::HandleDoctypeDecl(const nsAString & aSubset, + const nsAString & aName, + const nsAString & aSystemId, + const nsAString & aPublicId, + nsISupports* aCatalogData) +{ + return NS_OK; +} + +NS_IMETHODIMP +RDFContentSinkImpl::HandleCharacterData(const char16_t *aData, + uint32_t aLength) +{ + return aData ? AddText(aData, aLength) : NS_OK; +} + +NS_IMETHODIMP +RDFContentSinkImpl::HandleProcessingInstruction(const char16_t *aTarget, + const char16_t *aData) +{ + return NS_OK; +} + +NS_IMETHODIMP +RDFContentSinkImpl::HandleXMLDeclaration(const char16_t *aVersion, + const char16_t *aEncoding, + int32_t aStandalone) +{ + return NS_OK; +} + +NS_IMETHODIMP +RDFContentSinkImpl::ReportError(const char16_t* aErrorText, + const char16_t* aSourceText, + nsIScriptError *aError, + bool *_retval) +{ + NS_PRECONDITION(aError && aSourceText && aErrorText, "Check arguments!!!"); + + // The expat driver should report the error. + *_retval = true; + return NS_OK; +} + +//////////////////////////////////////////////////////////////////////// +// nsIContentSink interface + +NS_IMETHODIMP +RDFContentSinkImpl::WillParse(void) +{ + return NS_OK; +} + + +NS_IMETHODIMP +RDFContentSinkImpl::WillBuildModel(nsDTDMode) +{ + if (mDataSource) { + nsCOMPtr sink = do_QueryInterface(mDataSource); + if (sink) + return sink->BeginLoad(); + } + return NS_OK; +} + +NS_IMETHODIMP +RDFContentSinkImpl::DidBuildModel(bool aTerminated) +{ + if (mDataSource) { + nsCOMPtr sink = do_QueryInterface(mDataSource); + if (sink) + return sink->EndLoad(); + } + return NS_OK; +} + +NS_IMETHODIMP +RDFContentSinkImpl::WillInterrupt(void) +{ + if (mDataSource) { + nsCOMPtr sink = do_QueryInterface(mDataSource); + if (sink) + return sink->Interrupt(); + } + return NS_OK; +} + +NS_IMETHODIMP +RDFContentSinkImpl::WillResume(void) +{ + if (mDataSource) { + nsCOMPtr sink = do_QueryInterface(mDataSource); + if (sink) + return sink->Resume(); + } + return NS_OK; +} + +NS_IMETHODIMP +RDFContentSinkImpl::SetParser(nsParserBase* aParser) +{ + return NS_OK; +} + +//////////////////////////////////////////////////////////////////////// +// nsIRDFContentSink interface + +NS_IMETHODIMP +RDFContentSinkImpl::Init(nsIURI* aURL) +{ + NS_PRECONDITION(aURL != nullptr, "null ptr"); + if (! aURL) + return NS_ERROR_NULL_POINTER; + + mDocumentURL = aURL; + mState = eRDFContentSinkState_InProlog; + return NS_OK; +} + +NS_IMETHODIMP +RDFContentSinkImpl::SetDataSource(nsIRDFDataSource* aDataSource) +{ + NS_PRECONDITION(aDataSource != nullptr, "SetDataSource null ptr"); + mDataSource = aDataSource; + NS_ASSERTION(mDataSource != nullptr,"Couldn't QI RDF DataSource"); + return NS_OK; +} + + +NS_IMETHODIMP +RDFContentSinkImpl::GetDataSource(nsIRDFDataSource*& aDataSource) +{ + aDataSource = mDataSource; + NS_IF_ADDREF(aDataSource); + return NS_OK; +} + +//////////////////////////////////////////////////////////////////////// +// Text buffering + +static bool +rdf_IsDataInBuffer(char16_t* buffer, int32_t length) +{ + for (int32_t i = 0; i < length; ++i) { + if (buffer[i] == ' ' || + buffer[i] == '\t' || + buffer[i] == '\n' || + buffer[i] == '\r') + continue; + + return true; + } + return false; +} + +void +RDFContentSinkImpl::ParseText(nsIRDFNode **aResult) +{ + // XXXwaterson wasteful, but we'd need to make a copy anyway to be + // able to call nsIRDFService::Get[Resource|Literal|...](). + nsAutoString value; + value.Append(mText, mTextLength); + value.Trim(" \t\n\r"); + + switch (mParseMode) { + case eRDFContentSinkParseMode_Literal: + { + nsIRDFLiteral *result; + gRDFService->GetLiteral(value.get(), &result); + *aResult = result; + } + break; + + case eRDFContentSinkParseMode_Resource: + { + nsIRDFResource *result; + gRDFService->GetUnicodeResource(value, &result); + *aResult = result; + } + break; + + case eRDFContentSinkParseMode_Int: + { + nsresult err; + int32_t i = value.ToInteger(&err); + nsIRDFInt *result; + gRDFService->GetIntLiteral(i, &result); + *aResult = result; + } + break; + + case eRDFContentSinkParseMode_Date: + { + PRTime t = rdf_ParseDate(nsDependentCString(NS_LossyConvertUTF16toASCII(value).get(), value.Length())); + nsIRDFDate *result; + gRDFService->GetDateLiteral(t, &result); + *aResult = result; + } + break; + + default: + NS_NOTREACHED("unknown parse type"); + break; + } +} + +nsresult +RDFContentSinkImpl::FlushText() +{ + nsresult rv = NS_OK; + if (0 != mTextLength) { + if (rdf_IsDataInBuffer(mText, mTextLength)) { + // XXX if there's anything but whitespace, then we'll + // create a text node. + + switch (mState) { + case eRDFContentSinkState_InMemberElement: { + nsCOMPtr node; + ParseText(getter_AddRefs(node)); + + nsCOMPtr container; + NS_NewRDFContainer(getter_AddRefs(container)); + container->Init(mDataSource, GetContextElement(1)); + + container->AppendElement(node); + } break; + + case eRDFContentSinkState_InPropertyElement: { + nsCOMPtr node; + ParseText(getter_AddRefs(node)); + + mDataSource->Assert(GetContextElement(1), GetContextElement(0), node, true); + } break; + + default: + // just ignore it + break; + } + } + mTextLength = 0; + } + return rv; +} + + +nsresult +RDFContentSinkImpl::AddText(const char16_t* aText, int32_t aLength) +{ + // Create buffer when we first need it + if (0 == mTextSize) { + mText = (char16_t *) malloc(sizeof(char16_t) * 4096); + if (!mText) { + return NS_ERROR_OUT_OF_MEMORY; + } + mTextSize = 4096; + } + + // Copy data from string into our buffer; grow the buffer as needed. + // It never shrinks, but since the content sink doesn't stick around, + // this shouldn't be a bloat issue. + int32_t amount = mTextSize - mTextLength; + if (amount < aLength) { + // Grow the buffer by at least a factor of two to prevent thrashing. + // Since PR_REALLOC will leave mText intact if the call fails, + // don't clobber mText or mTextSize until the new mem is allocated. + int32_t newSize = (2 * mTextSize > (mTextSize + aLength)) ? + (2 * mTextSize) : (mTextSize + aLength); + char16_t* newText = + (char16_t *) realloc(mText, sizeof(char16_t) * newSize); + if (!newText) + return NS_ERROR_OUT_OF_MEMORY; + mTextSize = newSize; + mText = newText; + } + memcpy(&mText[mTextLength], aText, sizeof(char16_t) * aLength); + mTextLength += aLength; + + return NS_OK; +} + +bool +rdf_RequiresAbsoluteURI(const nsString& uri) +{ + // cheap shot at figuring out if this requires an absolute url translation + return !(StringBeginsWith(uri, NS_LITERAL_STRING("urn:")) || + StringBeginsWith(uri, NS_LITERAL_STRING("chrome:"))); +} + +nsresult +RDFContentSinkImpl::GetIdAboutAttribute(const char16_t** aAttributes, + nsIRDFResource** aResource, + bool* aIsAnonymous) +{ + // This corresponds to the dirty work of production [6.5] + nsresult rv = NS_OK; + + nsAutoString nodeID; + + nsCOMPtr localName; + for (; *aAttributes; aAttributes += 2) { + const nsDependentSubstring& nameSpaceURI = + SplitExpatName(aAttributes[0], getter_AddRefs(localName)); + + // We'll accept either `ID' or `rdf:ID' (ibid with `about' or + // `rdf:about') in the spirit of being liberal towards the + // input that we receive. + if (!nameSpaceURI.IsEmpty() && + !nameSpaceURI.EqualsLiteral(RDF_NAMESPACE_URI)) { + continue; + } + + // XXX you can't specify both, but we'll just pick up the + // first thing that was specified and ignore the other. + + if (localName == kAboutAtom) { + if (aIsAnonymous) + *aIsAnonymous = false; + + nsAutoString relURI(aAttributes[1]); + if (rdf_RequiresAbsoluteURI(relURI)) { + nsAutoCString uri; + rv = mDocumentURL->Resolve(NS_ConvertUTF16toUTF8(aAttributes[1]), uri); + if (NS_FAILED(rv)) return rv; + + return gRDFService->GetResource(uri, + aResource); + } + return gRDFService->GetResource(NS_ConvertUTF16toUTF8(aAttributes[1]), + aResource); + } + else if (localName == kIdAtom) { + if (aIsAnonymous) + *aIsAnonymous = false; + // In the spirit of leniency, we do not bother trying to + // enforce that this be a valid "XML Name" (see + // http://www.w3.org/TR/REC-xml#NT-Nmtoken), as per + // 6.21. If we wanted to, this would be where to do it. + + // Construct an in-line resource whose URI is the + // document's URI plus the XML name specified in the ID + // attribute. + nsAutoCString name; + nsAutoCString ref('#'); + AppendUTF16toUTF8(aAttributes[1], ref); + + rv = mDocumentURL->Resolve(ref, name); + if (NS_FAILED(rv)) return rv; + + return gRDFService->GetResource(name, aResource); + } + else if (localName == kNodeIdAtom) { + nodeID.Assign(aAttributes[1]); + } + else if (localName == kAboutEachAtom) { + // XXX we don't deal with aboutEach... + //MOZ_LOG(gLog, LogLevel::Warning, + // ("rdfxml: ignoring aboutEach at line %d", + // aNode.GetSourceLineNumber())); + } + } + + // Otherwise, we couldn't find anything, so just gensym one... + if (aIsAnonymous) + *aIsAnonymous = true; + + // If nodeID is present, check if we already know about it. If we've seen + // the nodeID before, use the same resource, otherwise generate a new one. + if (!nodeID.IsEmpty()) { + mNodeIDMap.Get(nodeID,aResource); + + if (!*aResource) { + rv = gRDFService->GetAnonymousResource(aResource); + mNodeIDMap.Put(nodeID,*aResource); + } + } + else { + rv = gRDFService->GetAnonymousResource(aResource); + } + + return rv; +} + +nsresult +RDFContentSinkImpl::GetResourceAttribute(const char16_t** aAttributes, + nsIRDFResource** aResource) +{ + nsCOMPtr localName; + + nsAutoString nodeID; + + for (; *aAttributes; aAttributes += 2) { + const nsDependentSubstring& nameSpaceURI = + SplitExpatName(aAttributes[0], getter_AddRefs(localName)); + + // We'll accept `resource' or `rdf:resource', under the spirit + // that we should be liberal towards the input that we + // receive. + if (!nameSpaceURI.IsEmpty() && + !nameSpaceURI.EqualsLiteral(RDF_NAMESPACE_URI)) { + continue; + } + + // XXX you can't specify both, but we'll just pick up the + // first thing that was specified and ignore the other. + + if (localName == kResourceAtom) { + // XXX Take the URI and make it fully qualified by + // sticking it into the document's URL. This may not be + // appropriate... + nsAutoString relURI(aAttributes[1]); + if (rdf_RequiresAbsoluteURI(relURI)) { + nsresult rv; + nsAutoCString uri; + + rv = mDocumentURL->Resolve(NS_ConvertUTF16toUTF8(aAttributes[1]), uri); + if (NS_FAILED(rv)) return rv; + + return gRDFService->GetResource(uri, aResource); + } + return gRDFService->GetResource(NS_ConvertUTF16toUTF8(aAttributes[1]), + aResource); + } + else if (localName == kNodeIdAtom) { + nodeID.Assign(aAttributes[1]); + } + } + + // If nodeID is present, check if we already know about it. If we've seen + // the nodeID before, use the same resource, otherwise generate a new one. + if (!nodeID.IsEmpty()) { + mNodeIDMap.Get(nodeID,aResource); + + if (!*aResource) { + nsresult rv; + rv = gRDFService->GetAnonymousResource(aResource); + if (NS_FAILED(rv)) { + return rv; + } + mNodeIDMap.Put(nodeID,*aResource); + } + return NS_OK; + } + + return NS_ERROR_FAILURE; +} + +nsresult +RDFContentSinkImpl::AddProperties(const char16_t** aAttributes, + nsIRDFResource* aSubject, + int32_t* aCount) +{ + if (aCount) + *aCount = 0; + + nsCOMPtr localName; + for (; *aAttributes; aAttributes += 2) { + const nsDependentSubstring& nameSpaceURI = + SplitExpatName(aAttributes[0], getter_AddRefs(localName)); + + // skip 'xmlns' directives, these are "meta" information + if (nameSpaceURI.EqualsLiteral("http://www.w3.org/2000/xmlns/")) { + continue; + } + + // skip `about', `ID', `resource', and 'nodeID' attributes (either with or + // without the `rdf:' prefix); these are all "special" and + // should've been dealt with by the caller. + if (localName == kAboutAtom || localName == kIdAtom || + localName == kResourceAtom || localName == kNodeIdAtom) { + if (nameSpaceURI.IsEmpty() || + nameSpaceURI.EqualsLiteral(RDF_NAMESPACE_URI)) + continue; + } + + // Skip `parseType', `RDF:parseType', and `NC:parseType'. This + // is meta-information that will be handled in SetParseMode. + if (localName == kParseTypeAtom) { + if (nameSpaceURI.IsEmpty() || + nameSpaceURI.EqualsLiteral(RDF_NAMESPACE_URI) || + nameSpaceURI.EqualsLiteral(NC_NAMESPACE_URI)) { + continue; + } + } + + NS_ConvertUTF16toUTF8 propertyStr(nameSpaceURI); + propertyStr.Append(nsAtomCString(localName)); + + // Add the assertion to RDF + nsCOMPtr property; + gRDFService->GetResource(propertyStr, getter_AddRefs(property)); + + nsCOMPtr target; + gRDFService->GetLiteral(aAttributes[1], + getter_AddRefs(target)); + + mDataSource->Assert(aSubject, property, target, true); + } + return NS_OK; +} + +void +RDFContentSinkImpl::SetParseMode(const char16_t **aAttributes) +{ + nsCOMPtr localName; + for (; *aAttributes; aAttributes += 2) { + const nsDependentSubstring& nameSpaceURI = + SplitExpatName(aAttributes[0], getter_AddRefs(localName)); + + if (localName == kParseTypeAtom) { + nsDependentString v(aAttributes[1]); + + if (nameSpaceURI.IsEmpty() || + nameSpaceURI.EqualsLiteral(RDF_NAMESPACE_URI)) { + if (v.EqualsLiteral("Resource")) + mParseMode = eRDFContentSinkParseMode_Resource; + + break; + } + else if (nameSpaceURI.EqualsLiteral(NC_NAMESPACE_URI)) { + if (v.EqualsLiteral("Date")) + mParseMode = eRDFContentSinkParseMode_Date; + else if (v.EqualsLiteral("Integer")) + mParseMode = eRDFContentSinkParseMode_Int; + + break; + } + } + } +} + +//////////////////////////////////////////////////////////////////////// +// RDF-specific routines used to build the model + +nsresult +RDFContentSinkImpl::OpenRDF(const char16_t* aName) +{ + // ensure that we're actually reading RDF by making sure that the + // opening tag is , where "rdf:" corresponds to whatever + // they've declared the standard RDF namespace to be. + nsCOMPtr localName; + const nsDependentSubstring& nameSpaceURI = + SplitExpatName(aName, getter_AddRefs(localName)); + + if (!nameSpaceURI.EqualsLiteral(RDF_NAMESPACE_URI) || localName != kRDFAtom) { + // MOZ_LOG(gLog, LogLevel::Info, + // ("rdfxml: expected RDF:RDF at line %d", + // aNode.GetSourceLineNumber())); + + return NS_ERROR_UNEXPECTED; + } + + PushContext(nullptr, mState, mParseMode); + mState = eRDFContentSinkState_InDocumentElement; + return NS_OK; +} + +nsresult +RDFContentSinkImpl::OpenObject(const char16_t* aName, + const char16_t** aAttributes) +{ + // an "object" non-terminal is either a "description", a "typed + // node", or a "container", so this change the content sink's + // state appropriately. + nsCOMPtr localName; + const nsDependentSubstring& nameSpaceURI = + SplitExpatName(aName, getter_AddRefs(localName)); + + // Figure out the URI of this object, and create an RDF node for it. + nsCOMPtr source; + GetIdAboutAttribute(aAttributes, getter_AddRefs(source)); + + // If there is no `ID' or `about', then there's not much we can do. + if (! source) + return NS_ERROR_FAILURE; + + // Push the element onto the context stack + PushContext(source, mState, mParseMode); + + // Now figure out what kind of state transition we need to + // make. We'll either be going into a mode where we parse a + // description or a container. + bool isaTypedNode = true; + + if (nameSpaceURI.EqualsLiteral(RDF_NAMESPACE_URI)) { + isaTypedNode = false; + + if (localName == kDescriptionAtom) { + // it's a description + mState = eRDFContentSinkState_InDescriptionElement; + } + else if (localName == kBagAtom) { + // it's a bag container + InitContainer(kRDF_Bag, source); + mState = eRDFContentSinkState_InContainerElement; + } + else if (localName == kSeqAtom) { + // it's a seq container + InitContainer(kRDF_Seq, source); + mState = eRDFContentSinkState_InContainerElement; + } + else if (localName == kAltAtom) { + // it's an alt container + InitContainer(kRDF_Alt, source); + mState = eRDFContentSinkState_InContainerElement; + } + else { + // heh, that's not *in* the RDF namespace: just treat it + // like a typed node + isaTypedNode = true; + } + } + + if (isaTypedNode) { + NS_ConvertUTF16toUTF8 typeStr(nameSpaceURI); + typeStr.Append(nsAtomCString(localName)); + + nsCOMPtr type; + nsresult rv = gRDFService->GetResource(typeStr, getter_AddRefs(type)); + if (NS_FAILED(rv)) return rv; + + rv = mDataSource->Assert(source, kRDF_type, type, true); + if (NS_FAILED(rv)) return rv; + + mState = eRDFContentSinkState_InDescriptionElement; + } + + AddProperties(aAttributes, source); + return NS_OK; +} + +nsresult +RDFContentSinkImpl::OpenProperty(const char16_t* aName, const char16_t** aAttributes) +{ + nsresult rv; + + // an "object" non-terminal is either a "description", a "typed + // node", or a "container", so this change the content sink's + // state appropriately. + nsCOMPtr localName; + const nsDependentSubstring& nameSpaceURI = + SplitExpatName(aName, getter_AddRefs(localName)); + + NS_ConvertUTF16toUTF8 propertyStr(nameSpaceURI); + propertyStr.Append(nsAtomCString(localName)); + + nsCOMPtr property; + rv = gRDFService->GetResource(propertyStr, getter_AddRefs(property)); + if (NS_FAILED(rv)) return rv; + + // See if they've specified a 'resource' attribute, in which case + // they mean *that* to be the object of this property. + nsCOMPtr target; + GetResourceAttribute(aAttributes, getter_AddRefs(target)); + + bool isAnonymous = false; + + if (! target) { + // See if an 'ID' attribute has been specified, in which case + // this corresponds to the fourth form of [6.12]. + + // XXX strictly speaking, we should reject the RDF/XML as + // invalid if they've specified both an 'ID' and a 'resource' + // attribute. Bah. + + // XXX strictly speaking, 'about=' isn't allowed here, but + // what the hell. + GetIdAboutAttribute(aAttributes, getter_AddRefs(target), &isAnonymous); + } + + if (target) { + // They specified an inline resource for the value of this + // property. Create an RDF resource for the inline resource + // URI, add the properties to it, and attach the inline + // resource to its parent. + int32_t count; + rv = AddProperties(aAttributes, target, &count); + NS_ASSERTION(NS_SUCCEEDED(rv), "problem adding properties"); + if (NS_FAILED(rv)) return rv; + + if (count || !isAnonymous) { + // If the resource was "anonymous" (i.e., they hadn't + // explicitly set an ID or resource attribute), then we'll + // only assert this property from the context element *if* + // there were properties specified on the anonymous + // resource. + rv = mDataSource->Assert(GetContextElement(0), property, target, true); + if (NS_FAILED(rv)) return rv; + } + + // XXX Technically, we should _not_ fall through here and push + // the element onto the stack: this is supposed to be a closed + // node. But right now I'm lazy and the code will just Do The + // Right Thing so long as the RDF is well-formed. + } + + // Push the element onto the context stack and change state. + PushContext(property, mState, mParseMode); + mState = eRDFContentSinkState_InPropertyElement; + SetParseMode(aAttributes); + + return NS_OK; +} + +nsresult +RDFContentSinkImpl::OpenMember(const char16_t* aName, + const char16_t** aAttributes) +{ + // ensure that we're actually reading a member element by making + // sure that the opening tag is , where "rdf:" corresponds + // to whatever they've declared the standard RDF namespace to be. + nsresult rv; + + nsCOMPtr localName; + const nsDependentSubstring& nameSpaceURI = + SplitExpatName(aName, getter_AddRefs(localName)); + + if (!nameSpaceURI.EqualsLiteral(RDF_NAMESPACE_URI) || + localName != kLiAtom) { + MOZ_LOG(gLog, LogLevel::Error, + ("rdfxml: expected RDF:li at line %d", + -1)); // XXX pass in line number + + return NS_ERROR_UNEXPECTED; + } + + // The parent element is the container. + nsIRDFResource* container = GetContextElement(0); + if (! container) + return NS_ERROR_NULL_POINTER; + + nsIRDFResource* resource; + if (NS_SUCCEEDED(rv = GetResourceAttribute(aAttributes, &resource))) { + // Okay, this node has an RDF:resource="..." attribute. That + // means that it's a "referenced item," as covered in [6.29]. + nsCOMPtr c; + NS_NewRDFContainer(getter_AddRefs(c)); + c->Init(mDataSource, container); + c->AppendElement(resource); + + // XXX Technically, we should _not_ fall through here and push + // the element onto the stack: this is supposed to be a closed + // node. But right now I'm lazy and the code will just Do The + // Right Thing so long as the RDF is well-formed. + NS_RELEASE(resource); + } + + // Change state. Pushing a null context element is a bit weird, + // but the idea is that there really is _no_ context "property". + // The contained element will use nsIRDFContainer::AppendElement() to add + // the element to the container, which requires only the container + // and the element to be added. + PushContext(nullptr, mState, mParseMode); + mState = eRDFContentSinkState_InMemberElement; + SetParseMode(aAttributes); + + return NS_OK; +} + + +nsresult +RDFContentSinkImpl::OpenValue(const char16_t* aName, const char16_t** aAttributes) +{ + // a "value" can either be an object or a string: we'll only get + // *here* if it's an object, as raw text is added as a leaf. + return OpenObject(aName,aAttributes); +} + +//////////////////////////////////////////////////////////////////////// +// namespace resolution +void +RDFContentSinkImpl::RegisterNamespaces(const char16_t **aAttributes) +{ + nsCOMPtr sink = do_QueryInterface(mDataSource); + if (!sink) { + return; + } + NS_NAMED_LITERAL_STRING(xmlns, "http://www.w3.org/2000/xmlns/"); + for (; *aAttributes; aAttributes += 2) { + // check the namespace + const char16_t* attr = aAttributes[0]; + const char16_t* xmlnsP = xmlns.BeginReading(); + while (*attr == *xmlnsP) { + ++attr; + ++xmlnsP; + } + if (*attr != 0xFFFF || + xmlnsP != xmlns.EndReading()) { + continue; + } + // get the localname (or "xmlns" for the default namespace) + const char16_t* endLocal = ++attr; + while (*endLocal && *endLocal != 0xFFFF) { + ++endLocal; + } + nsDependentSubstring lname(attr, endLocal); + nsCOMPtr preferred = NS_Atomize(lname); + if (preferred == kXMLNSAtom) { + preferred = nullptr; + } + sink->AddNameSpace(preferred, nsDependentString(aAttributes[1])); + } +} + +//////////////////////////////////////////////////////////////////////// +// Qualified name resolution + +const nsDependentSubstring +RDFContentSinkImpl::SplitExpatName(const char16_t *aExpatName, + nsIAtom **aLocalName) +{ + /** + * Expat can send the following: + * localName + * namespaceURIlocalName + * namespaceURIlocalNameprefix + * + * and we use 0xFFFF for the . + * + */ + + const char16_t *uriEnd = aExpatName; + const char16_t *nameStart = aExpatName; + const char16_t *pos; + for (pos = aExpatName; *pos; ++pos) { + if (*pos == 0xFFFF) { + if (uriEnd != aExpatName) { + break; + } + + uriEnd = pos; + nameStart = pos + 1; + } + } + + const nsDependentSubstring& nameSpaceURI = Substring(aExpatName, uriEnd); + *aLocalName = NS_Atomize(Substring(nameStart, pos)).take(); + return nameSpaceURI; +} + +nsresult +RDFContentSinkImpl::InitContainer(nsIRDFResource* aContainerType, nsIRDFResource* aContainer) +{ + // Do the right kind of initialization based on the container + // 'type' resource, and the state of the container (i.e., 'make' a + // new container vs. 'reinitialize' the container). + nsresult rv; + + static const ContainerInfo gContainerInfo[] = { + { &RDFContentSinkImpl::kRDF_Alt, &nsIRDFContainerUtils::IsAlt, &nsIRDFContainerUtils::MakeAlt }, + { &RDFContentSinkImpl::kRDF_Bag, &nsIRDFContainerUtils::IsBag, &nsIRDFContainerUtils::MakeBag }, + { &RDFContentSinkImpl::kRDF_Seq, &nsIRDFContainerUtils::IsSeq, &nsIRDFContainerUtils::MakeSeq }, + { 0, 0, 0 }, + }; + + for (const ContainerInfo* info = gContainerInfo; info->mType != 0; ++info) { + if (*info->mType != aContainerType) + continue; + + bool isContainer; + rv = (gRDFContainerUtils->*(info->mTestFn))(mDataSource, aContainer, &isContainer); + if (isContainer) { + rv = ReinitContainer(aContainerType, aContainer); + } + else { + rv = (gRDFContainerUtils->*(info->mMakeFn))(mDataSource, aContainer, nullptr); + } + return rv; + } + + NS_NOTREACHED("not an RDF container type"); + return NS_ERROR_FAILURE; +} + + + +nsresult +RDFContentSinkImpl::ReinitContainer(nsIRDFResource* aContainerType, nsIRDFResource* aContainer) +{ + // Mega-kludge to deal with the fact that Make[Seq|Alt|Bag] is + // idempotent, and as such, containers will have state (e.g., + // RDF:nextVal) maintained in the graph across loads. This + // re-initializes each container's RDF:nextVal to '1', and 'marks' + // the container as such. + nsresult rv; + + nsCOMPtr one; + rv = gRDFService->GetLiteral(u"1", getter_AddRefs(one)); + if (NS_FAILED(rv)) return rv; + + // Re-initialize the 'nextval' property + nsCOMPtr nextval; + rv = mDataSource->GetTarget(aContainer, kRDF_nextVal, true, getter_AddRefs(nextval)); + if (NS_FAILED(rv)) return rv; + + rv = mDataSource->Change(aContainer, kRDF_nextVal, nextval, one); + if (NS_FAILED(rv)) return rv; + + // Re-mark as a container. XXX should be kRDF_type + rv = mDataSource->Assert(aContainer, kRDF_instanceOf, aContainerType, true); + NS_ASSERTION(NS_SUCCEEDED(rv), "unable to mark container as such"); + if (NS_FAILED(rv)) return rv; + + return NS_OK; +} + +//////////////////////////////////////////////////////////////////////// +// Content stack management + +nsIRDFResource* +RDFContentSinkImpl::GetContextElement(int32_t ancestor /* = 0 */) +{ + if ((nullptr == mContextStack) || + (uint32_t(ancestor) >= mContextStack->Length())) { + return nullptr; + } + + return mContextStack->ElementAt( + mContextStack->Length()-ancestor-1).mResource; +} + +int32_t +RDFContentSinkImpl::PushContext(nsIRDFResource *aResource, + RDFContentSinkState aState, + RDFContentSinkParseMode aParseMode) +{ + if (! mContextStack) { + mContextStack = new AutoTArray(); + if (! mContextStack) + return 0; + } + + RDFContextStackElement* e = mContextStack->AppendElement(); + if (! e) + return mContextStack->Length(); + + e->mResource = aResource; + e->mState = aState; + e->mParseMode = aParseMode; + + return mContextStack->Length(); +} + +nsresult +RDFContentSinkImpl::PopContext(nsIRDFResource *&aResource, + RDFContentSinkState &aState, + RDFContentSinkParseMode &aParseMode) +{ + if ((nullptr == mContextStack) || + (mContextStack->IsEmpty())) { + return NS_ERROR_NULL_POINTER; + } + + uint32_t i = mContextStack->Length() - 1; + RDFContextStackElement &e = mContextStack->ElementAt(i); + + aResource = e.mResource; + NS_IF_ADDREF(aResource); + aState = e.mState; + aParseMode = e.mParseMode; + + mContextStack->RemoveElementAt(i); + return NS_OK; +} + + +//////////////////////////////////////////////////////////////////////// + +nsresult +NS_NewRDFContentSink(nsIRDFContentSink** aResult) +{ + NS_PRECONDITION(aResult != nullptr, "null ptr"); + if (! aResult) + return NS_ERROR_NULL_POINTER; + + RDFContentSinkImpl* sink = new RDFContentSinkImpl(); + if (! sink) + return NS_ERROR_OUT_OF_MEMORY; + + NS_ADDREF(sink); + *aResult = sink; + return NS_OK; +} diff --git a/rdf/base/nsRDFContentSinkAtomList.h b/rdf/base/nsRDFContentSinkAtomList.h new file mode 100644 index 000000000..5ef4f7b4e --- /dev/null +++ b/rdf/base/nsRDFContentSinkAtomList.h @@ -0,0 +1,18 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +RDF_ATOM(kAboutAtom, "about") +RDF_ATOM(kIdAtom, "ID") +RDF_ATOM(kNodeIdAtom, "nodeID") +RDF_ATOM(kAboutEachAtom, "aboutEach") +RDF_ATOM(kResourceAtom, "resource") +RDF_ATOM(kRDFAtom, "RDF") +RDF_ATOM(kDescriptionAtom, "Description") +RDF_ATOM(kBagAtom, "Bag") +RDF_ATOM(kSeqAtom, "Seq") +RDF_ATOM(kAltAtom, "Alt") +RDF_ATOM(kLiAtom, "li") +RDF_ATOM(kXMLNSAtom, "xmlns") +RDF_ATOM(kParseTypeAtom, "parseType") diff --git a/rdf/base/nsRDFService.cpp b/rdf/base/nsRDFService.cpp new file mode 100644 index 000000000..13a5e7195 --- /dev/null +++ b/rdf/base/nsRDFService.cpp @@ -0,0 +1,1551 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * + * This Original Code has been modified by IBM Corporation. + * Modifications made by IBM described herein are + * Copyright (c) International Business Machines + * Corporation, 2000 + * + * Modifications to Mozilla code or documentation + * identified per MPL Section 3.3 + * + * Date Modified by Description of modification + * 03/27/2000 IBM Corp. Added PR_CALLBACK for Optlink + * use in OS2 + */ + +/* + + This file provides the implementation for the RDF service manager. + + TO DO + ----- + + 1) Implement the CreateDataBase() methods. + + 2) Cache date and int literals. + + */ + +#include "nsRDFService.h" +#include "nsCOMPtr.h" +#include "nsAutoPtr.h" +#include "nsMemory.h" +#include "nsIAtom.h" +#include "nsIComponentManager.h" +#include "nsIRDFDataSource.h" +#include "nsIRDFNode.h" +#include "nsIRDFRemoteDataSource.h" +#include "nsIServiceManager.h" +#include "nsIFactory.h" +#include "nsRDFCID.h" +#include "nsString.h" +#include "nsXPIDLString.h" +#include "nsNetUtil.h" +#include "nsIURI.h" +#include "PLDHashTable.h" +#include "plhash.h" +#include "plstr.h" +#include "mozilla/Logging.h" +#include "prprf.h" +#include "prmem.h" +#include "rdf.h" +#include "nsCRT.h" +#include "nsCRTGlue.h" +#include "mozilla/HashFunctions.h" + +using namespace mozilla; + +//////////////////////////////////////////////////////////////////////// + +static NS_DEFINE_CID(kRDFXMLDataSourceCID, NS_RDFXMLDATASOURCE_CID); +static NS_DEFINE_CID(kRDFDefaultResourceCID, NS_RDFDEFAULTRESOURCE_CID); + +static NS_DEFINE_IID(kIRDFLiteralIID, NS_IRDFLITERAL_IID); +static NS_DEFINE_IID(kIRDFDateIID, NS_IRDFDATE_IID); +static NS_DEFINE_IID(kIRDFIntIID, NS_IRDFINT_IID); +static NS_DEFINE_IID(kIRDFNodeIID, NS_IRDFNODE_IID); +static NS_DEFINE_IID(kISupportsIID, NS_ISUPPORTS_IID); + +static LazyLogModule gLog("nsRDFService"); + +class BlobImpl; + +// These functions are copied from nsprpub/lib/ds/plhash.c, with one +// change to free the key in DataSourceFreeEntry. +// XXX sigh, why were DefaultAllocTable et. al. declared static, anyway? + +static void * +DataSourceAllocTable(void *pool, size_t size) +{ + return PR_MALLOC(size); +} + +static void +DataSourceFreeTable(void *pool, void *item) +{ + PR_Free(item); +} + +static PLHashEntry * +DataSourceAllocEntry(void *pool, const void *key) +{ + return PR_NEW(PLHashEntry); +} + +static void +DataSourceFreeEntry(void *pool, PLHashEntry *he, unsigned flag) +{ + if (flag == HT_FREE_ENTRY) { + PL_strfree((char*) he->key); + PR_Free(he); + } +} + +static PLHashAllocOps dataSourceHashAllocOps = { + DataSourceAllocTable, DataSourceFreeTable, + DataSourceAllocEntry, DataSourceFreeEntry +}; + +//---------------------------------------------------------------------- +// +// For the mResources hashtable. +// + +struct ResourceHashEntry : public PLDHashEntryHdr { + const char *mKey; + nsIRDFResource *mResource; + + static PLDHashNumber + HashKey(const void *key) + { + return HashString(static_cast(key)); + } + + static bool + MatchEntry(const PLDHashEntryHdr *hdr, const void *key) + { + const ResourceHashEntry *entry = + static_cast(hdr); + + return 0 == nsCRT::strcmp(static_cast(key), + entry->mKey); + } +}; + +static const PLDHashTableOps gResourceTableOps = { + ResourceHashEntry::HashKey, + ResourceHashEntry::MatchEntry, + PLDHashTable::MoveEntryStub, + PLDHashTable::ClearEntryStub, + nullptr +}; + +// ---------------------------------------------------------------------- +// +// For the mLiterals hashtable. +// + +struct LiteralHashEntry : public PLDHashEntryHdr { + nsIRDFLiteral *mLiteral; + const char16_t *mKey; + + static PLDHashNumber + HashKey(const void *key) + { + return HashString(static_cast(key)); + } + + static bool + MatchEntry(const PLDHashEntryHdr *hdr, const void *key) + { + const LiteralHashEntry *entry = + static_cast(hdr); + + return 0 == nsCRT::strcmp(static_cast(key), + entry->mKey); + } +}; + +static const PLDHashTableOps gLiteralTableOps = { + LiteralHashEntry::HashKey, + LiteralHashEntry::MatchEntry, + PLDHashTable::MoveEntryStub, + PLDHashTable::ClearEntryStub, + nullptr +}; + +// ---------------------------------------------------------------------- +// +// For the mInts hashtable. +// + +struct IntHashEntry : public PLDHashEntryHdr { + nsIRDFInt *mInt; + int32_t mKey; + + static PLDHashNumber + HashKey(const void *key) + { + return PLDHashNumber(*static_cast(key)); + } + + static bool + MatchEntry(const PLDHashEntryHdr *hdr, const void *key) + { + const IntHashEntry *entry = + static_cast(hdr); + + return *static_cast(key) == entry->mKey; + } +}; + +static const PLDHashTableOps gIntTableOps = { + IntHashEntry::HashKey, + IntHashEntry::MatchEntry, + PLDHashTable::MoveEntryStub, + PLDHashTable::ClearEntryStub, + nullptr +}; + +// ---------------------------------------------------------------------- +// +// For the mDates hashtable. +// + +struct DateHashEntry : public PLDHashEntryHdr { + nsIRDFDate *mDate; + PRTime mKey; + + static PLDHashNumber + HashKey(const void *key) + { + // xor the low 32 bits with the high 32 bits. + PRTime t = *static_cast(key); + int32_t h32 = int32_t(t >> 32); + int32_t l32 = int32_t(0xffffffff & t); + return PLDHashNumber(l32 ^ h32); + } + + static bool + MatchEntry(const PLDHashEntryHdr *hdr, const void *key) + { + const DateHashEntry *entry = + static_cast(hdr); + + return *static_cast(key) == entry->mKey; + } +}; + +static const PLDHashTableOps gDateTableOps = { + DateHashEntry::HashKey, + DateHashEntry::MatchEntry, + PLDHashTable::MoveEntryStub, + PLDHashTable::ClearEntryStub, + nullptr +}; + +class BlobImpl : public nsIRDFBlob +{ +public: + struct Data { + int32_t mLength; + uint8_t *mBytes; + }; + + BlobImpl(const uint8_t *aBytes, int32_t aLength) + { + mData.mLength = aLength; + mData.mBytes = new uint8_t[aLength]; + memcpy(mData.mBytes, aBytes, aLength); + NS_ADDREF(RDFServiceImpl::gRDFService); + RDFServiceImpl::gRDFService->RegisterBlob(this); + } + +protected: + virtual ~BlobImpl() + { + RDFServiceImpl::gRDFService->UnregisterBlob(this); + // Use NS_RELEASE2() here, because we want to decrease the + // refcount, but not null out the gRDFService pointer (which is + // what a vanilla NS_RELEASE() would do). + nsrefcnt refcnt; + NS_RELEASE2(RDFServiceImpl::gRDFService, refcnt); + delete[] mData.mBytes; + } + +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIRDFNODE + NS_DECL_NSIRDFBLOB + + Data mData; +}; + +NS_IMPL_ISUPPORTS(BlobImpl, nsIRDFNode, nsIRDFBlob) + +NS_IMETHODIMP +BlobImpl::EqualsNode(nsIRDFNode *aNode, bool *aEquals) +{ + nsCOMPtr blob = do_QueryInterface(aNode); + if (blob) { + int32_t length; + blob->GetLength(&length); + + if (length == mData.mLength) { + const uint8_t *bytes; + blob->GetValue(&bytes); + + if (0 == memcmp(bytes, mData.mBytes, length)) { + *aEquals = true; + return NS_OK; + } + } + } + + *aEquals = false; + return NS_OK; +} + +NS_IMETHODIMP +BlobImpl::GetValue(const uint8_t **aResult) +{ + *aResult = mData.mBytes; + return NS_OK; +} + +NS_IMETHODIMP +BlobImpl::GetLength(int32_t *aResult) +{ + *aResult = mData.mLength; + return NS_OK; +} + +// ---------------------------------------------------------------------- +// +// For the mBlobs hashtable. +// + +struct BlobHashEntry : public PLDHashEntryHdr { + BlobImpl *mBlob; + + static PLDHashNumber + HashKey(const void *key) + { + const BlobImpl::Data *data = + static_cast(key); + return HashBytes(data->mBytes, data->mLength); + } + + static bool + MatchEntry(const PLDHashEntryHdr *hdr, const void *key) + { + const BlobHashEntry *entry = + static_cast(hdr); + + const BlobImpl::Data *left = &entry->mBlob->mData; + + const BlobImpl::Data *right = + static_cast(key); + + return (left->mLength == right->mLength) + && 0 == memcmp(left->mBytes, right->mBytes, right->mLength); + } +}; + +static const PLDHashTableOps gBlobTableOps = { + BlobHashEntry::HashKey, + BlobHashEntry::MatchEntry, + PLDHashTable::MoveEntryStub, + PLDHashTable::ClearEntryStub, + nullptr +}; + +//////////////////////////////////////////////////////////////////////// +// LiteralImpl +// +// Currently, all literals are implemented exactly the same way; +// i.e., there is are no resource factories to allow you to generate +// customer resources. I doubt that makes sense, anyway. +// +class LiteralImpl : public nsIRDFLiteral { +public: + static nsresult + Create(const char16_t* aValue, nsIRDFLiteral** aResult); + + // nsISupports + NS_DECL_THREADSAFE_ISUPPORTS + + // nsIRDFNode + NS_DECL_NSIRDFNODE + + // nsIRDFLiteral + NS_DECL_NSIRDFLITERAL + +protected: + explicit LiteralImpl(const char16_t* s); + virtual ~LiteralImpl(); + + const char16_t* GetValue() const { + size_t objectSize = ((sizeof(LiteralImpl) + sizeof(char16_t) - 1) / sizeof(char16_t)) * sizeof(char16_t); + return reinterpret_cast(reinterpret_cast(this) + objectSize); + } +}; + + +nsresult +LiteralImpl::Create(const char16_t* aValue, nsIRDFLiteral** aResult) +{ + // Goofy math to get alignment right. Copied from nsSharedString.h. + size_t objectSize = ((sizeof(LiteralImpl) + sizeof(char16_t) - 1) / sizeof(char16_t)) * sizeof(char16_t); + size_t stringLen = nsCharTraits::length(aValue); + size_t stringSize = (stringLen + 1) * sizeof(char16_t); + + void* objectPtr = operator new(objectSize + stringSize); + if (! objectPtr) + return NS_ERROR_NULL_POINTER; + + char16_t* buf = reinterpret_cast(static_cast(objectPtr) + objectSize); + nsCharTraits::copy(buf, aValue, stringLen + 1); + + NS_ADDREF(*aResult = new (objectPtr) LiteralImpl(buf)); + return NS_OK; +} + + +LiteralImpl::LiteralImpl(const char16_t* s) +{ + RDFServiceImpl::gRDFService->RegisterLiteral(this); + NS_ADDREF(RDFServiceImpl::gRDFService); +} + +LiteralImpl::~LiteralImpl() +{ + RDFServiceImpl::gRDFService->UnregisterLiteral(this); + + // Use NS_RELEASE2() here, because we want to decrease the + // refcount, but not null out the gRDFService pointer (which is + // what a vanilla NS_RELEASE() would do). + nsrefcnt refcnt; + NS_RELEASE2(RDFServiceImpl::gRDFService, refcnt); +} + +NS_IMPL_ADDREF(LiteralImpl) +NS_IMPL_RELEASE(LiteralImpl) + +nsresult +LiteralImpl::QueryInterface(REFNSIID iid, void** result) +{ + if (! result) + return NS_ERROR_NULL_POINTER; + + *result = nullptr; + if (iid.Equals(kIRDFLiteralIID) || + iid.Equals(kIRDFNodeIID) || + iid.Equals(kISupportsIID)) { + *result = static_cast(this); + AddRef(); + return NS_OK; + } + return NS_NOINTERFACE; +} + +NS_IMETHODIMP +LiteralImpl::EqualsNode(nsIRDFNode* aNode, bool* aResult) +{ + nsresult rv; + nsIRDFLiteral* literal; + rv = aNode->QueryInterface(kIRDFLiteralIID, (void**) &literal); + if (NS_SUCCEEDED(rv)) { + *aResult = (static_cast(this) == literal); + NS_RELEASE(literal); + return NS_OK; + } + else if (rv == NS_NOINTERFACE) { + *aResult = false; + return NS_OK; + } + else { + return rv; + } +} + +NS_IMETHODIMP +LiteralImpl::GetValue(char16_t* *value) +{ + NS_ASSERTION(value, "null ptr"); + if (! value) + return NS_ERROR_NULL_POINTER; + + const char16_t *temp = GetValue(); + *value = temp? NS_strdup(temp) : 0; + return NS_OK; +} + + +NS_IMETHODIMP +LiteralImpl::GetValueConst(const char16_t** aValue) +{ + *aValue = GetValue(); + return NS_OK; +} + +//////////////////////////////////////////////////////////////////////// +// DateImpl +// + +class DateImpl : public nsIRDFDate { +public: + explicit DateImpl(const PRTime s); + + // nsISupports + NS_DECL_ISUPPORTS + + // nsIRDFNode + NS_DECL_NSIRDFNODE + + // nsIRDFDate + NS_IMETHOD GetValue(PRTime *value) override; + +private: + virtual ~DateImpl(); + + nsresult EqualsDate(nsIRDFDate* date, bool* result); + PRTime mValue; +}; + + +DateImpl::DateImpl(const PRTime s) + : mValue(s) +{ + RDFServiceImpl::gRDFService->RegisterDate(this); + NS_ADDREF(RDFServiceImpl::gRDFService); +} + +DateImpl::~DateImpl() +{ + RDFServiceImpl::gRDFService->UnregisterDate(this); + + // Use NS_RELEASE2() here, because we want to decrease the + // refcount, but not null out the gRDFService pointer (which is + // what a vanilla NS_RELEASE() would do). + nsrefcnt refcnt; + NS_RELEASE2(RDFServiceImpl::gRDFService, refcnt); +} + +NS_IMPL_ADDREF(DateImpl) +NS_IMPL_RELEASE(DateImpl) + +nsresult +DateImpl::QueryInterface(REFNSIID iid, void** result) +{ + if (! result) + return NS_ERROR_NULL_POINTER; + + *result = nullptr; + if (iid.Equals(kIRDFDateIID) || + iid.Equals(kIRDFNodeIID) || + iid.Equals(kISupportsIID)) { + *result = static_cast(this); + AddRef(); + return NS_OK; + } + return NS_NOINTERFACE; +} + +NS_IMETHODIMP +DateImpl::EqualsNode(nsIRDFNode* node, bool* result) +{ + nsresult rv; + nsIRDFDate* date; + if (NS_SUCCEEDED(node->QueryInterface(kIRDFDateIID, (void**) &date))) { + rv = EqualsDate(date, result); + NS_RELEASE(date); + } + else { + *result = false; + rv = NS_OK; + } + return rv; +} + +NS_IMETHODIMP +DateImpl::GetValue(PRTime *value) +{ + NS_ASSERTION(value, "null ptr"); + if (! value) + return NS_ERROR_NULL_POINTER; + + *value = mValue; + return NS_OK; +} + + +nsresult +DateImpl::EqualsDate(nsIRDFDate* date, bool* result) +{ + NS_ASSERTION(date && result, "null ptr"); + if (!date || !result) + return NS_ERROR_NULL_POINTER; + + nsresult rv; + PRTime p; + if (NS_FAILED(rv = date->GetValue(&p))) + return rv; + + *result = p == mValue; + return NS_OK; +} + +//////////////////////////////////////////////////////////////////////// +// IntImpl +// + +class IntImpl : public nsIRDFInt { +public: + explicit IntImpl(int32_t s); + + // nsISupports + NS_DECL_ISUPPORTS + + // nsIRDFNode + NS_DECL_NSIRDFNODE + + // nsIRDFInt + NS_IMETHOD GetValue(int32_t *value) override; + +private: + virtual ~IntImpl(); + + nsresult EqualsInt(nsIRDFInt* value, bool* result); + int32_t mValue; +}; + + +IntImpl::IntImpl(int32_t s) + : mValue(s) +{ + RDFServiceImpl::gRDFService->RegisterInt(this); + NS_ADDREF(RDFServiceImpl::gRDFService); +} + +IntImpl::~IntImpl() +{ + RDFServiceImpl::gRDFService->UnregisterInt(this); + + // Use NS_RELEASE2() here, because we want to decrease the + // refcount, but not null out the gRDFService pointer (which is + // what a vanilla NS_RELEASE() would do). + nsrefcnt refcnt; + NS_RELEASE2(RDFServiceImpl::gRDFService, refcnt); +} + +NS_IMPL_ADDREF(IntImpl) +NS_IMPL_RELEASE(IntImpl) + +nsresult +IntImpl::QueryInterface(REFNSIID iid, void** result) +{ + if (! result) + return NS_ERROR_NULL_POINTER; + + *result = nullptr; + if (iid.Equals(kIRDFIntIID) || + iid.Equals(kIRDFNodeIID) || + iid.Equals(kISupportsIID)) { + *result = static_cast(this); + AddRef(); + return NS_OK; + } + return NS_NOINTERFACE; +} + +NS_IMETHODIMP +IntImpl::EqualsNode(nsIRDFNode* node, bool* result) +{ + nsresult rv; + nsIRDFInt* intValue; + if (NS_SUCCEEDED(node->QueryInterface(kIRDFIntIID, (void**) &intValue))) { + rv = EqualsInt(intValue, result); + NS_RELEASE(intValue); + } + else { + *result = false; + rv = NS_OK; + } + return rv; +} + +NS_IMETHODIMP +IntImpl::GetValue(int32_t *value) +{ + NS_ASSERTION(value, "null ptr"); + if (! value) + return NS_ERROR_NULL_POINTER; + + *value = mValue; + return NS_OK; +} + + +nsresult +IntImpl::EqualsInt(nsIRDFInt* intValue, bool* result) +{ + NS_ASSERTION(intValue && result, "null ptr"); + if (!intValue || !result) + return NS_ERROR_NULL_POINTER; + + nsresult rv; + int32_t p; + if (NS_FAILED(rv = intValue->GetValue(&p))) + return rv; + + *result = (p == mValue); + return NS_OK; +} + +//////////////////////////////////////////////////////////////////////// +// RDFServiceImpl + +RDFServiceImpl* +RDFServiceImpl::gRDFService; + +RDFServiceImpl::RDFServiceImpl() + : mNamedDataSources(nullptr) + , mResources(&gResourceTableOps, sizeof(ResourceHashEntry)) + , mLiterals(&gLiteralTableOps, sizeof(LiteralHashEntry)) + , mInts(&gIntTableOps, sizeof(IntHashEntry)) + , mDates(&gDateTableOps, sizeof(DateHashEntry)) + , mBlobs(&gBlobTableOps, sizeof(BlobHashEntry)) +{ + gRDFService = this; +} + +nsresult +RDFServiceImpl::Init() +{ + nsresult rv; + + mNamedDataSources = PL_NewHashTable(23, + PL_HashString, + PL_CompareStrings, + PL_CompareValues, + &dataSourceHashAllocOps, nullptr); + + if (! mNamedDataSources) + return NS_ERROR_OUT_OF_MEMORY; + + mDefaultResourceFactory = do_GetClassObject(kRDFDefaultResourceCID, &rv); + NS_ASSERTION(NS_SUCCEEDED(rv), "unable to get default resource factory"); + if (NS_FAILED(rv)) return rv; + + return NS_OK; +} + + +RDFServiceImpl::~RDFServiceImpl() +{ + if (mNamedDataSources) { + PL_HashTableDestroy(mNamedDataSources); + mNamedDataSources = nullptr; + } + gRDFService = nullptr; +} + + +// static +nsresult +RDFServiceImpl::CreateSingleton(nsISupports* aOuter, + const nsIID& aIID, void **aResult) +{ + NS_ENSURE_NO_AGGREGATION(aOuter); + + if (gRDFService) { + NS_ERROR("Trying to create RDF serviec twice."); + return gRDFService->QueryInterface(aIID, aResult); + } + + RefPtr serv = new RDFServiceImpl(); + nsresult rv = serv->Init(); + if (NS_FAILED(rv)) + return rv; + + return serv->QueryInterface(aIID, aResult); +} + +NS_IMPL_ISUPPORTS(RDFServiceImpl, nsIRDFService, nsISupportsWeakReference) + +// Per RFC2396. +static const uint8_t +kLegalSchemeChars[] = { + // ASCII Bits Ordered Hex + // 01234567 76543210 + 0x00, // 00-07 + 0x00, // 08-0F + 0x00, // 10-17 + 0x00, // 18-1F + 0x00, // 20-27 !"#$%&' 00000000 00000000 + 0x28, // 28-2F ()*+,-./ 00010100 00101000 0x28 + 0xff, // 30-37 01234567 11111111 11111111 0xFF + 0x03, // 38-3F 89:;<=>? 11000000 00000011 0x03 + 0xfe, // 40-47 @ABCDEFG 01111111 11111110 0xFE + 0xff, // 48-4F HIJKLMNO 11111111 11111111 0xFF + 0xff, // 50-57 PQRSTUVW 11111111 11111111 0xFF + 0x87, // 58-5F XYZ[\]^_ 11100001 10000111 0x87 + 0xfe, // 60-67 `abcdefg 01111111 11111110 0xFE + 0xff, // 68-6F hijklmno 11111111 11111111 0xFF + 0xff, // 70-77 pqrstuvw 11111111 11111111 0xFF + 0x07, // 78-7F xyz{|}~ 11100000 00000111 0x07 + 0x00, 0x00, 0x00, 0x00, // >= 80 + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00 +}; + +static inline bool +IsLegalSchemeCharacter(const char aChar) +{ + uint8_t mask = kLegalSchemeChars[aChar >> 3]; + uint8_t bit = 1u << (aChar & 0x7); + return bool((mask & bit) != 0); +} + + +NS_IMETHODIMP +RDFServiceImpl::GetResource(const nsACString& aURI, nsIRDFResource** aResource) +{ + // Sanity checks + NS_PRECONDITION(aResource != nullptr, "null ptr"); + NS_PRECONDITION(!aURI.IsEmpty(), "URI is empty"); + if (! aResource) + return NS_ERROR_NULL_POINTER; + if (aURI.IsEmpty()) + return NS_ERROR_INVALID_ARG; + + const nsAFlatCString& flatURI = PromiseFlatCString(aURI); + MOZ_LOG(gLog, LogLevel::Debug, ("rdfserv get-resource %s", flatURI.get())); + + // First, check the cache to see if we've already created and + // registered this thing. + PLDHashEntryHdr *hdr = mResources.Search(flatURI.get()); + if (hdr) { + ResourceHashEntry *entry = static_cast(hdr); + NS_ADDREF(*aResource = entry->mResource); + return NS_OK; + } + + // Nope. So go to the repository to create it. + + // Compute the scheme of the URI. Scan forward until we either: + // + // 1. Reach the end of the string + // 2. Encounter a non-alpha character + // 3. Encouter a colon. + // + // If we encounter a colon _before_ encountering a non-alpha + // character, then assume it's the scheme. + // + // XXX Although it's really not correct, we'll allow underscore + // characters ('_'), too. + nsACString::const_iterator p, end; + aURI.BeginReading(p); + aURI.EndReading(end); + while (p != end && IsLegalSchemeCharacter(*p)) + ++p; + + nsresult rv; + nsCOMPtr factory; + + nsACString::const_iterator begin; + aURI.BeginReading(begin); + if (*p == ':') { + // There _was_ a scheme. First see if it's the same scheme + // that we just tried to use... + if (mLastFactory && mLastURIPrefix.Equals(Substring(begin, p))) + factory = mLastFactory; + else { + // Try to find a factory using the component manager. + nsACString::const_iterator begin; + aURI.BeginReading(begin); + nsAutoCString contractID; + contractID = NS_LITERAL_CSTRING(NS_RDF_RESOURCE_FACTORY_CONTRACTID_PREFIX) + + Substring(begin, p); + + factory = do_GetClassObject(contractID.get()); + if (factory) { + // Store the factory in our one-element cache. + if (p != begin) { + mLastFactory = factory; + mLastURIPrefix = Substring(begin, p); + } + } + } + } + + if (! factory) { + // fall through to using the "default" resource factory if either: + // + // 1. The URI didn't have a scheme, or + // 2. There was no resource factory registered for the scheme. + factory = mDefaultResourceFactory; + + // Store the factory in our one-element cache. + if (p != begin) { + mLastFactory = factory; + mLastURIPrefix = Substring(begin, p); + } + } + + nsIRDFResource *result; + rv = factory->CreateInstance(nullptr, NS_GET_IID(nsIRDFResource), (void**) &result); + if (NS_FAILED(rv)) return rv; + + // Now initialize it with its URI. At this point, the resource + // implementation should register itself with the RDF service. + rv = result->Init(flatURI.get()); + if (NS_FAILED(rv)) { + NS_ERROR("unable to initialize resource"); + NS_RELEASE(result); + return rv; + } + + *aResource = result; // already refcounted from repository + return rv; +} + +NS_IMETHODIMP +RDFServiceImpl::GetUnicodeResource(const nsAString& aURI, nsIRDFResource** aResource) +{ + return GetResource(NS_ConvertUTF16toUTF8(aURI), aResource); +} + + +NS_IMETHODIMP +RDFServiceImpl::GetAnonymousResource(nsIRDFResource** aResult) +{ +static uint32_t gCounter = 0; +static char gChars[] = "0123456789abcdef" + "ghijklmnopqrstuv" + "wxyzABCDEFGHIJKL" + "MNOPQRSTUVWXYZ.+"; + +static int32_t kMask = 0x003f; +static int32_t kShift = 6; + + if (! gCounter) { + // Start it at a semi-unique value, just to minimize the + // chance that we get into a situation where + // + // 1. An anonymous resource gets serialized out in a graph + // 2. Reboot + // 3. The same anonymous resource gets requested, and refers + // to something completely different. + // 4. The serialization is read back in. + gCounter = uint32_t(PR_Now()); + } + + nsresult rv; + nsAutoCString s; + + do { + // Ugh, this is a really sloppy way to do this; I copied the + // implementation from the days when it lived outside the RDF + // service. Now that it's a member we can be more cleverer. + + s.Truncate(); + s.AppendLiteral("rdf:#$"); + + uint32_t id = ++gCounter; + while (id) { + char ch = gChars[(id & kMask)]; + s.Append(ch); + id >>= kShift; + } + + nsIRDFResource* resource; + rv = GetResource(s, &resource); + if (NS_FAILED(rv)) return rv; + + // XXX an ugly but effective way to make sure that this + // resource is really unique in the world. + resource->AddRef(); + nsrefcnt refcnt = resource->Release(); + + if (refcnt == 1) { + *aResult = resource; + break; + } + + NS_RELEASE(resource); + } while (1); + + return NS_OK; +} + + +NS_IMETHODIMP +RDFServiceImpl::GetLiteral(const char16_t* aValue, nsIRDFLiteral** aLiteral) +{ + NS_PRECONDITION(aValue != nullptr, "null ptr"); + if (! aValue) + return NS_ERROR_NULL_POINTER; + + NS_PRECONDITION(aLiteral != nullptr, "null ptr"); + if (! aLiteral) + return NS_ERROR_NULL_POINTER; + + // See if we have one already cached + PLDHashEntryHdr *hdr = mLiterals.Search(aValue); + if (hdr) { + LiteralHashEntry *entry = static_cast(hdr); + NS_ADDREF(*aLiteral = entry->mLiteral); + return NS_OK; + } + + // Nope. Create a new one + return LiteralImpl::Create(aValue, aLiteral); +} + +NS_IMETHODIMP +RDFServiceImpl::GetDateLiteral(PRTime aTime, nsIRDFDate** aResult) +{ + // See if we have one already cached + PLDHashEntryHdr *hdr = mDates.Search(&aTime); + if (hdr) { + DateHashEntry *entry = static_cast(hdr); + NS_ADDREF(*aResult = entry->mDate); + return NS_OK; + } + + DateImpl* result = new DateImpl(aTime); + if (! result) + return NS_ERROR_OUT_OF_MEMORY; + + NS_ADDREF(*aResult = result); + return NS_OK; +} + +NS_IMETHODIMP +RDFServiceImpl::GetIntLiteral(int32_t aInt, nsIRDFInt** aResult) +{ + // See if we have one already cached + PLDHashEntryHdr *hdr = mInts.Search(&aInt); + if (hdr) { + IntHashEntry *entry = static_cast(hdr); + NS_ADDREF(*aResult = entry->mInt); + return NS_OK; + } + + IntImpl* result = new IntImpl(aInt); + if (! result) + return NS_ERROR_OUT_OF_MEMORY; + + NS_ADDREF(*aResult = result); + return NS_OK; +} + +NS_IMETHODIMP +RDFServiceImpl::GetBlobLiteral(const uint8_t *aBytes, int32_t aLength, + nsIRDFBlob **aResult) +{ + BlobImpl::Data key = { aLength, const_cast(aBytes) }; + + PLDHashEntryHdr *hdr = mBlobs.Search(&key); + if (hdr) { + BlobHashEntry *entry = static_cast(hdr); + NS_ADDREF(*aResult = entry->mBlob); + return NS_OK; + } + + BlobImpl *result = new BlobImpl(aBytes, aLength); + if (! result) + return NS_ERROR_OUT_OF_MEMORY; + + NS_ADDREF(*aResult = result); + return NS_OK; +} + +NS_IMETHODIMP +RDFServiceImpl::IsAnonymousResource(nsIRDFResource* aResource, bool* _result) +{ + NS_PRECONDITION(aResource != nullptr, "null ptr"); + if (! aResource) + return NS_ERROR_NULL_POINTER; + + nsresult rv; + + const char* uri; + rv = aResource->GetValueConst(&uri); + if (NS_FAILED(rv)) return rv; + + if ((uri[0] == 'r') && + (uri[1] == 'd') && + (uri[2] == 'f') && + (uri[3] == ':') && + (uri[4] == '#') && + (uri[5] == '$')) { + *_result = true; + } + else { + *_result = false; + } + + return NS_OK; +} + +NS_IMETHODIMP +RDFServiceImpl::RegisterResource(nsIRDFResource* aResource, bool aReplace) +{ + NS_PRECONDITION(aResource != nullptr, "null ptr"); + if (! aResource) + return NS_ERROR_NULL_POINTER; + + nsresult rv; + + const char* uri; + rv = aResource->GetValueConst(&uri); + NS_ASSERTION(NS_SUCCEEDED(rv), "unable to get URI from resource"); + if (NS_FAILED(rv)) return rv; + + NS_ASSERTION(uri != nullptr, "resource has no URI"); + if (! uri) + return NS_ERROR_NULL_POINTER; + + PLDHashEntryHdr *hdr = mResources.Search(uri); + if (hdr) { + if (!aReplace) { + NS_WARNING("resource already registered, and replace not specified"); + return NS_ERROR_FAILURE; // already registered + } + + // N.B., we do _not_ release the original resource because we + // only ever held a weak reference to it. We simply replace + // it. + + MOZ_LOG(gLog, LogLevel::Debug, + ("rdfserv replace-resource [%p] <-- [%p] %s", + static_cast(hdr)->mResource, + aResource, (const char*) uri)); + } + else { + hdr = mResources.Add(uri, fallible); + if (! hdr) + return NS_ERROR_OUT_OF_MEMORY; + + MOZ_LOG(gLog, LogLevel::Debug, + ("rdfserv register-resource [%p] %s", + aResource, (const char*) uri)); + } + + // N.B., we only hold a weak reference to the resource: that way, + // the resource can be destroyed when the last refcount goes + // away. The single addref that the CreateResource() call made + // will be owned by the callee. + ResourceHashEntry *entry = static_cast(hdr); + entry->mResource = aResource; + entry->mKey = uri; + + return NS_OK; +} + +NS_IMETHODIMP +RDFServiceImpl::UnregisterResource(nsIRDFResource* aResource) +{ + NS_PRECONDITION(aResource != nullptr, "null ptr"); + if (! aResource) + return NS_ERROR_NULL_POINTER; + + nsresult rv; + + const char* uri; + rv = aResource->GetValueConst(&uri); + if (NS_FAILED(rv)) return rv; + + NS_ASSERTION(uri != nullptr, "resource has no URI"); + if (! uri) + return NS_ERROR_UNEXPECTED; + + MOZ_LOG(gLog, LogLevel::Debug, + ("rdfserv unregister-resource [%p] %s", + aResource, (const char*) uri)); + +#ifdef DEBUG + if (!mResources.Search(uri)) + NS_WARNING("resource was never registered"); +#endif + + mResources.Remove(uri); + return NS_OK; +} + +NS_IMETHODIMP +RDFServiceImpl::RegisterDataSource(nsIRDFDataSource* aDataSource, bool aReplace) +{ + NS_PRECONDITION(aDataSource != nullptr, "null ptr"); + if (! aDataSource) + return NS_ERROR_NULL_POINTER; + + nsresult rv; + + nsXPIDLCString uri; + rv = aDataSource->GetURI(getter_Copies(uri)); + if (NS_FAILED(rv)) return rv; + + PLHashEntry** hep = + PL_HashTableRawLookup(mNamedDataSources, (*mNamedDataSources->keyHash)(uri), uri); + + if (*hep) { + if (! aReplace) + return NS_ERROR_FAILURE; // already registered + + // N.B., we only hold a weak reference to the datasource, so + // just replace the old with the new and don't touch any + // refcounts. + MOZ_LOG(gLog, LogLevel::Debug, + ("rdfserv replace-datasource [%p] <-- [%p] %s", + (*hep)->value, aDataSource, (const char*) uri)); + + (*hep)->value = aDataSource; + } + else { + const char* key = PL_strdup(uri); + if (! key) + return NS_ERROR_OUT_OF_MEMORY; + + PL_HashTableAdd(mNamedDataSources, key, aDataSource); + + MOZ_LOG(gLog, LogLevel::Debug, + ("rdfserv register-datasource [%p] %s", + aDataSource, (const char*) uri)); + + // N.B., we only hold a weak reference to the datasource, so don't + // addref. + } + + return NS_OK; +} + +NS_IMETHODIMP +RDFServiceImpl::UnregisterDataSource(nsIRDFDataSource* aDataSource) +{ + NS_PRECONDITION(aDataSource != nullptr, "null ptr"); + if (! aDataSource) + return NS_ERROR_NULL_POINTER; + + nsresult rv; + + nsXPIDLCString uri; + rv = aDataSource->GetURI(getter_Copies(uri)); + if (NS_FAILED(rv)) return rv; + + //NS_ASSERTION(uri != nullptr, "datasource has no URI"); + if (! uri) + return NS_ERROR_UNEXPECTED; + + PLHashEntry** hep = + PL_HashTableRawLookup(mNamedDataSources, (*mNamedDataSources->keyHash)(uri), uri); + + // It may well be that this datasource was never registered. If + // so, don't unregister it. + if (! *hep || ((*hep)->value != aDataSource)) + return NS_OK; + + // N.B., we only held a weak reference to the datasource, so we + // don't release here. + PL_HashTableRawRemove(mNamedDataSources, hep, *hep); + + MOZ_LOG(gLog, LogLevel::Debug, + ("rdfserv unregister-datasource [%p] %s", + aDataSource, (const char*) uri)); + + return NS_OK; +} + +NS_IMETHODIMP +RDFServiceImpl::GetDataSource(const char* aURI, nsIRDFDataSource** aDataSource) +{ + // Use the other GetDataSource and ask for a non-blocking Refresh. + // If you wanted it loaded synchronously, then you should've tried to do it + // yourself, or used GetDataSourceBlocking. + return GetDataSource( aURI, false, aDataSource ); +} + +NS_IMETHODIMP +RDFServiceImpl::GetDataSourceBlocking(const char* aURI, nsIRDFDataSource** aDataSource) +{ + // Use GetDataSource and ask for a blocking Refresh. + return GetDataSource( aURI, true, aDataSource ); +} + +nsresult +RDFServiceImpl::GetDataSource(const char* aURI, bool aBlock, nsIRDFDataSource** aDataSource) +{ + NS_PRECONDITION(aURI != nullptr, "null ptr"); + if (! aURI) + return NS_ERROR_NULL_POINTER; + + nsresult rv; + + // Attempt to canonify the URI before we look for it in the + // cache. We won't bother doing this on `rdf:' URIs to avoid + // useless (and expensive) protocol handler lookups. + nsAutoCString spec(aURI); + + if (!StringBeginsWith(spec, NS_LITERAL_CSTRING("rdf:"))) { + nsCOMPtr uri; + NS_NewURI(getter_AddRefs(uri), spec); + if (uri) { + rv = uri->GetSpec(spec); + if (NS_FAILED(rv)) return rv; + } + } + + // First, check the cache to see if we already have this + // datasource loaded and initialized. + { + nsIRDFDataSource* cached = + static_cast(PL_HashTableLookup(mNamedDataSources, spec.get())); + + if (cached) { + NS_ADDREF(cached); + *aDataSource = cached; + return NS_OK; + } + } + + // Nope. So go to the repository to try to create it. + nsCOMPtr ds; + if (StringBeginsWith(spec, NS_LITERAL_CSTRING("rdf:"))) { + // It's a built-in data source. Convert it to a contract ID. + nsAutoCString contractID( + NS_LITERAL_CSTRING(NS_RDF_DATASOURCE_CONTRACTID_PREFIX) + + Substring(spec, 4, spec.Length() - 4)); + + // Strip params to get ``base'' contractID for data source. + int32_t p = contractID.FindChar(char16_t('&')); + if (p >= 0) + contractID.Truncate(p); + + ds = do_GetService(contractID.get(), &rv); + if (NS_FAILED(rv)) return rv; + + nsCOMPtr remote = do_QueryInterface(ds); + if (remote) { + rv = remote->Init(spec.get()); + if (NS_FAILED(rv)) return rv; + } + } + else { + // Try to load this as an RDF/XML data source + ds = do_CreateInstance(kRDFXMLDataSourceCID, &rv); + if (NS_FAILED(rv)) return rv; + + nsCOMPtr remote(do_QueryInterface(ds)); + NS_ASSERTION(remote, "not a remote RDF/XML data source!"); + if (! remote) return NS_ERROR_UNEXPECTED; + + rv = remote->Init(spec.get()); + if (NS_FAILED(rv)) return rv; + + rv = remote->Refresh(aBlock); + if (NS_FAILED(rv)) return rv; + } + + *aDataSource = ds; + NS_ADDREF(*aDataSource); + return NS_OK; +} + +//////////////////////////////////////////////////////////////////////// + +nsresult +RDFServiceImpl::RegisterLiteral(nsIRDFLiteral* aLiteral) +{ + const char16_t* value; + aLiteral->GetValueConst(&value); + + NS_ASSERTION(!mLiterals.Search(value), "literal already registered"); + + PLDHashEntryHdr *hdr = mLiterals.Add(value, fallible); + if (! hdr) + return NS_ERROR_OUT_OF_MEMORY; + + LiteralHashEntry *entry = static_cast(hdr); + + // N.B., we only hold a weak reference to the literal: that + // way, the literal can be destroyed when the last refcount + // goes away. The single addref that the CreateLiteral() call + // made will be owned by the callee. + entry->mLiteral = aLiteral; + entry->mKey = value; + + MOZ_LOG(gLog, LogLevel::Debug, + ("rdfserv register-literal [%p] %s", + aLiteral, (const char16_t*) value)); + + return NS_OK; +} + + +nsresult +RDFServiceImpl::UnregisterLiteral(nsIRDFLiteral* aLiteral) +{ + const char16_t* value; + aLiteral->GetValueConst(&value); + + NS_ASSERTION(mLiterals.Search(value), "literal was never registered"); + + mLiterals.Remove(value); + + // N.B. that we _don't_ release the literal: we only held a weak + // reference to it in the hashtable. + MOZ_LOG(gLog, LogLevel::Debug, + ("rdfserv unregister-literal [%p] %s", + aLiteral, (const char16_t*) value)); + + return NS_OK; +} + +//---------------------------------------------------------------------- + +nsresult +RDFServiceImpl::RegisterInt(nsIRDFInt* aInt) +{ + int32_t value; + aInt->GetValue(&value); + + NS_ASSERTION(!mInts.Search(&value), "int already registered"); + + PLDHashEntryHdr *hdr = mInts.Add(&value, fallible); + if (! hdr) + return NS_ERROR_OUT_OF_MEMORY; + + IntHashEntry *entry = static_cast(hdr); + + // N.B., we only hold a weak reference to the literal: that + // way, the literal can be destroyed when the last refcount + // goes away. The single addref that the CreateInt() call + // made will be owned by the callee. + entry->mInt = aInt; + entry->mKey = value; + + MOZ_LOG(gLog, LogLevel::Debug, + ("rdfserv register-int [%p] %d", + aInt, value)); + + return NS_OK; +} + + +nsresult +RDFServiceImpl::UnregisterInt(nsIRDFInt* aInt) +{ + int32_t value; + aInt->GetValue(&value); + + NS_ASSERTION(mInts.Search(&value), "int was never registered"); + + mInts.Remove(&value); + + // N.B. that we _don't_ release the literal: we only held a weak + // reference to it in the hashtable. + MOZ_LOG(gLog, LogLevel::Debug, + ("rdfserv unregister-int [%p] %d", + aInt, value)); + + return NS_OK; +} + +//---------------------------------------------------------------------- + +nsresult +RDFServiceImpl::RegisterDate(nsIRDFDate* aDate) +{ + PRTime value; + aDate->GetValue(&value); + + NS_ASSERTION(!mDates.Search(&value), "date already registered"); + + PLDHashEntryHdr *hdr = mDates.Add(&value, fallible); + if (! hdr) + return NS_ERROR_OUT_OF_MEMORY; + + DateHashEntry *entry = static_cast(hdr); + + // N.B., we only hold a weak reference to the literal: that + // way, the literal can be destroyed when the last refcount + // goes away. The single addref that the CreateDate() call + // made will be owned by the callee. + entry->mDate = aDate; + entry->mKey = value; + + MOZ_LOG(gLog, LogLevel::Debug, + ("rdfserv register-date [%p] %ld", + aDate, value)); + + return NS_OK; +} + + +nsresult +RDFServiceImpl::UnregisterDate(nsIRDFDate* aDate) +{ + PRTime value; + aDate->GetValue(&value); + + NS_ASSERTION(mDates.Search(&value), "date was never registered"); + + mDates.Remove(&value); + + // N.B. that we _don't_ release the literal: we only held a weak + // reference to it in the hashtable. + MOZ_LOG(gLog, LogLevel::Debug, + ("rdfserv unregister-date [%p] %ld", + aDate, value)); + + return NS_OK; +} + +nsresult +RDFServiceImpl::RegisterBlob(BlobImpl *aBlob) +{ + NS_ASSERTION(!mBlobs.Search(&aBlob->mData), "blob already registered"); + + PLDHashEntryHdr *hdr = mBlobs.Add(&aBlob->mData, fallible); + if (! hdr) + return NS_ERROR_OUT_OF_MEMORY; + + BlobHashEntry *entry = static_cast(hdr); + + // N.B., we only hold a weak reference to the literal: that + // way, the literal can be destroyed when the last refcount + // goes away. The single addref that the CreateInt() call + // made will be owned by the callee. + entry->mBlob = aBlob; + + MOZ_LOG(gLog, LogLevel::Debug, + ("rdfserv register-blob [%p] %s", + aBlob, aBlob->mData.mBytes)); + + return NS_OK; +} + +nsresult +RDFServiceImpl::UnregisterBlob(BlobImpl *aBlob) +{ + NS_ASSERTION(mBlobs.Search(&aBlob->mData), "blob was never registered"); + + mBlobs.Remove(&aBlob->mData); + + // N.B. that we _don't_ release the literal: we only held a weak + // reference to it in the hashtable. + MOZ_LOG(gLog, LogLevel::Debug, + ("rdfserv unregister-blob [%p] %s", + aBlob, aBlob->mData.mBytes)); + + return NS_OK; +} diff --git a/rdf/base/nsRDFService.h b/rdf/base/nsRDFService.h new file mode 100644 index 000000000..0cc8c79ce --- /dev/null +++ b/rdf/base/nsRDFService.h @@ -0,0 +1,79 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * This Original Code has been modified by IBM Corporation. + * Modifications made by IBM described herein are + * Copyright (c) International Business Machines + * Corporation, 2000 + * + * Modifications to Mozilla code or documentation + * identified per MPL Section 3.3 + * + * Date Modified by Description of modification + * 03/27/2000 IBM Corp. Added PR_CALLBACK for Optlink + * use in OS2 + */ + +#ifndef nsRDFService_h__ +#define nsRDFService_h__ + +#include "nsIRDFService.h" +#include "nsWeakReference.h" +#include "nsIFactory.h" +#include "nsCOMPtr.h" +#include "PLDHashTable.h" +#include "nsString.h" + +struct PLHashTable; +class nsIRDFLiteral; +class nsIRDFInt; +class nsIRDFDate; +class BlobImpl; + +class RDFServiceImpl final : public nsIRDFService, + public nsSupportsWeakReference +{ +protected: + PLHashTable* mNamedDataSources; + PLDHashTable mResources; + PLDHashTable mLiterals; + PLDHashTable mInts; + PLDHashTable mDates; + PLDHashTable mBlobs; + + nsCString mLastURIPrefix; + nsCOMPtr mLastFactory; + nsCOMPtr mDefaultResourceFactory; + + RDFServiceImpl(); + nsresult Init(); + virtual ~RDFServiceImpl(); + +public: + static RDFServiceImpl *gRDFService NS_VISIBILITY_HIDDEN; + static nsresult CreateSingleton(nsISupports* aOuter, + const nsIID& aIID, void **aResult); + + // nsISupports + NS_DECL_ISUPPORTS + + // nsIRDFService + NS_DECL_NSIRDFSERVICE + + // Implementation methods + nsresult RegisterLiteral(nsIRDFLiteral* aLiteral); + nsresult UnregisterLiteral(nsIRDFLiteral* aLiteral); + nsresult RegisterInt(nsIRDFInt* aInt); + nsresult UnregisterInt(nsIRDFInt* aInt); + nsresult RegisterDate(nsIRDFDate* aDate); + nsresult UnregisterDate(nsIRDFDate* aDate); + nsresult RegisterBlob(BlobImpl* aBlob); + nsresult UnregisterBlob(BlobImpl* aBlob); + + nsresult GetDataSource(const char *aURI, bool aBlock, nsIRDFDataSource **aDataSource ); +}; + +#endif // nsRDFService_h__ diff --git a/rdf/base/nsRDFXMLDataSource.cpp b/rdf/base/nsRDFXMLDataSource.cpp new file mode 100644 index 000000000..0e9127420 --- /dev/null +++ b/rdf/base/nsRDFXMLDataSource.cpp @@ -0,0 +1,1179 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + + A data source that can read itself from and write itself to an + RDF/XML stream. + + For more information on the RDF/XML syntax, + see http://www.w3.org/TR/REC-rdf-syntax/. + + This code is based on the final W3C Recommendation, + http://www.w3.org/TR/1999/REC-rdf-syntax-19990222. + + + TO DO + ----- + + 1) Right now, the only kind of stream data sources that are _really_ + writable are "file:" URIs. (In fact, _all_ "file:" URIs are + writable, modulo file system permissions; this may lead to some + surprising behavior.) Eventually, it'd be great if we could open + an arbitrary nsIOutputStream on *any* URL, and Netlib could just + do the magic. + + 2) Implement a more terse output for "typed" nodes; that is, instead + of "RDF:Description type='ns:foo'", just output "ns:foo". + + 3) When re-serializing, we "cheat" for Descriptions that talk about + inline resources (i.e.., using the `ID' attribute specified in + [6.21]). Instead of writing an `ID="foo"' for the first instance, + and then `about="#foo"' for each subsequent instance, we just + _always_ write `about="#foo"'. + + We do this so that we can handle the case where an RDF container + has been assigned arbitrary properties: the spec says we can't + dangle the attributes directly off the container, so we need to + refer to it. Of course, with a little cleverness, we could fix + this. But who cares? + + 4) When re-serializing containers. We have to cheat on some + containers, and use an illegal "about=" construct. We do this to + handle containers that have been assigned URIs outside of the + local document. + + + Logging + ------- + + To turn on logging for this module, set + + MOZ_LOG=nsRDFXMLDataSource:5 + + */ + +#include "nsIFileStreams.h" +#include "nsIOutputStream.h" +#include "nsIFile.h" +#include "nsIFileChannel.h" +#include "nsIDTD.h" +#include "nsIRDFPurgeableDataSource.h" +#include "nsIInputStream.h" +#include "nsIOutputStream.h" +#include "nsIRDFContainerUtils.h" +#include "nsIRDFNode.h" +#include "nsIRDFRemoteDataSource.h" +#include "nsIRDFService.h" +#include "nsIRDFXMLParser.h" +#include "nsIRDFXMLSerializer.h" +#include "nsIRDFXMLSink.h" +#include "nsIRDFXMLSource.h" +#include "nsISafeOutputStream.h" +#include "nsIServiceManager.h" +#include "nsIStreamListener.h" +#include "nsIURL.h" +#include "nsIFileURL.h" +#include "nsISafeOutputStream.h" +#include "nsIChannel.h" +#include "nsRDFCID.h" +#include "nsRDFBaseDataSources.h" +#include "nsCOMArray.h" +#include "nsXPIDLString.h" +#include "plstr.h" +#include "prio.h" +#include "prthread.h" +#include "rdf.h" +#include "rdfutil.h" +#include "mozilla/Logging.h" +#include "nsNameSpaceMap.h" +#include "nsCRT.h" +#include "nsCycleCollectionParticipant.h" +#include "nsIScriptSecurityManager.h" +#include "nsIChannelEventSink.h" +#include "nsIAsyncVerifyRedirectCallback.h" +#include "nsNetUtil.h" +#include "nsIContentPolicy.h" +#include "nsContentUtils.h" + +#include "rdfIDataSource.h" + +//---------------------------------------------------------------------- +// +// RDFXMLDataSourceImpl +// + +class RDFXMLDataSourceImpl : public nsIRDFDataSource, + public nsIRDFRemoteDataSource, + public nsIRDFXMLSink, + public nsIRDFXMLSource, + public nsIStreamListener, + public rdfIDataSource, + public nsIInterfaceRequestor, + public nsIChannelEventSink +{ +protected: + enum LoadState { + eLoadState_Unloaded, + eLoadState_Pending, + eLoadState_Loading, + eLoadState_Loaded + }; + + nsCOMPtr mInner; + bool mIsWritable; // true if the document can be written back + bool mIsDirty; // true if the document should be written back + LoadState mLoadState; // what we're doing now + nsCOMArray mObservers; + nsCOMPtr mURL; + nsCOMPtr mListener; + nsNameSpaceMap mNameSpaces; + + // pseudo-constants + static int32_t gRefCnt; + static nsIRDFService* gRDFService; + + static mozilla::LazyLogModule gLog; + + nsresult Init(); + RDFXMLDataSourceImpl(void); + virtual ~RDFXMLDataSourceImpl(void); + nsresult rdfXMLFlush(nsIURI *aURI); + + friend nsresult + NS_NewRDFXMLDataSource(nsIRDFDataSource** aResult); + + inline bool IsLoading() { + return (mLoadState == eLoadState_Pending) || + (mLoadState == eLoadState_Loading); + } + +public: + // nsISupports + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(RDFXMLDataSourceImpl, + nsIRDFDataSource) + + // nsIRDFDataSource + NS_IMETHOD GetURI(char* *uri) override; + + NS_IMETHOD GetSource(nsIRDFResource* property, + nsIRDFNode* target, + bool tv, + nsIRDFResource** source) override { + return mInner->GetSource(property, target, tv, source); + } + + NS_IMETHOD GetSources(nsIRDFResource* property, + nsIRDFNode* target, + bool tv, + nsISimpleEnumerator** sources) override { + return mInner->GetSources(property, target, tv, sources); + } + + NS_IMETHOD GetTarget(nsIRDFResource* source, + nsIRDFResource* property, + bool tv, + nsIRDFNode** target) override { + return mInner->GetTarget(source, property, tv, target); + } + + NS_IMETHOD GetTargets(nsIRDFResource* source, + nsIRDFResource* property, + bool tv, + nsISimpleEnumerator** targets) override { + return mInner->GetTargets(source, property, tv, targets); + } + + NS_IMETHOD Assert(nsIRDFResource* aSource, + nsIRDFResource* aProperty, + nsIRDFNode* aTarget, + bool tv) override; + + NS_IMETHOD Unassert(nsIRDFResource* source, + nsIRDFResource* property, + nsIRDFNode* target) override; + + NS_IMETHOD Change(nsIRDFResource* aSource, + nsIRDFResource* aProperty, + nsIRDFNode* aOldTarget, + nsIRDFNode* aNewTarget) override; + + NS_IMETHOD Move(nsIRDFResource* aOldSource, + nsIRDFResource* aNewSource, + nsIRDFResource* aProperty, + nsIRDFNode* aTarget) override; + + NS_IMETHOD HasAssertion(nsIRDFResource* source, + nsIRDFResource* property, + nsIRDFNode* target, + bool tv, + bool* hasAssertion) override { + return mInner->HasAssertion(source, property, target, tv, hasAssertion); + } + + NS_IMETHOD AddObserver(nsIRDFObserver* aObserver) override { + return mInner->AddObserver(aObserver); + } + + NS_IMETHOD RemoveObserver(nsIRDFObserver* aObserver) override { + return mInner->RemoveObserver(aObserver); + } + + NS_IMETHOD HasArcIn(nsIRDFNode *aNode, nsIRDFResource *aArc, bool *_retval) override { + return mInner->HasArcIn(aNode, aArc, _retval); + } + + NS_IMETHOD HasArcOut(nsIRDFResource *aSource, nsIRDFResource *aArc, bool *_retval) override { + return mInner->HasArcOut(aSource, aArc, _retval); + } + + NS_IMETHOD ArcLabelsIn(nsIRDFNode* node, + nsISimpleEnumerator** labels) override { + return mInner->ArcLabelsIn(node, labels); + } + + NS_IMETHOD ArcLabelsOut(nsIRDFResource* source, + nsISimpleEnumerator** labels) override { + return mInner->ArcLabelsOut(source, labels); + } + + NS_IMETHOD GetAllResources(nsISimpleEnumerator** aResult) override { + return mInner->GetAllResources(aResult); + } + + NS_IMETHOD GetAllCmds(nsIRDFResource* source, + nsISimpleEnumerator/**/** commands) override { + return mInner->GetAllCmds(source, commands); + } + + NS_IMETHOD IsCommandEnabled(nsISupports* aSources, + nsIRDFResource* aCommand, + nsISupports* aArguments, + bool* aResult) override { + return NS_ERROR_NOT_IMPLEMENTED; + } + + NS_IMETHOD DoCommand(nsISupports* aSources, + nsIRDFResource* aCommand, + nsISupports* aArguments) override { + return NS_ERROR_NOT_IMPLEMENTED; + } + + NS_IMETHOD BeginUpdateBatch() override { + return mInner->BeginUpdateBatch(); + } + + NS_IMETHOD EndUpdateBatch() override { + return mInner->EndUpdateBatch(); + } + + // nsIRDFRemoteDataSource interface + NS_DECL_NSIRDFREMOTEDATASOURCE + + // nsIRDFXMLSink interface + NS_DECL_NSIRDFXMLSINK + + // nsIRDFXMLSource interface + NS_DECL_NSIRDFXMLSOURCE + + // nsIRequestObserver + NS_DECL_NSIREQUESTOBSERVER + + // nsIStreamListener + NS_DECL_NSISTREAMLISTENER + + // nsIInterfaceRequestor + NS_DECL_NSIINTERFACEREQUESTOR + + // nsIChannelEventSink + NS_DECL_NSICHANNELEVENTSINK + + // rdfIDataSource + NS_IMETHOD VisitAllSubjects(rdfITripleVisitor *aVisitor) override { + nsresult rv; + nsCOMPtr rdfds = do_QueryInterface(mInner, &rv); + if (NS_FAILED(rv)) return rv; + return rdfds->VisitAllSubjects(aVisitor); + } + + NS_IMETHOD VisitAllTriples(rdfITripleVisitor *aVisitor) override { + nsresult rv; + nsCOMPtr rdfds = do_QueryInterface(mInner, &rv); + if (NS_FAILED(rv)) return rv; + return rdfds->VisitAllTriples(aVisitor); + } + + // Implementation methods + bool + MakeQName(nsIRDFResource* aResource, + nsString& property, + nsString& nameSpacePrefix, + nsString& nameSpaceURI); + + nsresult + SerializeAssertion(nsIOutputStream* aStream, + nsIRDFResource* aResource, + nsIRDFResource* aProperty, + nsIRDFNode* aValue); + + nsresult + SerializeProperty(nsIOutputStream* aStream, + nsIRDFResource* aResource, + nsIRDFResource* aProperty); + + bool + IsContainerProperty(nsIRDFResource* aProperty); + + nsresult + SerializeDescription(nsIOutputStream* aStream, + nsIRDFResource* aResource); + + nsresult + SerializeMember(nsIOutputStream* aStream, + nsIRDFResource* aContainer, + nsIRDFNode* aMember); + + nsresult + SerializeContainer(nsIOutputStream* aStream, + nsIRDFResource* aContainer); + + nsresult + SerializePrologue(nsIOutputStream* aStream); + + nsresult + SerializeEpilogue(nsIOutputStream* aStream); + + bool + IsA(nsIRDFDataSource* aDataSource, nsIRDFResource* aResource, nsIRDFResource* aType); + +protected: + nsresult + BlockingParse(nsIURI* aURL, nsIStreamListener* aConsumer); +}; + +int32_t RDFXMLDataSourceImpl::gRefCnt = 0; +nsIRDFService* RDFXMLDataSourceImpl::gRDFService; + +mozilla::LazyLogModule RDFXMLDataSourceImpl::gLog("nsRDFXMLDataSource"); + +static const char kFileURIPrefix[] = "file:"; +static const char kResourceURIPrefix[] = "resource:"; + + +//---------------------------------------------------------------------- + +nsresult +NS_NewRDFXMLDataSource(nsIRDFDataSource** aResult) +{ + NS_PRECONDITION(aResult != nullptr, "null ptr"); + if (! aResult) + return NS_ERROR_NULL_POINTER; + + RDFXMLDataSourceImpl* datasource = new RDFXMLDataSourceImpl(); + if (! datasource) + return NS_ERROR_OUT_OF_MEMORY; + + nsresult rv; + rv = datasource->Init(); + + if (NS_FAILED(rv)) { + delete datasource; + return rv; + } + + NS_ADDREF(datasource); + *aResult = datasource; + return NS_OK; +} + + +RDFXMLDataSourceImpl::RDFXMLDataSourceImpl(void) + : mIsWritable(true), + mIsDirty(false), + mLoadState(eLoadState_Unloaded) +{ +} + + +nsresult +RDFXMLDataSourceImpl::Init() +{ + nsresult rv; + NS_DEFINE_CID(kRDFInMemoryDataSourceCID, NS_RDFINMEMORYDATASOURCE_CID); + mInner = do_CreateInstance(kRDFInMemoryDataSourceCID, &rv); + if (NS_FAILED(rv)) return rv; + + if (gRefCnt++ == 0) { + NS_DEFINE_CID(kRDFServiceCID, NS_RDFSERVICE_CID); + rv = CallGetService(kRDFServiceCID, &gRDFService); + + NS_ASSERTION(NS_SUCCEEDED(rv), "unable to get RDF service"); + if (NS_FAILED(rv)) return rv; + } + + return NS_OK; +} + + +RDFXMLDataSourceImpl::~RDFXMLDataSourceImpl(void) +{ + // Unregister first so that nobody else tries to get us. + (void) gRDFService->UnregisterDataSource(this); + + // Now flush contents + (void) Flush(); + + // Release RDF/XML sink observers + mObservers.Clear(); + + if (--gRefCnt == 0) + NS_IF_RELEASE(gRDFService); +} + +NS_IMPL_CYCLE_COLLECTION_CLASS(RDFXMLDataSourceImpl) + +NS_IMPL_CYCLE_COLLECTION_UNLINK_0(RDFXMLDataSourceImpl) +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(RDFXMLDataSourceImpl) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mInner) +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +NS_IMPL_CYCLE_COLLECTING_ADDREF(RDFXMLDataSourceImpl) +NS_IMPL_CYCLE_COLLECTING_RELEASE(RDFXMLDataSourceImpl) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(RDFXMLDataSourceImpl) + NS_INTERFACE_MAP_ENTRY(nsIRDFDataSource) + NS_INTERFACE_MAP_ENTRY(nsIRDFRemoteDataSource) + NS_INTERFACE_MAP_ENTRY(nsIRDFXMLSink) + NS_INTERFACE_MAP_ENTRY(nsIRDFXMLSource) + NS_INTERFACE_MAP_ENTRY(nsIRequestObserver) + NS_INTERFACE_MAP_ENTRY(nsIStreamListener) + NS_INTERFACE_MAP_ENTRY(rdfIDataSource) + NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor) + NS_INTERFACE_MAP_ENTRY(nsIChannelEventSink) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIRDFDataSource) +NS_INTERFACE_MAP_END + +// nsIInterfaceRequestor +NS_IMETHODIMP +RDFXMLDataSourceImpl::GetInterface(const nsIID& aIID, void** aSink) +{ + return QueryInterface(aIID, aSink); +} + +nsresult +RDFXMLDataSourceImpl::BlockingParse(nsIURI* aURL, nsIStreamListener* aConsumer) +{ + nsresult rv; + + // XXX I really hate the way that we're spoon-feeding this stuff + // to the parser: it seems like this is something that netlib + // should be able to do by itself. + + nsCOMPtr channel; + + // Null LoadGroup ? + rv = NS_NewChannel(getter_AddRefs(channel), + aURL, + nsContentUtils::GetSystemPrincipal(), + nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL, + nsIContentPolicy::TYPE_OTHER); + + if (NS_FAILED(rv)) return rv; + nsCOMPtr in; + rv = channel->Open2(getter_AddRefs(in)); + + // Report success if the file doesn't exist, but propagate other errors. + if (rv == NS_ERROR_FILE_NOT_FOUND) return NS_OK; + if (NS_FAILED(rv)) return rv; + + if (! in) { + NS_ERROR("no input stream"); + return NS_ERROR_FAILURE; + } + + // Wrap the channel's input stream in a buffered stream to ensure that + // ReadSegments is implemented (which OnDataAvailable expects). + nsCOMPtr bufStream; + rv = NS_NewBufferedInputStream(getter_AddRefs(bufStream), in, + 4096 /* buffer size */); + if (NS_FAILED(rv)) return rv; + + // Notify load observers + int32_t i; + for (i = mObservers.Count() - 1; i >= 0; --i) { + // Make sure to hold a strong reference to the observer so + // that it doesn't go away in this call if it removes itself + // as an observer + nsCOMPtr obs = mObservers[i]; + + if (obs) { + obs->OnBeginLoad(this); + } + } + + rv = aConsumer->OnStartRequest(channel, nullptr); + + uint64_t offset = 0; + while (NS_SUCCEEDED(rv)) { + // Skip ODA if the channel is canceled + channel->GetStatus(&rv); + if (NS_FAILED(rv)) + break; + + uint64_t avail; + if (NS_FAILED(rv = bufStream->Available(&avail))) + break; // error + + if (avail == 0) + break; // eof + + if (avail > UINT32_MAX) + avail = UINT32_MAX; + + rv = aConsumer->OnDataAvailable(channel, nullptr, bufStream, offset, (uint32_t)avail); + if (NS_SUCCEEDED(rv)) + offset += avail; + } + + if (NS_FAILED(rv)) + channel->Cancel(rv); + + channel->GetStatus(&rv); + aConsumer->OnStopRequest(channel, nullptr, rv); + + // Notify load observers + for (i = mObservers.Count() - 1; i >= 0; --i) { + // Make sure to hold a strong reference to the observer so + // that it doesn't go away in this call if it removes itself + // as an observer + nsCOMPtr obs = mObservers[i]; + + if (obs) { + if (NS_FAILED(rv)) + obs->OnError(this, rv, nullptr); + + obs->OnEndLoad(this); + } + } + + return rv; +} + +NS_IMETHODIMP +RDFXMLDataSourceImpl::GetLoaded(bool* _result) +{ + *_result = (mLoadState == eLoadState_Loaded); + return NS_OK; +} + +NS_IMETHODIMP +RDFXMLDataSourceImpl::Init(const char* uri) +{ + NS_PRECONDITION(mInner != nullptr, "not initialized"); + if (! mInner) + return NS_ERROR_OUT_OF_MEMORY; + + nsresult rv; + + rv = NS_NewURI(getter_AddRefs(mURL), nsDependentCString(uri)); + if (NS_FAILED(rv)) return rv; + + // XXX this is a hack: any "file:" URI is considered writable. All + // others are considered read-only. + if ((PL_strncmp(uri, kFileURIPrefix, sizeof(kFileURIPrefix) - 1) != 0) && + (PL_strncmp(uri, kResourceURIPrefix, sizeof(kResourceURIPrefix) - 1) != 0)) { + mIsWritable = false; + } + + rv = gRDFService->RegisterDataSource(this, false); + if (NS_FAILED(rv)) return rv; + + return NS_OK; +} + + +NS_IMETHODIMP +RDFXMLDataSourceImpl::GetURI(char* *aURI) +{ + *aURI = nullptr; + if (!mURL) { + return NS_OK; + } + + nsAutoCString spec; + nsresult rv = mURL->GetSpec(spec); + NS_ENSURE_SUCCESS(rv, rv); + *aURI = ToNewCString(spec); + if (!*aURI) { + return NS_ERROR_OUT_OF_MEMORY; + } + + return NS_OK; +} + +NS_IMETHODIMP +RDFXMLDataSourceImpl::Assert(nsIRDFResource* aSource, + nsIRDFResource* aProperty, + nsIRDFNode* aTarget, + bool aTruthValue) +{ + // We don't accept assertions unless we're writable (except in the + // case that we're actually _reading_ the datasource in). + nsresult rv; + + if (IsLoading()) { + bool hasAssertion = false; + + nsCOMPtr gcable = do_QueryInterface(mInner); + if (gcable) { + rv = gcable->Mark(aSource, aProperty, aTarget, aTruthValue, &hasAssertion); + if (NS_FAILED(rv)) return rv; + } + + rv = NS_RDF_ASSERTION_ACCEPTED; + + if (! hasAssertion) { + rv = mInner->Assert(aSource, aProperty, aTarget, aTruthValue); + + if (NS_SUCCEEDED(rv) && gcable) { + // Now mark the new assertion, so it doesn't get + // removed when we sweep. Ignore rv, because we want + // to return what mInner->Assert() gave us. + bool didMark; + (void) gcable->Mark(aSource, aProperty, aTarget, aTruthValue, &didMark); + } + + if (NS_FAILED(rv)) return rv; + } + + return rv; + } + else if (mIsWritable) { + rv = mInner->Assert(aSource, aProperty, aTarget, aTruthValue); + + if (rv == NS_RDF_ASSERTION_ACCEPTED) + mIsDirty = true; + + return rv; + } + else { + return NS_RDF_ASSERTION_REJECTED; + } +} + + +NS_IMETHODIMP +RDFXMLDataSourceImpl::Unassert(nsIRDFResource* source, + nsIRDFResource* property, + nsIRDFNode* target) +{ + // We don't accept assertions unless we're writable (except in the + // case that we're actually _reading_ the datasource in). + nsresult rv; + + if (IsLoading() || mIsWritable) { + rv = mInner->Unassert(source, property, target); + if (!IsLoading() && rv == NS_RDF_ASSERTION_ACCEPTED) + mIsDirty = true; + } + else { + rv = NS_RDF_ASSERTION_REJECTED; + } + + return rv; +} + +NS_IMETHODIMP +RDFXMLDataSourceImpl::Change(nsIRDFResource* aSource, + nsIRDFResource* aProperty, + nsIRDFNode* aOldTarget, + nsIRDFNode* aNewTarget) +{ + nsresult rv; + + if (IsLoading() || mIsWritable) { + rv = mInner->Change(aSource, aProperty, aOldTarget, aNewTarget); + + if (!IsLoading() && rv == NS_RDF_ASSERTION_ACCEPTED) + mIsDirty = true; + } + else { + rv = NS_RDF_ASSERTION_REJECTED; + } + + return rv; +} + +NS_IMETHODIMP +RDFXMLDataSourceImpl::Move(nsIRDFResource* aOldSource, + nsIRDFResource* aNewSource, + nsIRDFResource* aProperty, + nsIRDFNode* aTarget) +{ + nsresult rv; + + if (IsLoading() || mIsWritable) { + rv = mInner->Move(aOldSource, aNewSource, aProperty, aTarget); + if (!IsLoading() && rv == NS_RDF_ASSERTION_ACCEPTED) + mIsDirty = true; + } + else { + rv = NS_RDF_ASSERTION_REJECTED; + } + + return rv; +} + + +nsresult +RDFXMLDataSourceImpl::rdfXMLFlush(nsIURI *aURI) +{ + + nsresult rv; + + { + // Quick and dirty check to see if we're in XPCOM shutdown. If + // we are, we're screwed: it's too late to serialize because + // many of the services that we'll need to acquire to properly + // write the file will be unaquirable. + NS_DEFINE_CID(kRDFServiceCID, NS_RDFSERVICE_CID); + nsCOMPtr dummy = do_GetService(kRDFServiceCID, &rv); + if (NS_FAILED(rv)) { + NS_WARNING("unable to Flush() dirty datasource during XPCOM shutdown"); + return rv; + } + } + + // Is it a file? If so, we can write to it. Some day, it'd be nice + // if we didn't care what kind of stream this was... + nsCOMPtr fileURL = do_QueryInterface(aURI); + + if (fileURL) { + nsCOMPtr file; + fileURL->GetFile(getter_AddRefs(file)); + if (file) { + // get a safe output stream, so we don't clobber the datasource file unless + // all the writes succeeded. + nsCOMPtr out; + rv = NS_NewSafeLocalFileOutputStream(getter_AddRefs(out), + file, + PR_WRONLY | PR_CREATE_FILE, + /*octal*/ 0666, + 0); + if (NS_FAILED(rv)) return rv; + + nsCOMPtr bufferedOut; + rv = NS_NewBufferedOutputStream(getter_AddRefs(bufferedOut), out, 4096); + if (NS_FAILED(rv)) return rv; + + rv = Serialize(bufferedOut); + if (NS_FAILED(rv)) return rv; + + // All went ok. Maybe except for problems in Write(), but the stream detects + // that for us + nsCOMPtr safeStream = do_QueryInterface(bufferedOut, &rv); + if (NS_FAILED(rv)) return rv; + + rv = safeStream->Finish(); + if (NS_FAILED(rv)) { + NS_WARNING("failed to save datasource file! possible dataloss"); + return rv; + } + } + } + + return NS_OK; +} + + +NS_IMETHODIMP +RDFXMLDataSourceImpl::FlushTo(const char *aURI) +{ + NS_PRECONDITION(aURI != nullptr, "not initialized"); + if (!aURI) + return NS_ERROR_NULL_POINTER; + + // XXX this is a hack: any "file:" URI is considered writable. All + // others are considered read-only. + if ((PL_strncmp(aURI, kFileURIPrefix, sizeof(kFileURIPrefix) - 1) != 0) && + (PL_strncmp(aURI, kResourceURIPrefix, sizeof(kResourceURIPrefix) - 1) != 0)) + { + return NS_ERROR_ILLEGAL_VALUE; + } + + nsCOMPtr url; + nsresult rv = NS_NewURI(getter_AddRefs(url), aURI); + if (NS_FAILED(rv)) + return rv; + rv = rdfXMLFlush(url); + return rv; +} + + +NS_IMETHODIMP +RDFXMLDataSourceImpl::Flush(void) +{ + if (!mIsWritable || !mIsDirty) + return NS_OK; + + // while it is not fatal if mURL is not set, + // indicate failure since we can't flush back to an unknown origin + if (! mURL) + return NS_ERROR_NOT_INITIALIZED; + + if (MOZ_LOG_TEST(gLog, LogLevel::Debug)) { + MOZ_LOG(gLog, LogLevel::Debug, + ("rdfxml[%p] flush(%s)", this, mURL->GetSpecOrDefault().get())); + } + + nsresult rv; + if (NS_SUCCEEDED(rv = rdfXMLFlush(mURL))) + { + mIsDirty = false; + } + return rv; +} + + +//---------------------------------------------------------------------- +// +// nsIRDFXMLDataSource methods +// + +NS_IMETHODIMP +RDFXMLDataSourceImpl::GetReadOnly(bool* aIsReadOnly) +{ + *aIsReadOnly = !mIsWritable; + return NS_OK; +} + + +NS_IMETHODIMP +RDFXMLDataSourceImpl::SetReadOnly(bool aIsReadOnly) +{ + if (mIsWritable && aIsReadOnly) + mIsWritable = false; + + return NS_OK; +} + +// nsIChannelEventSink + +// This code is copied from nsSameOriginChecker::OnChannelRedirect. See +// bug 475940 on providing this code in a shared location. +NS_IMETHODIMP +RDFXMLDataSourceImpl::AsyncOnChannelRedirect(nsIChannel *aOldChannel, + nsIChannel *aNewChannel, + uint32_t aFlags, + nsIAsyncVerifyRedirectCallback *cb) +{ + NS_PRECONDITION(aNewChannel, "Redirecting to null channel?"); + + nsresult rv; + nsCOMPtr secMan = + do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr oldPrincipal; + secMan->GetChannelResultPrincipal(aOldChannel, getter_AddRefs(oldPrincipal)); + + nsCOMPtr newURI; + aNewChannel->GetURI(getter_AddRefs(newURI)); + nsCOMPtr newOriginalURI; + aNewChannel->GetOriginalURI(getter_AddRefs(newOriginalURI)); + + NS_ENSURE_STATE(oldPrincipal && newURI && newOriginalURI); + + rv = oldPrincipal->CheckMayLoad(newURI, false, false); + if (NS_SUCCEEDED(rv) && newOriginalURI != newURI) { + rv = oldPrincipal->CheckMayLoad(newOriginalURI, false, false); + } + + if (NS_FAILED(rv)) + return rv; + + cb->OnRedirectVerifyCallback(NS_OK); + return NS_OK; +} + +NS_IMETHODIMP +RDFXMLDataSourceImpl::Refresh(bool aBlocking) +{ + nsAutoCString spec; + if (mURL) { + spec = mURL->GetSpecOrDefault(); + } + MOZ_LOG(gLog, LogLevel::Debug, + ("rdfxml[%p] refresh(%s) %sblocking", this, spec.get(), (aBlocking ? "" : "non"))); + + // If an asynchronous load is already pending, then just let it do + // the honors. + if (IsLoading()) { + MOZ_LOG(gLog, LogLevel::Debug, + ("rdfxml[%p] refresh(%s) a load was pending", this, spec.get())); + + if (aBlocking) { + NS_WARNING("blocking load requested when async load pending"); + return NS_ERROR_FAILURE; + } + else { + return NS_OK; + } + } + + if (! mURL) + return NS_ERROR_FAILURE; + nsCOMPtr parser = do_CreateInstance("@mozilla.org/rdf/xml-parser;1"); + if (! parser) + return NS_ERROR_FAILURE; + + nsresult rv = parser->ParseAsync(this, mURL, getter_AddRefs(mListener)); + if (NS_FAILED(rv)) return rv; + + if (aBlocking) { + rv = BlockingParse(mURL, this); + + mListener = nullptr; // release the parser + + if (NS_FAILED(rv)) return rv; + } + else { + // Null LoadGroup ? + nsCOMPtr channel; + rv = NS_NewChannel(getter_AddRefs(channel), + mURL, + nsContentUtils::GetSystemPrincipal(), + nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL, + nsIContentPolicy::TYPE_OTHER, + nullptr, // aLoadGroup + this); // aCallbacks + NS_ENSURE_SUCCESS(rv, rv); + rv = channel->AsyncOpen2(this); + NS_ENSURE_SUCCESS(rv, rv); + + // So we don't try to issue two asynchronous loads at once. + mLoadState = eLoadState_Pending; + } + + return NS_OK; +} + +NS_IMETHODIMP +RDFXMLDataSourceImpl::BeginLoad(void) +{ + if (MOZ_LOG_TEST(gLog, LogLevel::Debug)) { + MOZ_LOG(gLog, LogLevel::Debug, + ("rdfxml[%p] begin-load(%s)", this, + mURL ? mURL->GetSpecOrDefault().get() : "")); + } + + mLoadState = eLoadState_Loading; + for (int32_t i = mObservers.Count() - 1; i >= 0; --i) { + // Make sure to hold a strong reference to the observer so + // that it doesn't go away in this call if it removes itself + // as an observer + nsCOMPtr obs = mObservers[i]; + + if (obs) { + obs->OnBeginLoad(this); + } + } + return NS_OK; +} + +NS_IMETHODIMP +RDFXMLDataSourceImpl::Interrupt(void) +{ + if (MOZ_LOG_TEST(gLog, LogLevel::Debug)) { + MOZ_LOG(gLog, LogLevel::Debug, + ("rdfxml[%p] interrupt(%s)", this, + mURL ? mURL->GetSpecOrDefault().get() : "")); + } + + for (int32_t i = mObservers.Count() - 1; i >= 0; --i) { + // Make sure to hold a strong reference to the observer so + // that it doesn't go away in this call if it removes itself + // as an observer + nsCOMPtr obs = mObservers[i]; + + if (obs) { + obs->OnInterrupt(this); + } + } + return NS_OK; +} + +NS_IMETHODIMP +RDFXMLDataSourceImpl::Resume(void) +{ + if (MOZ_LOG_TEST(gLog, LogLevel::Debug)) { + MOZ_LOG(gLog, LogLevel::Debug, + ("rdfxml[%p] resume(%s)", this, + mURL ? mURL->GetSpecOrDefault().get() : "")); + } + + for (int32_t i = mObservers.Count() - 1; i >= 0; --i) { + // Make sure to hold a strong reference to the observer so + // that it doesn't go away in this call if it removes itself + // as an observer + nsCOMPtr obs = mObservers[i]; + + if (obs) { + obs->OnResume(this); + } + } + return NS_OK; +} + +NS_IMETHODIMP +RDFXMLDataSourceImpl::EndLoad(void) +{ + if (MOZ_LOG_TEST(gLog, LogLevel::Debug)) { + MOZ_LOG(gLog, LogLevel::Debug, + ("rdfxml[%p] end-load(%s)", this, + mURL ? mURL->GetSpecOrDefault().get() : "")); + } + + mLoadState = eLoadState_Loaded; + + // Clear out any unmarked assertions from the datasource. + nsCOMPtr gcable = do_QueryInterface(mInner); + if (gcable) { + gcable->Sweep(); + } + + // Notify load observers + for (int32_t i = mObservers.Count() - 1; i >= 0; --i) { + // Make sure to hold a strong reference to the observer so + // that it doesn't go away in this call if it removes itself + // as an observer + nsCOMPtr obs = mObservers[i]; + + if (obs) { + obs->OnEndLoad(this); + } + } + return NS_OK; +} + +NS_IMETHODIMP +RDFXMLDataSourceImpl::AddNameSpace(nsIAtom* aPrefix, const nsString& aURI) +{ + mNameSpaces.Put(aURI, aPrefix); + return NS_OK; +} + + +NS_IMETHODIMP +RDFXMLDataSourceImpl::AddXMLSinkObserver(nsIRDFXMLSinkObserver* aObserver) +{ + if (! aObserver) + return NS_ERROR_NULL_POINTER; + + mObservers.AppendObject(aObserver); + return NS_OK; +} + +NS_IMETHODIMP +RDFXMLDataSourceImpl::RemoveXMLSinkObserver(nsIRDFXMLSinkObserver* aObserver) +{ + if (! aObserver) + return NS_ERROR_NULL_POINTER; + + mObservers.RemoveObject(aObserver); + + return NS_OK; +} + + +//---------------------------------------------------------------------- +// +// nsIRequestObserver +// + +NS_IMETHODIMP +RDFXMLDataSourceImpl::OnStartRequest(nsIRequest *request, nsISupports *ctxt) +{ + return mListener->OnStartRequest(request, ctxt); +} + +NS_IMETHODIMP +RDFXMLDataSourceImpl::OnStopRequest(nsIRequest *request, + nsISupports *ctxt, + nsresult status) +{ + if (NS_FAILED(status)) { + for (int32_t i = mObservers.Count() - 1; i >= 0; --i) { + // Make sure to hold a strong reference to the observer so + // that it doesn't go away in this call if it removes + // itself as an observer + nsCOMPtr obs = mObservers[i]; + + if (obs) { + obs->OnError(this, status, nullptr); + } + } + } + + nsresult rv; + rv = mListener->OnStopRequest(request, ctxt, status); + + mListener = nullptr; // release the parser + + return rv; +} + +//---------------------------------------------------------------------- +// +// nsIStreamListener +// + +NS_IMETHODIMP +RDFXMLDataSourceImpl::OnDataAvailable(nsIRequest *request, + nsISupports *ctxt, + nsIInputStream *inStr, + uint64_t sourceOffset, + uint32_t count) +{ + return mListener->OnDataAvailable(request, ctxt, inStr, sourceOffset, count); +} + +//---------------------------------------------------------------------- +// +// nsIRDFXMLSource +// + +NS_IMETHODIMP +RDFXMLDataSourceImpl::Serialize(nsIOutputStream* aStream) +{ + nsresult rv; + nsCOMPtr serializer + = do_CreateInstance("@mozilla.org/rdf/xml-serializer;1", &rv); + + if (! serializer) + return rv; + + rv = serializer->Init(this); + if (NS_FAILED(rv)) return rv; + + // Add any namespace information that we picked up when reading + // the RDF/XML + nsNameSpaceMap::const_iterator last = mNameSpaces.last(); + for (nsNameSpaceMap::const_iterator iter = mNameSpaces.first(); + iter != last; ++iter) { + // We might wanna change nsIRDFXMLSerializer to nsACString and + // use a heap allocated buffer here in the future. + NS_ConvertUTF8toUTF16 uri(iter->mURI); + serializer->AddNameSpace(iter->mPrefix, uri); + } + + // Serialize! + nsCOMPtr source = do_QueryInterface(serializer); + if (! source) + return NS_ERROR_FAILURE; + + return source->Serialize(aStream); +} diff --git a/rdf/base/nsRDFXMLParser.cpp b/rdf/base/nsRDFXMLParser.cpp new file mode 100644 index 000000000..67de90820 --- /dev/null +++ b/rdf/base/nsRDFXMLParser.cpp @@ -0,0 +1,137 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsRDFXMLParser.h" + +#include "nsIComponentManager.h" +#include "nsIParser.h" +#include "nsCharsetSource.h" +#include "nsIRDFContentSink.h" +#include "nsParserCIID.h" +#include "nsStringStream.h" +#include "nsNetUtil.h" +#include "nsNullPrincipal.h" + +static NS_DEFINE_CID(kParserCID, NS_PARSER_CID); + +NS_IMPL_ISUPPORTS(nsRDFXMLParser, nsIRDFXMLParser) + +nsresult +nsRDFXMLParser::Create(nsISupports* aOuter, REFNSIID aIID, void** aResult) +{ + if (aOuter) + return NS_ERROR_NO_AGGREGATION; + + nsRDFXMLParser* result = new nsRDFXMLParser(); + if (! result) + return NS_ERROR_OUT_OF_MEMORY; + + nsresult rv; + NS_ADDREF(result); + rv = result->QueryInterface(aIID, aResult); + NS_RELEASE(result); + return rv; +} + +nsRDFXMLParser::nsRDFXMLParser() +{ + MOZ_COUNT_CTOR(nsRDFXMLParser); +} + +nsRDFXMLParser::~nsRDFXMLParser() +{ + MOZ_COUNT_DTOR(nsRDFXMLParser); +} + +NS_IMETHODIMP +nsRDFXMLParser::ParseAsync(nsIRDFDataSource* aSink, nsIURI* aBaseURI, nsIStreamListener** aResult) +{ + nsresult rv; + + nsCOMPtr sink = + do_CreateInstance("@mozilla.org/rdf/content-sink;1", &rv); + + if (NS_FAILED(rv)) return rv; + + rv = sink->Init(aBaseURI); + if (NS_FAILED(rv)) return rv; + + // We set the content sink's data source directly to our in-memory + // store. This allows the initial content to be generated "directly". + rv = sink->SetDataSource(aSink); + if (NS_FAILED(rv)) return rv; + + nsCOMPtr parser = do_CreateInstance(kParserCID, &rv); + if (NS_FAILED(rv)) return rv; + + parser->SetDocumentCharset(NS_LITERAL_CSTRING("UTF-8"), + kCharsetFromDocTypeDefault); + parser->SetContentSink(sink); + + rv = parser->Parse(aBaseURI); + if (NS_FAILED(rv)) return rv; + + return CallQueryInterface(parser, aResult); +} + +NS_IMETHODIMP +nsRDFXMLParser::ParseString(nsIRDFDataSource* aSink, nsIURI* aBaseURI, const nsACString& aString) +{ + nsresult rv; + + nsCOMPtr sink = + do_CreateInstance("@mozilla.org/rdf/content-sink;1", &rv); + + if (NS_FAILED(rv)) return rv; + + rv = sink->Init(aBaseURI); + if (NS_FAILED(rv)) return rv; + + // We set the content sink's data source directly to our in-memory + // store. This allows the initial content to be generated "directly". + rv = sink->SetDataSource(aSink); + if (NS_FAILED(rv)) return rv; + + nsCOMPtr parser = do_CreateInstance(kParserCID, &rv); + if (NS_FAILED(rv)) return rv; + + parser->SetDocumentCharset(NS_LITERAL_CSTRING("UTF-8"), + kCharsetFromOtherComponent); + parser->SetContentSink(sink); + + rv = parser->Parse(aBaseURI); + if (NS_FAILED(rv)) return rv; + + nsCOMPtr listener = + do_QueryInterface(parser); + + if (! listener) + return NS_ERROR_FAILURE; + + nsCOMPtr stream; + rv = NS_NewCStringInputStream(getter_AddRefs(stream), aString); + if (NS_FAILED(rv)) return rv; + + nsCOMPtr nullPrincipal = nsNullPrincipal::Create(); + + // The following channel is never openend, so it does not matter what + // securityFlags we pass; let's follow the principle of least privilege. + nsCOMPtr channel; + rv = NS_NewInputStreamChannel(getter_AddRefs(channel), + aBaseURI, + stream, + nullPrincipal, + nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_IS_BLOCKED, + nsIContentPolicy::TYPE_OTHER, + NS_LITERAL_CSTRING("text/xml")); + if (NS_FAILED(rv)) return rv; + + listener->OnStartRequest(channel, nullptr); + listener->OnDataAvailable(channel, nullptr, stream, 0, aString.Length()); + listener->OnStopRequest(channel, nullptr, NS_OK); + + return NS_OK; +} diff --git a/rdf/base/nsRDFXMLParser.h b/rdf/base/nsRDFXMLParser.h new file mode 100644 index 000000000..d35c873fb --- /dev/null +++ b/rdf/base/nsRDFXMLParser.h @@ -0,0 +1,31 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsRDFParser_h__ +#define nsRDFParser_h__ + +#include "nsCOMPtr.h" +#include "nsString.h" +#include "nsIRDFXMLParser.h" +#include "nsIRDFDataSource.h" + +/** + * A helper class that is used to parse RDF/XML. + */ +class nsRDFXMLParser : public nsIRDFXMLParser { +public: + static nsresult + Create(nsISupports* aOuter, REFNSIID aIID, void** aResult); + + NS_DECL_ISUPPORTS + NS_DECL_NSIRDFXMLPARSER + +protected: + nsRDFXMLParser(); + virtual ~nsRDFXMLParser(); +}; + +#endif // nsRDFParser_h__ diff --git a/rdf/base/nsRDFXMLSerializer.cpp b/rdf/base/nsRDFXMLSerializer.cpp new file mode 100644 index 000000000..677f58ca3 --- /dev/null +++ b/rdf/base/nsRDFXMLSerializer.cpp @@ -0,0 +1,1127 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * vim: set ts=4 sw=4 et tw=80: + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsRDFXMLSerializer.h" + +#include "nsIAtom.h" +#include "nsIOutputStream.h" +#include "nsIRDFService.h" +#include "nsIRDFContainerUtils.h" +#include "nsIServiceManager.h" +#include "nsString.h" +#include "nsXPIDLString.h" +#include "nsTArray.h" +#include "rdf.h" +#include "rdfutil.h" +#include "mozilla/Attributes.h" + +#include "rdfIDataSource.h" + +int32_t nsRDFXMLSerializer::gRefCnt = 0; +nsIRDFContainerUtils* nsRDFXMLSerializer::gRDFC; +nsIRDFResource* nsRDFXMLSerializer::kRDF_instanceOf; +nsIRDFResource* nsRDFXMLSerializer::kRDF_type; +nsIRDFResource* nsRDFXMLSerializer::kRDF_nextVal; +nsIRDFResource* nsRDFXMLSerializer::kRDF_Bag; +nsIRDFResource* nsRDFXMLSerializer::kRDF_Seq; +nsIRDFResource* nsRDFXMLSerializer::kRDF_Alt; + +static const char kRDFDescriptionOpen[] = " \n"; +static const char kRDFParseTypeInteger[] = " NC:parseType=\"Integer\">"; +static const char kRDFParseTypeDate[] = " NC:parseType=\"Date\">"; +static const char kRDFUnknown[] = ">"; + +nsresult +nsRDFXMLSerializer::Create(nsISupports* aOuter, REFNSIID aIID, void** aResult) +{ + if (aOuter) + return NS_ERROR_NO_AGGREGATION; + + nsCOMPtr result = new nsRDFXMLSerializer(); + if (! result) + return NS_ERROR_OUT_OF_MEMORY; + // The serializer object is here, addref gRefCnt so that the + // destructor can safely release it. + gRefCnt++; + + nsresult rv; + rv = result->QueryInterface(aIID, aResult); + + if (NS_FAILED(rv)) return rv; + + if (gRefCnt == 1) do { + nsCOMPtr rdf = do_GetService("@mozilla.org/rdf/rdf-service;1", &rv); + if (NS_FAILED(rv)) break; + + rv = rdf->GetResource(NS_LITERAL_CSTRING(RDF_NAMESPACE_URI "instanceOf"), + &kRDF_instanceOf); + if (NS_FAILED(rv)) break; + + rv = rdf->GetResource(NS_LITERAL_CSTRING(RDF_NAMESPACE_URI "type"), + &kRDF_type); + if (NS_FAILED(rv)) break; + + rv = rdf->GetResource(NS_LITERAL_CSTRING(RDF_NAMESPACE_URI "nextVal"), + &kRDF_nextVal); + if (NS_FAILED(rv)) break; + + rv = rdf->GetResource(NS_LITERAL_CSTRING(RDF_NAMESPACE_URI "Bag"), + &kRDF_Bag); + if (NS_FAILED(rv)) break; + + rv = rdf->GetResource(NS_LITERAL_CSTRING(RDF_NAMESPACE_URI "Seq"), + &kRDF_Seq); + if (NS_FAILED(rv)) break; + + rv = rdf->GetResource(NS_LITERAL_CSTRING(RDF_NAMESPACE_URI "Alt"), + &kRDF_Alt); + if (NS_FAILED(rv)) break; + + rv = CallGetService("@mozilla.org/rdf/container-utils;1", &gRDFC); + if (NS_FAILED(rv)) break; + } while (0); + + return rv; +} + +nsRDFXMLSerializer::nsRDFXMLSerializer() +{ + MOZ_COUNT_CTOR(nsRDFXMLSerializer); +} + +nsRDFXMLSerializer::~nsRDFXMLSerializer() +{ + MOZ_COUNT_DTOR(nsRDFXMLSerializer); + + if (--gRefCnt == 0) { + NS_IF_RELEASE(kRDF_Bag); + NS_IF_RELEASE(kRDF_Seq); + NS_IF_RELEASE(kRDF_Alt); + NS_IF_RELEASE(kRDF_instanceOf); + NS_IF_RELEASE(kRDF_type); + NS_IF_RELEASE(kRDF_nextVal); + NS_IF_RELEASE(gRDFC); + } +} + +NS_IMPL_ISUPPORTS(nsRDFXMLSerializer, nsIRDFXMLSerializer, nsIRDFXMLSource) + +NS_IMETHODIMP +nsRDFXMLSerializer::Init(nsIRDFDataSource* aDataSource) +{ + if (! aDataSource) + return NS_ERROR_NULL_POINTER; + + mDataSource = aDataSource; + mDataSource->GetURI(getter_Copies(mBaseURLSpec)); + + // Add the ``RDF'' prefix, by default. + nsCOMPtr prefix; + + prefix = NS_Atomize("RDF"); + AddNameSpace(prefix, NS_LITERAL_STRING("http://www.w3.org/1999/02/22-rdf-syntax-ns#")); + + prefix = NS_Atomize("NC"); + AddNameSpace(prefix, NS_LITERAL_STRING("http://home.netscape.com/NC-rdf#")); + + mPrefixID = 0; + + return NS_OK; +} + +NS_IMETHODIMP +nsRDFXMLSerializer::AddNameSpace(nsIAtom* aPrefix, const nsAString& aURI) +{ + nsCOMPtr prefix = aPrefix; + if (!prefix) { + // Make up a prefix, we don't want default namespaces, so + // that we can use QNames for elements and attributes alike. + prefix = EnsureNewPrefix(); + } + mNameSpaces.Put(aURI, prefix); + return NS_OK; +} + +static nsresult +rdf_BlockingWrite(nsIOutputStream* stream, const char* buf, uint32_t size) +{ + uint32_t written = 0; + uint32_t remaining = size; + while (remaining > 0) { + nsresult rv; + uint32_t cb; + + if (NS_FAILED(rv = stream->Write(buf + written, remaining, &cb))) + return rv; + + written += cb; + remaining -= cb; + } + return NS_OK; +} + +static nsresult +rdf_BlockingWrite(nsIOutputStream* stream, const nsCSubstring& s) +{ + return rdf_BlockingWrite(stream, s.BeginReading(), s.Length()); +} + +static nsresult +rdf_BlockingWrite(nsIOutputStream* stream, const nsAString& s) +{ + NS_ConvertUTF16toUTF8 utf8(s); + return rdf_BlockingWrite(stream, utf8.get(), utf8.Length()); +} + +already_AddRefed +nsRDFXMLSerializer::EnsureNewPrefix() +{ + nsAutoString qname; + nsCOMPtr prefix; + bool isNewPrefix; + do { + isNewPrefix = true; + qname.AssignLiteral("NS"); + qname.AppendInt(++mPrefixID, 10); + prefix = NS_Atomize(qname); + nsNameSpaceMap::const_iterator iter = mNameSpaces.first(); + while (iter != mNameSpaces.last() && isNewPrefix) { + isNewPrefix = (iter->mPrefix != prefix); + ++iter; + } + } while (!isNewPrefix); + return prefix.forget(); +} + +// This converts a property resource (like +// "http://www.w3.org/TR/WD-rdf-syntax#Description") into a QName +// ("RDF:Description"), and registers the namespace, if it's made up. + +nsresult +nsRDFXMLSerializer::RegisterQName(nsIRDFResource* aResource) +{ + nsAutoCString uri, qname; + aResource->GetValueUTF8(uri); + + nsNameSpaceMap::const_iterator iter = mNameSpaces.GetNameSpaceOf(uri); + if (iter != mNameSpaces.last()) { + NS_ENSURE_TRUE(iter->mPrefix, NS_ERROR_UNEXPECTED); + iter->mPrefix->ToUTF8String(qname); + qname.Append(':'); + qname += StringTail(uri, uri.Length() - iter->mURI.Length()); + mQNames.Put(aResource, qname); + return NS_OK; + } + + // Okay, so we don't have it in our map. Try to make one up. This + // is very bogus. + int32_t i = uri.RFindChar('#'); // first try a '#' + if (i == -1) { + i = uri.RFindChar('/'); + if (i == -1) { + // Okay, just punt and assume there is _no_ namespace on + // this thing... + mQNames.Put(aResource, uri); + return NS_OK; + } + } + + // Take whatever is to the right of the '#' or '/' and call it the + // local name, make up a prefix. + nsCOMPtr prefix = EnsureNewPrefix(); + mNameSpaces.Put(StringHead(uri, i+1), prefix); + prefix->ToUTF8String(qname); + qname.Append(':'); + qname += StringTail(uri, uri.Length() - (i + 1)); + + mQNames.Put(aResource, qname); + return NS_OK; +} + +nsresult +nsRDFXMLSerializer::GetQName(nsIRDFResource* aResource, nsCString& aQName) +{ + return mQNames.Get(aResource, &aQName) ? NS_OK : NS_ERROR_UNEXPECTED; +} + +bool +nsRDFXMLSerializer::IsContainerProperty(nsIRDFResource* aProperty) +{ + // Return `true' if the property is an internal property related + // to being a container. + if (aProperty == kRDF_instanceOf) + return true; + + if (aProperty == kRDF_nextVal) + return true; + + bool isOrdinal = false; + gRDFC->IsOrdinalProperty(aProperty, &isOrdinal); + if (isOrdinal) + return true; + + return false; +} + + +// convert '&', '<', and '>' into "&", "<", and ">", respectively. +static const char amp[] = "&"; +static const char lt[] = "<"; +static const char gt[] = ">"; +static const char quot[] = """; + +static void +rdf_EscapeAmpersandsAndAngleBrackets(nsCString& s) +{ + uint32_t newLength, origLength; + newLength = origLength = s.Length(); + + // Compute the length of the result string. + const char* start = s.BeginReading(); + const char* end = s.EndReading(); + const char* c = start; + while (c != end) { + switch (*c) { + case '&' : + newLength += sizeof(amp) - 2; + break; + case '<': + case '>': + newLength += sizeof(gt) - 2; + break; + default: + break; + } + ++c; + } + if (newLength == origLength) { + // nothing to escape + return; + } + + // escape the chars from the end back to the front. + s.SetLength(newLength); + + // Buffer might have changed, get the pointers again + start = s.BeginReading(); // begin of string + c = start + origLength - 1; // last char in original string + char* w = s.EndWriting() - 1; // last char in grown buffer + while (c >= start) { + switch (*c) { + case '&' : + w -= 4; + nsCharTraits::copy(w, amp, sizeof(amp) - 1); + break; + case '<': + w -= 3; + nsCharTraits::copy(w, lt, sizeof(lt) - 1); + break; + case '>': + w -= 3; + nsCharTraits::copy(w, gt, sizeof(gt) - 1); + break; + default: + *w = *c; + } + --w; + --c; + } +} + +// convert '"' to """ +static void +rdf_EscapeQuotes(nsCString& s) +{ + int32_t i = 0; + while ((i = s.FindChar('"', i)) != -1) { + s.Replace(i, 1, quot, sizeof(quot) - 1); + i += sizeof(quot) - 2; + } +} + +static void +rdf_EscapeAttributeValue(nsCString& s) +{ + rdf_EscapeAmpersandsAndAngleBrackets(s); + rdf_EscapeQuotes(s); +} + + +nsresult +nsRDFXMLSerializer::SerializeInlineAssertion(nsIOutputStream* aStream, + nsIRDFResource* aResource, + nsIRDFResource* aProperty, + nsIRDFLiteral* aValue) +{ + nsresult rv; + nsCString qname; + rv = GetQName(aProperty, qname); + NS_ENSURE_SUCCESS(rv, rv); + + rv = rdf_BlockingWrite(aStream, + NS_LITERAL_CSTRING("\n ")); + if (NS_FAILED(rv)) return rv; + + const char16_t* value; + aValue->GetValueConst(&value); + NS_ConvertUTF16toUTF8 s(value); + + rdf_EscapeAttributeValue(s); + + rv = rdf_BlockingWrite(aStream, qname); + if (NS_FAILED(rv)) return rv; + rv = rdf_BlockingWrite(aStream, "=\"", 2); + if (NS_FAILED(rv)) return rv; + s.Append('"'); + return rdf_BlockingWrite(aStream, s); +} + +nsresult +nsRDFXMLSerializer::SerializeChildAssertion(nsIOutputStream* aStream, + nsIRDFResource* aResource, + nsIRDFResource* aProperty, + nsIRDFNode* aValue) +{ + nsCString qname; + nsresult rv = GetQName(aProperty, qname); + NS_ENSURE_SUCCESS(rv, rv); + + rv = rdf_BlockingWrite(aStream, " <", 5); + if (NS_FAILED(rv)) return rv; + rv = rdf_BlockingWrite(aStream, qname); + if (NS_FAILED(rv)) return rv; + + nsCOMPtr resource; + nsCOMPtr literal; + nsCOMPtr number; + nsCOMPtr date; + + if ((resource = do_QueryInterface(aValue)) != nullptr) { + nsAutoCString uri; + resource->GetValueUTF8(uri); + + rdf_MakeRelativeRef(mBaseURLSpec, uri); + rdf_EscapeAttributeValue(uri); + + rv = rdf_BlockingWrite(aStream, kRDFResource1, + sizeof(kRDFResource1) - 1); + if (NS_FAILED(rv)) return rv; + rv = rdf_BlockingWrite(aStream, uri); + if (NS_FAILED(rv)) return rv; + rv = rdf_BlockingWrite(aStream, kRDFResource2, + sizeof(kRDFResource2) - 1); + if (NS_FAILED(rv)) return rv; + + goto no_close_tag; + } + else if ((literal = do_QueryInterface(aValue)) != nullptr) { + const char16_t *value; + literal->GetValueConst(&value); + NS_ConvertUTF16toUTF8 s(value); + + rdf_EscapeAmpersandsAndAngleBrackets(s); + + rv = rdf_BlockingWrite(aStream, ">", 1); + if (NS_FAILED(rv)) return rv; + rv = rdf_BlockingWrite(aStream, s); + if (NS_FAILED(rv)) return rv; + } + else if ((number = do_QueryInterface(aValue)) != nullptr) { + int32_t value; + number->GetValue(&value); + + nsAutoCString n; + n.AppendInt(value); + + rv = rdf_BlockingWrite(aStream, kRDFParseTypeInteger, + sizeof(kRDFParseTypeInteger) - 1); + if (NS_FAILED(rv)) return rv; + rv = rdf_BlockingWrite(aStream, n); + if (NS_FAILED(rv)) return rv; + } + else if ((date = do_QueryInterface(aValue)) != nullptr) { + PRTime value; + date->GetValue(&value); + + nsAutoCString s; + rdf_FormatDate(value, s); + + rv = rdf_BlockingWrite(aStream, kRDFParseTypeDate, + sizeof(kRDFParseTypeDate) - 1); + if (NS_FAILED(rv)) return rv; + rv = rdf_BlockingWrite(aStream, s); + if (NS_FAILED(rv)) return rv; + } + else { + // XXX it doesn't support nsIRDFResource _or_ nsIRDFLiteral??? + // We should serialize nsIRDFInt, nsIRDFDate, etc... + NS_WARNING("unknown RDF node type"); + + rv = rdf_BlockingWrite(aStream, kRDFUnknown, sizeof(kRDFUnknown) - 1); + if (NS_FAILED(rv)) return rv; + } + + rv = rdf_BlockingWrite(aStream, "\n", 2); + + no_close_tag: + return NS_OK; +} + +nsresult +nsRDFXMLSerializer::SerializeProperty(nsIOutputStream* aStream, + nsIRDFResource* aResource, + nsIRDFResource* aProperty, + bool aInline, + int32_t* aSkipped) +{ + nsresult rv = NS_OK; + + int32_t skipped = 0; + + nsCOMPtr assertions; + mDataSource->GetTargets(aResource, aProperty, true, getter_AddRefs(assertions)); + if (! assertions) + return NS_ERROR_FAILURE; + + // Serializing the assertion inline is ok as long as the property has + // only one target value, and it is a literal that doesn't include line + // breaks. + bool needsChild = false; + + while (1) { + bool hasMore = false; + assertions->HasMoreElements(&hasMore); + if (! hasMore) + break; + + nsCOMPtr isupports; + assertions->GetNext(getter_AddRefs(isupports)); + nsCOMPtr literal = do_QueryInterface(isupports); + needsChild |= (!literal); + + if (!needsChild) { + assertions->HasMoreElements(&needsChild); + if (!needsChild) { + const char16_t* literalVal = nullptr; + literal->GetValueConst(&literalVal); + if (literalVal) { + for (; *literalVal; literalVal++) { + if (*literalVal == char16_t('\n') || + *literalVal == char16_t('\r')) { + needsChild = true; + break; + } + } + } + } + } + + if (aInline && !needsChild) { + rv = SerializeInlineAssertion(aStream, aResource, aProperty, literal); + } + else if (!aInline && needsChild) { + nsCOMPtr value = do_QueryInterface(isupports); + rv = SerializeChildAssertion(aStream, aResource, aProperty, value); + } + else { + ++skipped; + rv = NS_OK; + } + + if (NS_FAILED(rv)) + break; + } + + *aSkipped += skipped; + return rv; +} + + +nsresult +nsRDFXMLSerializer::SerializeDescription(nsIOutputStream* aStream, + nsIRDFResource* aResource) +{ + nsresult rv; + + bool isTypedNode = false; + nsCString typeQName; + + nsCOMPtr typeNode; + mDataSource->GetTarget(aResource, kRDF_type, true, getter_AddRefs(typeNode)); + if (typeNode) { + nsCOMPtr type = do_QueryInterface(typeNode, &rv); + if (type) { + // Try to get a namespace prefix. If none is available, + // just treat the description as if it weren't a typed node + // after all and emit rdf:type as a normal property. This + // seems preferable to using a bogus (invented) prefix. + isTypedNode = NS_SUCCEEDED(GetQName(type, typeQName)); + } + } + + nsAutoCString uri; + rv = aResource->GetValueUTF8(uri); + if (NS_FAILED(rv)) return rv; + + rdf_MakeRelativeRef(mBaseURLSpec, uri); + rdf_EscapeAttributeValue(uri); + + // Emit an open tag and the subject + if (isTypedNode) { + rv = rdf_BlockingWrite(aStream, NS_LITERAL_STRING(" <")); + if (NS_FAILED(rv)) return rv; + // Watch out for the default namespace! + rv = rdf_BlockingWrite(aStream, typeQName); + if (NS_FAILED(rv)) return rv; + } + else { + rv = rdf_BlockingWrite(aStream, kRDFDescriptionOpen, + sizeof(kRDFDescriptionOpen) - 1); + if (NS_FAILED(rv)) return rv; + } + if (uri[0] == char16_t('#')) { + uri.Cut(0, 1); + rv = rdf_BlockingWrite(aStream, kIDAttr, sizeof(kIDAttr) - 1); + } + else { + rv = rdf_BlockingWrite(aStream, kAboutAttr, sizeof(kAboutAttr) - 1); + } + if (NS_FAILED(rv)) return rv; + + uri.Append('"'); + rv = rdf_BlockingWrite(aStream, uri); + if (NS_FAILED(rv)) return rv; + + // Any value that's a literal we can write out as an inline + // attribute on the RDF:Description + AutoTArray visited; + int32_t skipped = 0; + + nsCOMPtr arcs; + mDataSource->ArcLabelsOut(aResource, getter_AddRefs(arcs)); + + if (arcs) { + // Don't re-serialize rdf:type later on + if (isTypedNode) + visited.AppendElement(kRDF_type); + + while (1) { + bool hasMore = false; + arcs->HasMoreElements(&hasMore); + if (! hasMore) + break; + + nsCOMPtr isupports; + arcs->GetNext(getter_AddRefs(isupports)); + + nsCOMPtr property = do_QueryInterface(isupports); + if (! property) + continue; + + // Ignore properties that pertain to containers; we may be + // called from SerializeContainer() if the container resource + // has been assigned non-container properties. + if (IsContainerProperty(property)) + continue; + + // Only serialize values for the property once. + if (visited.Contains(property.get())) + continue; + + visited.AppendElement(property.get()); + + SerializeProperty(aStream, aResource, property, true, &skipped); + } + } + + if (skipped) { + // Close the RDF:Description tag. + rv = rdf_BlockingWrite(aStream, NS_LITERAL_CSTRING(">\n")); + if (NS_FAILED(rv)) return rv; + + // Now write out resources (which might have their own + // substructure) as children. + mDataSource->ArcLabelsOut(aResource, getter_AddRefs(arcs)); + + if (arcs) { + // Forget that we've visited anything + visited.Clear(); + // ... except for rdf:type + if (isTypedNode) + visited.AppendElement(kRDF_type); + + while (1) { + bool hasMore = false; + arcs->HasMoreElements(&hasMore); + if (! hasMore) + break; + + nsCOMPtr isupports; + arcs->GetNext(getter_AddRefs(isupports)); + + nsCOMPtr property = do_QueryInterface(isupports); + if (! property) + continue; + + // Ignore properties that pertain to containers; we may be + // called from SerializeContainer() if the container + // resource has been assigned non-container properties. + if (IsContainerProperty(property)) + continue; + + // have we already seen this property? If so, don't write it + // out again; serialize property will write each instance. + if (visited.Contains(property.get())) + continue; + + visited.AppendElement(property.get()); + + SerializeProperty(aStream, aResource, property, false, &skipped); + } + } + + // Emit a proper close-tag. + if (isTypedNode) { + rv = rdf_BlockingWrite(aStream, NS_LITERAL_CSTRING(" \n", 2); + if (NS_FAILED(rv)) return rv; + } + else { + rv = rdf_BlockingWrite(aStream, kRDFDescriptionClose, + sizeof(kRDFDescriptionClose) - 1); + if (NS_FAILED(rv)) return rv; + } + } + else { + // If we saw _no_ child properties, then we can don't need a + // close-tag. + rv = rdf_BlockingWrite(aStream, NS_LITERAL_CSTRING(" />\n")); + if (NS_FAILED(rv)) return rv; + } + + return NS_OK; +} + +nsresult +nsRDFXMLSerializer::SerializeMember(nsIOutputStream* aStream, + nsIRDFResource* aContainer, + nsIRDFNode* aMember) +{ + // If it's a resource, then output a "" + // tag, because we'll be dumping the resource separately. (We + // iterate thru all the resources in the datasource, + // remember?) Otherwise, output the literal value. + + nsCOMPtr resource; + nsCOMPtr literal; + nsCOMPtr number; + nsCOMPtr date; + +static const char kRDFLIOpen[] = " GetValueUTF8(uri); + + rdf_MakeRelativeRef(mBaseURLSpec, uri); + rdf_EscapeAttributeValue(uri); + + rv = rdf_BlockingWrite(aStream, kRDFResource1, + sizeof(kRDFResource1) - 1); + if (NS_FAILED(rv)) return rv; + rv = rdf_BlockingWrite(aStream, uri); + if (NS_FAILED(rv)) return rv; + rv = rdf_BlockingWrite(aStream, kRDFResource2, + sizeof(kRDFResource2) - 1); + if (NS_FAILED(rv)) return rv; + + goto no_close_tag; + } + else if ((literal = do_QueryInterface(aMember)) != nullptr) { + const char16_t *value; + literal->GetValueConst(&value); +static const char kRDFLIOpenGT[] = ">"; + // close the 'GetValue(&value); + + nsAutoCString n; + n.AppendInt(value); + + rv = rdf_BlockingWrite(aStream, kRDFParseTypeInteger, + sizeof(kRDFParseTypeInteger) - 1); + if (NS_FAILED(rv)) return rv; + rv = rdf_BlockingWrite(aStream, n); + if (NS_FAILED(rv)) return rv; + } + else if ((date = do_QueryInterface(aMember)) != nullptr) { + PRTime value; + date->GetValue(&value); + + nsAutoCString s; + rdf_FormatDate(value, s); + + rv = rdf_BlockingWrite(aStream, kRDFParseTypeDate, + sizeof(kRDFParseTypeDate) - 1); + if (NS_FAILED(rv)) return rv; + rv = rdf_BlockingWrite(aStream, s); + if (NS_FAILED(rv)) return rv; + } + else { + // XXX it doesn't support nsIRDFResource _or_ nsIRDFLiteral??? + // We should serialize nsIRDFInt, nsIRDFDate, etc... + NS_WARNING("unknown RDF node type"); + + rv = rdf_BlockingWrite(aStream, kRDFUnknown, sizeof(kRDFUnknown) - 1); + if (NS_FAILED(rv)) return rv; + } + + { +static const char kRDFLIClose[] = "\n"; + rv = rdf_BlockingWrite(aStream, kRDFLIClose, sizeof(kRDFLIClose) - 1); + if (NS_FAILED(rv)) return rv; + } + + no_close_tag: + return NS_OK; +} + + +nsresult +nsRDFXMLSerializer::SerializeContainer(nsIOutputStream* aStream, + nsIRDFResource* aContainer) +{ + nsresult rv; + nsAutoCString tag; + + // Decide if it's a sequence, bag, or alternation, and print the + // appropriate tag-open sequence + + if (IsA(mDataSource, aContainer, kRDF_Bag)) { + tag.AssignLiteral("RDF:Bag"); + } + else if (IsA(mDataSource, aContainer, kRDF_Seq)) { + tag.AssignLiteral("RDF:Seq"); + } + else if (IsA(mDataSource, aContainer, kRDF_Alt)) { + tag.AssignLiteral("RDF:Alt"); + } + else { + NS_ASSERTION(false, "huh? this is _not_ a container."); + return NS_ERROR_UNEXPECTED; + } + + rv = rdf_BlockingWrite(aStream, " <", 3); + if (NS_FAILED(rv)) return rv; + rv = rdf_BlockingWrite(aStream, tag); + if (NS_FAILED(rv)) return rv; + + + // Unfortunately, we always need to print out the identity of the + // resource, even if was constructed "anonymously". We need to do + // this because we never really know who else might be referring + // to it... + + nsAutoCString uri; + if (NS_SUCCEEDED(aContainer->GetValueUTF8(uri))) { + rdf_MakeRelativeRef(mBaseURLSpec, uri); + + rdf_EscapeAttributeValue(uri); + + if (uri.First() == '#') { + // Okay, it's actually identified as an element in the + // current document, not trying to decorate some absolute + // URI. We can use the 'ID=' attribute... + + uri.Cut(0, 1); // chop the '#' + rv = rdf_BlockingWrite(aStream, kIDAttr, sizeof(kIDAttr) - 1); + if (NS_FAILED(rv)) return rv; + } + else { + // We need to cheat and spit out an illegal 'about=' on + // the sequence. + rv = rdf_BlockingWrite(aStream, kAboutAttr, + sizeof(kAboutAttr) - 1); + if (NS_FAILED(rv)) return rv; + } + + rv = rdf_BlockingWrite(aStream, uri); + if (NS_FAILED(rv)) return rv; + rv = rdf_BlockingWrite(aStream, "\"", 1); + if (NS_FAILED(rv)) return rv; + } + + rv = rdf_BlockingWrite(aStream, ">\n", 2); + if (NS_FAILED(rv)) return rv; + + // First iterate through each of the ordinal elements (the RDF/XML + // syntax doesn't allow us to place properties on RDF container + // elements). + nsCOMPtr elements; + rv = NS_NewContainerEnumerator(mDataSource, aContainer, getter_AddRefs(elements)); + + if (NS_SUCCEEDED(rv)) { + while (1) { + bool hasMore; + rv = elements->HasMoreElements(&hasMore); + if (NS_FAILED(rv)) break; + + if (! hasMore) + break; + + nsCOMPtr isupports; + elements->GetNext(getter_AddRefs(isupports)); + + nsCOMPtr element = do_QueryInterface(isupports); + NS_ASSERTION(element != nullptr, "not an nsIRDFNode"); + if (! element) + continue; + + SerializeMember(aStream, aContainer, element); + } + } + + // close the container tag + rv = rdf_BlockingWrite(aStream, " \n", 2); + rv = rdf_BlockingWrite(aStream, tag); + if (NS_FAILED(rv)) return rv; + + // Now, we iterate through _all_ of the arcs, in case someone has + // applied properties to the bag itself. These'll be placed in a + // separate RDF:Description element. + nsCOMPtr arcs; + mDataSource->ArcLabelsOut(aContainer, getter_AddRefs(arcs)); + + bool wroteDescription = false; + while (! wroteDescription) { + bool hasMore = false; + rv = arcs->HasMoreElements(&hasMore); + if (NS_FAILED(rv)) break; + + if (! hasMore) + break; + + nsIRDFResource* property; + rv = arcs->GetNext((nsISupports**) &property); + if (NS_FAILED(rv)) break; + + // If it's a membership property, then output a "LI" + // tag. Otherwise, output a property. + if (! IsContainerProperty(property)) { + rv = SerializeDescription(aStream, aContainer); + wroteDescription = true; + } + + NS_RELEASE(property); + if (NS_FAILED(rv)) + break; + } + + return NS_OK; +} + + +nsresult +nsRDFXMLSerializer::SerializePrologue(nsIOutputStream* aStream) +{ +static const char kXMLVersion[] = "\n"; + + nsresult rv; + rv = rdf_BlockingWrite(aStream, kXMLVersion, sizeof(kXMLVersion) - 1); + if (NS_FAILED(rv)) return rv; + + // global name space declarations + rv = rdf_BlockingWrite(aStream, NS_LITERAL_CSTRING("mPrefix) { + rv = rdf_BlockingWrite(aStream, NS_LITERAL_CSTRING(":")); + if (NS_FAILED(rv)) return rv; + nsAutoCString prefix; + entry->mPrefix->ToUTF8String(prefix); + rv = rdf_BlockingWrite(aStream, prefix); + if (NS_FAILED(rv)) return rv; + } + + rv = rdf_BlockingWrite(aStream, NS_LITERAL_CSTRING("=\"")); + if (NS_FAILED(rv)) return rv; + nsAutoCString uri(entry->mURI); + rdf_EscapeAttributeValue(uri); + rv = rdf_BlockingWrite(aStream, uri); + if (NS_FAILED(rv)) return rv; + rv = rdf_BlockingWrite(aStream, NS_LITERAL_CSTRING("\"")); + if (NS_FAILED(rv)) return rv; + } + + return rdf_BlockingWrite(aStream, NS_LITERAL_CSTRING(">\n")); +} + + +nsresult +nsRDFXMLSerializer::SerializeEpilogue(nsIOutputStream* aStream) +{ + return rdf_BlockingWrite(aStream, NS_LITERAL_CSTRING("\n")); +} + +class QNameCollector final : public rdfITripleVisitor { +public: + NS_DECL_ISUPPORTS + NS_DECL_RDFITRIPLEVISITOR + explicit QNameCollector(nsRDFXMLSerializer* aParent) + : mParent(aParent){} +private: + ~QNameCollector() {} + nsRDFXMLSerializer* mParent; +}; + +NS_IMPL_ISUPPORTS(QNameCollector, rdfITripleVisitor) +nsresult +QNameCollector::Visit(nsIRDFNode* aSubject, nsIRDFResource* aPredicate, + nsIRDFNode* aObject, bool aTruthValue) +{ + if (aPredicate == mParent->kRDF_type) { + // try to get a type QName for aObject, should be a resource + nsCOMPtr resType = do_QueryInterface(aObject); + if (!resType) { + // ignore error + return NS_OK; + } + if (mParent->mQNames.Get(resType, nullptr)) { + return NS_OK; + } + mParent->RegisterQName(resType); + return NS_OK; + } + + if (mParent->mQNames.Get(aPredicate, nullptr)) { + return NS_OK; + } + if (aPredicate == mParent->kRDF_instanceOf || + aPredicate == mParent->kRDF_nextVal) + return NS_OK; + bool isOrdinal = false; + mParent->gRDFC->IsOrdinalProperty(aPredicate, &isOrdinal); + if (isOrdinal) + return NS_OK; + + mParent->RegisterQName(aPredicate); + + return NS_OK; +} + +nsresult +nsRDFXMLSerializer::CollectNamespaces() +{ + // Iterate over all Triples to get namespaces for subject resource types + // and Predicates and cache all the QNames we want to use. + nsCOMPtr collector = + new QNameCollector(this); + nsCOMPtr ds = do_QueryInterface(mDataSource); // XXX API + NS_ENSURE_TRUE(collector && ds, NS_ERROR_FAILURE); + return ds->VisitAllTriples(collector); +} + +//---------------------------------------------------------------------- + +NS_IMETHODIMP +nsRDFXMLSerializer::Serialize(nsIOutputStream* aStream) +{ + nsresult rv; + + rv = CollectNamespaces(); + if (NS_FAILED(rv)) return rv; + + nsCOMPtr resources; + rv = mDataSource->GetAllResources(getter_AddRefs(resources)); + if (NS_FAILED(rv)) return rv; + + rv = SerializePrologue(aStream); + if (NS_FAILED(rv)) + return rv; + + while (1) { + bool hasMore = false; + resources->HasMoreElements(&hasMore); + if (! hasMore) + break; + + nsCOMPtr isupports; + resources->GetNext(getter_AddRefs(isupports)); + + nsCOMPtr resource = do_QueryInterface(isupports); + if (! resource) + continue; + + if (IsA(mDataSource, resource, kRDF_Bag) || + IsA(mDataSource, resource, kRDF_Seq) || + IsA(mDataSource, resource, kRDF_Alt)) { + rv = SerializeContainer(aStream, resource); + } + else { + rv = SerializeDescription(aStream, resource); + } + + if (NS_FAILED(rv)) + break; + } + + rv = SerializeEpilogue(aStream); + + return rv; +} + + +bool +nsRDFXMLSerializer::IsA(nsIRDFDataSource* aDataSource, nsIRDFResource* aResource, nsIRDFResource* aType) +{ + nsresult rv; + + bool result; + rv = aDataSource->HasAssertion(aResource, kRDF_instanceOf, aType, true, &result); + if (NS_FAILED(rv)) return false; + + return result; +} diff --git a/rdf/base/nsRDFXMLSerializer.h b/rdf/base/nsRDFXMLSerializer.h new file mode 100644 index 000000000..b31a088cd --- /dev/null +++ b/rdf/base/nsRDFXMLSerializer.h @@ -0,0 +1,117 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef nsRDFXMLSerializer_h__ +#define nsRDFXMLSerializer_h__ + +#include "nsIRDFLiteral.h" +#include "nsIRDFXMLSerializer.h" +#include "nsIRDFXMLSource.h" +#include "nsNameSpaceMap.h" +#include "nsXPIDLString.h" + +#include "nsDataHashtable.h" +#include "rdfITripleVisitor.h" + +class nsIOutputStream; +class nsIRDFContainerUtils; + +/** + * A helper class that can serialize RDF/XML from a + * datasource. Implements both nsIRDFXMLSerializer and + * nsIRDFXMLSource. + */ +class nsRDFXMLSerializer : public nsIRDFXMLSerializer, + public nsIRDFXMLSource +{ +public: + static nsresult + Create(nsISupports* aOuter, REFNSIID aIID, void** aResult); + + NS_DECL_ISUPPORTS + NS_DECL_NSIRDFXMLSERIALIZER + NS_DECL_NSIRDFXMLSOURCE + +protected: + nsRDFXMLSerializer(); + virtual ~nsRDFXMLSerializer(); + + // Implementation methods + nsresult + RegisterQName(nsIRDFResource* aResource); + nsresult + GetQName(nsIRDFResource* aResource, nsCString& aQName); + already_AddRefed + EnsureNewPrefix(); + + nsresult + SerializeInlineAssertion(nsIOutputStream* aStream, + nsIRDFResource* aResource, + nsIRDFResource* aProperty, + nsIRDFLiteral* aValue); + + nsresult + SerializeChildAssertion(nsIOutputStream* aStream, + nsIRDFResource* aResource, + nsIRDFResource* aProperty, + nsIRDFNode* aValue); + + nsresult + SerializeProperty(nsIOutputStream* aStream, + nsIRDFResource* aResource, + nsIRDFResource* aProperty, + bool aInline, + int32_t* aSkipped); + + bool + IsContainerProperty(nsIRDFResource* aProperty); + + nsresult + SerializeDescription(nsIOutputStream* aStream, + nsIRDFResource* aResource); + + nsresult + SerializeMember(nsIOutputStream* aStream, + nsIRDFResource* aContainer, + nsIRDFNode* aMember); + + nsresult + SerializeContainer(nsIOutputStream* aStream, + nsIRDFResource* aContainer); + + nsresult + SerializePrologue(nsIOutputStream* aStream); + + nsresult + SerializeEpilogue(nsIOutputStream* aStream); + + nsresult + CollectNamespaces(); + + bool + IsA(nsIRDFDataSource* aDataSource, nsIRDFResource* aResource, nsIRDFResource* aType); + + nsCOMPtr mDataSource; + nsNameSpaceMap mNameSpaces; + nsXPIDLCString mBaseURLSpec; + + // hash mapping resources to utf8-encoded QNames + nsDataHashtable mQNames; + friend class QNameCollector; + + uint32_t mPrefixID; + + static int32_t gRefCnt; + static nsIRDFResource* kRDF_instanceOf; + static nsIRDFResource* kRDF_type; + static nsIRDFResource* kRDF_nextVal; + static nsIRDFResource* kRDF_Bag; + static nsIRDFResource* kRDF_Seq; + static nsIRDFResource* kRDF_Alt; + static nsIRDFContainerUtils* gRDFC; +}; + +#endif // nsRDFXMLSerializer_h__ diff --git a/rdf/base/rdf.h b/rdf/base/rdf.h new file mode 100644 index 000000000..a863884a0 --- /dev/null +++ b/rdf/base/rdf.h @@ -0,0 +1,61 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + + A catch-all header file for miscellaneous RDF stuff. Currently + contains error codes and vocabulary macros. + + */ + +#ifndef rdf_h___ +#define rdf_h___ + +#include "nsError.h" + +/** + * The following macros are to aid in vocabulary definition. They + * creates const char*'s for "kURI[prefix]_[name]", appropriate + * complete namespace qualification on the URI, e.g., + * + * #define RDF_NAMESPACE_URI "http://www.w3.org/TR/WD-rdf-syntax#" + * DEFINE_RDF_ELEMENT(RDF_NAMESPACE_URI, RDF, ID); + * + * will define: + * + * kURIRDF_ID to be "http://www.w3.org/TR/WD-rdf-syntax#ID" + */ + +#define DEFINE_RDF_VOCAB(ns, prefix, name) \ +static const char kURI##prefix##_##name[] = ns #name + +/** + * Core RDF vocabularies that we use to define semantics + */ + +#define RDF_NAMESPACE_URI "http://www.w3.org/1999/02/22-rdf-syntax-ns#" +#define WEB_NAMESPACE_URI "http://home.netscape.com/WEB-rdf#" +#define NC_NAMESPACE_URI "http://home.netscape.com/NC-rdf#" +#define DEVMO_NAMESPACE_URI_PREFIX "http://developer.mozilla.org/rdf/vocabulary/" + + +/* ContractID prefixes for RDF DLL registration. */ +#define NS_RDF_CONTRACTID "@mozilla.org/rdf" +#define NS_RDF_DATASOURCE_CONTRACTID NS_RDF_CONTRACTID "/datasource;1" +#define NS_RDF_DATASOURCE_CONTRACTID_PREFIX NS_RDF_DATASOURCE_CONTRACTID "?name=" +#define NS_RDF_RESOURCE_FACTORY_CONTRACTID "@mozilla.org/rdf/resource-factory;1" +#define NS_RDF_RESOURCE_FACTORY_CONTRACTID_PREFIX NS_RDF_RESOURCE_FACTORY_CONTRACTID "?name=" +#define NS_RDF_INFER_DATASOURCE_CONTRACTID_PREFIX NS_RDF_CONTRACTID "/infer-datasource;1?engine=" + +#define NS_RDF_SERIALIZER NS_RDF_CONTRACTID "/serializer;1?format=" + +// contract ID is in the form +// @mozilla.org/rdf/delegate-factory;1?key=&scheme= +#define NS_RDF_DELEGATEFACTORY_CONTRACTID "@mozilla.org/rdf/delegate-factory;1" +#define NS_RDF_DELEGATEFACTORY_CONTRACTID_PREFIX NS_RDF_DELEGATEFACTORY_CONTRACTID "?key=" + +/*@}*/ + +#endif /* rdf_h___ */ diff --git a/rdf/base/rdfIDataSource.idl b/rdf/base/rdfIDataSource.idl new file mode 100644 index 000000000..848cbca11 --- /dev/null +++ b/rdf/base/rdfIDataSource.idl @@ -0,0 +1,38 @@ +/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface rdfITripleVisitor; + +/** + * Interface used in RDF to describe data sources. + * + * @status PLASMA + */ + +[scriptable, uuid(ebce86bd-1568-4a34-a808-9ccf9cde8087)] +interface rdfIDataSource : nsISupports +{ + /** + * Visit all the subject resources in the datasource. The order is + * intederminate and may change from one invocation to the next. + * The subjects will be in the aSubject argument in calls into + * aVisitor, aPredicate and aObject will be null. + * @note Implementations may throw NS_ERROR_NOT_IMPLEMENTED for + * this method, but in this case RDF serializations of this + * datasource will not be possible. + */ + void visitAllSubjects(in rdfITripleVisitor aVisitor); + + /** + * Visit all the triples in the datasource. The order is + * intederminate and may change from one invocation to the next. + * @note Implementations may throw NS_ERROR_NOT_IMPLEMENTED for + * this method, but in this case RDF serializations of this + * datasource will not be possible. + */ + void visitAllTriples(in rdfITripleVisitor aVisitor); +}; diff --git a/rdf/base/rdfISerializer.idl b/rdf/base/rdfISerializer.idl new file mode 100644 index 000000000..9ec6b69ea --- /dev/null +++ b/rdf/base/rdfISerializer.idl @@ -0,0 +1,30 @@ +/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface rdfIDataSource; +interface nsIOutputStream; + +/** + * Interface used to serialize RDF. + * + * @status PLASMA + */ + +[scriptable, uuid(f0edfcdd-8bca-4d32-9226-7421001396a4)] +interface rdfISerializer : nsISupports +{ + /** + * Synchronously serialize the given datasource to the outputstream. + * + * Implementations are not required to implement any buffering or + * other stream-based optimizations. + * + * @param aDataSource The RDF data source to be serialized. + * @param aOut The output stream to use. + */ + void serialize(in rdfIDataSource aDataSource, in nsIOutputStream aOut); +}; diff --git a/rdf/base/rdfITripleVisitor.idl b/rdf/base/rdfITripleVisitor.idl new file mode 100644 index 000000000..ecac14871 --- /dev/null +++ b/rdf/base/rdfITripleVisitor.idl @@ -0,0 +1,31 @@ +/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsIRDFResource; +interface nsIRDFNode; + +/** + * Interface used in RDF to enumerate triples. + * Also used by rdfIDataSource::getAllSubjects, then aPredicate, + * aObject and aTruthValue are ignored. + * + * @status PLASMA + */ + +[scriptable, function, uuid(aafea151-c271-4505-9978-a100d292800c)] +interface rdfITripleVisitor : nsISupports +{ + /** + * Callback function for returning query results. + * + * @param aSubject, aPredicate, aObject describe the (sub-)arc + * @returnCode NS_RDF_STOP_VISIT to stop iterating over the query result. + * Any error code will stop the iteration as well. + */ + void visit(in nsIRDFNode aSubject, in nsIRDFResource aPredicate, + in nsIRDFNode aObject, in boolean aTruthValue); +}; diff --git a/rdf/base/rdfTriplesSerializer.cpp b/rdf/base/rdfTriplesSerializer.cpp new file mode 100644 index 000000000..f419c7612 --- /dev/null +++ b/rdf/base/rdfTriplesSerializer.cpp @@ -0,0 +1,151 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsIOutputStream.h" +#include "nsReadableUtils.h" +#include "nsCRT.h" +#include "nsCOMPtr.h" +#include "nsString.h" +#include "nsPrintfCString.h" +#include "nsIBufferedStreams.h" +#include "nsNetCID.h" +#include "nsComponentManagerUtils.h" + +#include "rdfISerializer.h" +#include "rdfIDataSource.h" +#include "rdfITripleVisitor.h" + +#include "nsIRDFResource.h" +#include "nsIRDFLiteral.h" +#include "mozilla/Attributes.h" + +class TriplesVisitor final : public rdfITripleVisitor +{ +public: + explicit TriplesVisitor(nsIOutputStream* aOut) : mOut(aOut) {} + NS_DECL_RDFITRIPLEVISITOR + NS_DECL_ISUPPORTS +protected: + ~TriplesVisitor() {} + nsresult writeResource(nsIRDFResource* aResource); + nsIOutputStream* mOut; +}; + +NS_IMPL_ISUPPORTS(TriplesVisitor, rdfITripleVisitor) + +nsresult +TriplesVisitor::writeResource(nsIRDFResource *aResource) +{ + nsCString res; + uint32_t writeCount, wroteCount; + mOut->Write("<", 1, &wroteCount); + NS_ENSURE_TRUE(wroteCount == 1, NS_ERROR_FAILURE); + nsresult rv = aResource->GetValueUTF8(res); + NS_ENSURE_SUCCESS(rv, rv); + writeCount = res.Length(); + mOut->Write(res.get(), writeCount, &wroteCount); + NS_ENSURE_TRUE(writeCount == wroteCount, NS_ERROR_FAILURE); + mOut->Write("> ", 2, &wroteCount); + NS_ENSURE_TRUE(wroteCount == 2, NS_ERROR_FAILURE); + return NS_OK; +} + +NS_IMETHODIMP +TriplesVisitor::Visit(nsIRDFNode *aSubject, nsIRDFResource *aPredicate, + nsIRDFNode *aObject, bool aTruthValue) +{ + nsCOMPtr subjectRes = do_QueryInterface(aSubject); + nsresult rv = NS_OK; + if (subjectRes) { + rv = writeResource(subjectRes); + } + if (NS_FAILED(rv)) { + return rv; + } + rv = writeResource(aPredicate); + if (NS_FAILED(rv)) { + return rv; + } + nsCOMPtr res = do_QueryInterface(aObject); + nsCOMPtr lit; + nsCOMPtr intLit; + uint32_t wroteCount; + if (res) { + rv = writeResource(res); + } else if ((lit = do_QueryInterface(aObject)) != nullptr) { + const char16_t *value; + lit->GetValueConst(&value); + nsAutoCString object; + object.Append('"'); + AppendUTF16toUTF8(value, object); + object.AppendLiteral("\" "); + uint32_t writeCount = object.Length(); + rv = mOut->Write(object.get(), writeCount, &wroteCount); + NS_ENSURE_TRUE(writeCount == wroteCount, NS_ERROR_FAILURE); + } else if ((intLit = do_QueryInterface(aObject)) != nullptr) { + int32_t value; + intLit->GetValue(&value); + nsPrintfCString + object("\"%i\"^^ ", + value); + uint32_t writeCount = object.Length(); + rv = mOut->Write(object.get(), writeCount, &wroteCount); + NS_ENSURE_TRUE(writeCount == wroteCount, NS_ERROR_FAILURE); + } + NS_ENSURE_SUCCESS(rv, rv); + return mOut->Write(".\n", 2, &wroteCount); +} + +class rdfTriplesSerializer final : public rdfISerializer +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_RDFISERIALIZER + + rdfTriplesSerializer(); + +private: + ~rdfTriplesSerializer(); + +}; + +nsresult +NS_NewTriplesSerializer(rdfISerializer** aResult) +{ + NS_PRECONDITION(aResult != nullptr, "null ptr"); + if (! aResult) + return NS_ERROR_NULL_POINTER; + + *aResult = new rdfTriplesSerializer(); + if (! *aResult) + return NS_ERROR_OUT_OF_MEMORY; + NS_ADDREF(*aResult); + return NS_OK; +} + +NS_IMPL_ISUPPORTS(rdfTriplesSerializer, rdfISerializer) + +rdfTriplesSerializer::rdfTriplesSerializer() +{ +} + +rdfTriplesSerializer::~rdfTriplesSerializer() +{ +} + +NS_IMETHODIMP +rdfTriplesSerializer::Serialize(rdfIDataSource *aDataSource, + nsIOutputStream *aOut) +{ + nsresult rv; + nsCOMPtr bufout = + do_CreateInstance(NS_BUFFEREDOUTPUTSTREAM_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + rv = bufout->Init(aOut, 1024); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr tv = new TriplesVisitor(bufout); + NS_ENSURE_TRUE(tv, NS_ERROR_OUT_OF_MEMORY); + return aDataSource->VisitAllTriples(tv); +} diff --git a/rdf/base/rdfutil.cpp b/rdf/base/rdfutil.cpp new file mode 100644 index 000000000..849072145 --- /dev/null +++ b/rdf/base/rdfutil.cpp @@ -0,0 +1,111 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +/* + + Implementations for a bunch of useful RDF utility routines. Many of + these will eventually be exported outside of RDF.DLL via the + nsIRDFService interface. + + TO DO + + 1) Make this so that it doesn't permanently leak the RDF service + object. + + 2) Make container functions thread-safe. They currently don't ensure + that the RDF:nextVal property is maintained safely. + + */ + +#include "nsCOMPtr.h" +#include "nsIRDFDataSource.h" +#include "nsIRDFNode.h" +#include "nsIRDFService.h" +#include "nsIServiceManager.h" +#include "nsIURL.h" +#include "nsIIOService.h" +#include "nsIURL.h" +#include "nsRDFCID.h" +#include "nsString.h" +#include "nsXPIDLString.h" +#include "nsUnicharUtils.h" +#include "rdfutil.h" +#include "prtime.h" + +//////////////////////////////////////////////////////////////////////// + +nsresult +rdf_MakeRelativeRef(const nsCSubstring& aBaseURI, nsCString& aURI) +{ + // This implementation is extremely simple: e.g., it can't compute + // relative paths, or anything fancy like that. If the context URI + // is not a prefix of the URI in question, we'll just bail. + uint32_t prefixLen = aBaseURI.Length(); + if (prefixLen != 0 && StringBeginsWith(aURI, aBaseURI)) { + if (prefixLen < aURI.Length() && aURI.CharAt(prefixLen) == '/') + ++prefixLen; // chop the leading slash so it's not `absolute' + + aURI.Cut(0, prefixLen); + } + + return NS_OK; +} + +void +rdf_FormatDate(PRTime aTime, nsACString &aResult) +{ + // Outputs Unixish date in GMT plus usecs; e.g., + // Wed Jan 9 19:15:13 2002 +002441 + // + PRExplodedTime t; + PR_ExplodeTime(aTime, PR_GMTParameters, &t); + + char buf[256]; + PR_FormatTimeUSEnglish(buf, sizeof buf, "%a %b %d %H:%M:%S %Y", &t); + aResult.Append(buf); + + // usecs + aResult.AppendLiteral(" +"); + int32_t usec = t.tm_usec; + for (int32_t digit = 100000; digit > 1; digit /= 10) { + aResult.Append(char('0' + (usec / digit))); + usec %= digit; + } + aResult.Append(char('0' + usec)); +} + +PRTime +rdf_ParseDate(const nsACString &aTime) +{ + PRTime t; + PR_ParseTimeString(PromiseFlatCString(aTime).get(), true, &t); + + int32_t usec = 0; + + nsACString::const_iterator begin, digit, end; + aTime.BeginReading(begin); + aTime.EndReading(end); + + // Walk backwards until we find a `+', run out of string, or a + // non-numeric character. + digit = end; + while (--digit != begin && *digit != '+') { + if (*digit < '0' || *digit > '9') + break; + } + + if (digit != begin && *digit == '+') { + // There's a usec field specified (or, at least, something + // that looks close enough. Parse it, and add it to the time. + while (++digit != end) { + usec *= 10; + usec += *digit - '0'; + } + + t += usec; + } + + return t; +} diff --git a/rdf/base/rdfutil.h b/rdf/base/rdfutil.h new file mode 100644 index 000000000..c11581a4a --- /dev/null +++ b/rdf/base/rdfutil.h @@ -0,0 +1,40 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + + +/* + + A bunch of useful RDF utility routines. Many of these will + eventually be exported outside of RDF.DLL via the nsIRDFService + interface. + + TO DO + + 1) Move the anonymous resource stuff to nsIRDFService? + + 2) All that's left is rdf_PossiblyMakeRelative() and + -Absolute(). Maybe those go on nsIRDFService, too. + + */ + +#ifndef rdfutil_h__ +#define rdfutil_h__ + + +class nsACString; +class nsCString; + +nsresult +rdf_MakeRelativeRef(const nsCSubstring& aBaseURI, nsCString& aURI); + +void +rdf_FormatDate(PRTime aTime, nsACString &aResult); + +PRTime +rdf_ParseDate(const nsACString &aTime); + +#endif // rdfutil_h__ + + -- cgit v1.2.3