michael@0: /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: #include "nsRDFConMemberTestNode.h" michael@0: #include "nsIRDFContainer.h" michael@0: #include "nsIRDFContainerUtils.h" michael@0: #include "nsRDFCID.h" michael@0: #include "nsIServiceManager.h" michael@0: #include "nsResourceSet.h" michael@0: #include "nsString.h" michael@0: #include "nsXULContentUtils.h" michael@0: michael@0: #include "prlog.h" michael@0: #ifdef PR_LOGGING michael@0: extern PRLogModuleInfo* gXULTemplateLog; michael@0: #endif michael@0: michael@0: nsRDFConMemberTestNode::nsRDFConMemberTestNode(TestNode* aParent, michael@0: nsXULTemplateQueryProcessorRDF* aProcessor, michael@0: nsIAtom *aContainerVariable, michael@0: nsIAtom *aMemberVariable) michael@0: : nsRDFTestNode(aParent), michael@0: mProcessor(aProcessor), michael@0: mContainerVariable(aContainerVariable), michael@0: mMemberVariable(aMemberVariable) michael@0: { michael@0: #ifdef PR_LOGGING michael@0: if (PR_LOG_TEST(gXULTemplateLog, PR_LOG_DEBUG)) { michael@0: nsAutoCString props; michael@0: michael@0: nsResourceSet& containmentProps = aProcessor->ContainmentProperties(); michael@0: nsResourceSet::ConstIterator last = containmentProps.Last(); michael@0: nsResourceSet::ConstIterator first = containmentProps.First(); michael@0: nsResourceSet::ConstIterator iter; michael@0: michael@0: for (iter = first; iter != last; ++iter) { michael@0: if (iter != first) michael@0: props += " "; michael@0: michael@0: const char* str; michael@0: iter->GetValueConst(&str); michael@0: michael@0: props += str; michael@0: } michael@0: michael@0: nsAutoString cvar(NS_LITERAL_STRING("(none)")); michael@0: if (mContainerVariable) michael@0: mContainerVariable->ToString(cvar); michael@0: michael@0: nsAutoString mvar(NS_LITERAL_STRING("(none)")); michael@0: if (mMemberVariable) michael@0: mMemberVariable->ToString(mvar); michael@0: michael@0: PR_LOG(gXULTemplateLog, PR_LOG_DEBUG, michael@0: ("nsRDFConMemberTestNode[%p]: parent=%p member-props=(%s) container-var=%s member-var=%s", michael@0: this, michael@0: aParent, michael@0: props.get(), michael@0: NS_ConvertUTF16toUTF8(cvar).get(), michael@0: NS_ConvertUTF16toUTF8(mvar).get())); michael@0: } michael@0: #endif michael@0: } michael@0: michael@0: nsresult michael@0: nsRDFConMemberTestNode::FilterInstantiations(InstantiationSet& aInstantiations, michael@0: bool* aCantHandleYet) const michael@0: { michael@0: // XXX Uh, factor me, please! michael@0: nsresult rv; michael@0: michael@0: if (aCantHandleYet) michael@0: *aCantHandleYet = false; michael@0: michael@0: nsCOMPtr rdfc = michael@0: do_GetService("@mozilla.org/rdf/container-utils;1"); michael@0: michael@0: if (! rdfc) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: nsIRDFDataSource* ds = mProcessor->GetDataSource(); michael@0: michael@0: InstantiationSet::Iterator last = aInstantiations.Last(); michael@0: for (InstantiationSet::Iterator inst = aInstantiations.First(); inst != last; ++inst) { michael@0: bool hasContainerBinding; michael@0: nsCOMPtr containerValue; michael@0: hasContainerBinding = inst->mAssignments.GetAssignmentFor(mContainerVariable, michael@0: getter_AddRefs(containerValue)); michael@0: michael@0: nsCOMPtr containerRes = do_QueryInterface(containerValue); michael@0: michael@0: nsCOMPtr rdfcontainer; michael@0: michael@0: if (hasContainerBinding && containerRes) { michael@0: // If we have a container assignment, then see if the michael@0: // container is an RDF container (bag, seq, alt), and if michael@0: // so, wrap it. michael@0: bool isRDFContainer; michael@0: rv = rdfc->IsContainer(ds, containerRes, &isRDFContainer); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: if (isRDFContainer) { michael@0: rdfcontainer = do_CreateInstance("@mozilla.org/rdf/container;1", &rv); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: rv = rdfcontainer->Init(ds, containerRes); michael@0: if (NS_FAILED(rv)) return rv; michael@0: } michael@0: } michael@0: michael@0: bool hasMemberBinding; michael@0: nsCOMPtr memberValue; michael@0: hasMemberBinding = inst->mAssignments.GetAssignmentFor(mMemberVariable, michael@0: getter_AddRefs(memberValue)); michael@0: michael@0: #ifdef PR_LOGGING michael@0: if (PR_LOG_TEST(gXULTemplateLog, PR_LOG_DEBUG)) { michael@0: const char* container = "(unbound)"; michael@0: if (hasContainerBinding) michael@0: containerRes->GetValueConst(&container); michael@0: michael@0: nsAutoString member(NS_LITERAL_STRING("(unbound)")); michael@0: if (hasMemberBinding) michael@0: nsXULContentUtils::GetTextForNode(memberValue, member); michael@0: michael@0: PR_LOG(gXULTemplateLog, PR_LOG_DEBUG, michael@0: ("nsRDFConMemberTestNode[%p]: FilterInstantiations() container=[%s] member=[%s]", michael@0: this, container, NS_ConvertUTF16toUTF8(member).get())); michael@0: } michael@0: #endif michael@0: michael@0: if (hasContainerBinding && hasMemberBinding) { michael@0: // it's a consistency check. see if we have a assignment that is consistent michael@0: bool isconsistent = false; michael@0: michael@0: if (rdfcontainer) { michael@0: // RDF containers are easy. Just use the container API. michael@0: int32_t index; michael@0: rv = rdfcontainer->IndexOf(memberValue, &index); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: if (index >= 0) michael@0: isconsistent = true; michael@0: } michael@0: michael@0: // XXXwaterson oof. if we *are* an RDF container, why do michael@0: // we still need to grovel through all the containment michael@0: // properties if the thing we're looking for wasn't there? michael@0: michael@0: if (! isconsistent) { michael@0: // Othewise, we'll need to grovel through the michael@0: // membership properties to see if we have an michael@0: // assertion that indicates membership. michael@0: nsResourceSet& containmentProps = mProcessor->ContainmentProperties(); michael@0: for (nsResourceSet::ConstIterator property = containmentProps.First(); michael@0: property != containmentProps.Last(); michael@0: ++property) { michael@0: bool hasAssertion; michael@0: rv = ds->HasAssertion(containerRes, michael@0: *property, michael@0: memberValue, michael@0: true, michael@0: &hasAssertion); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: if (hasAssertion) { michael@0: // it's consistent. leave it in the set and we'll michael@0: // run it up to our parent. michael@0: isconsistent = true; michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: michael@0: PR_LOG(gXULTemplateLog, PR_LOG_DEBUG, michael@0: (" consistency check => %s", isconsistent ? "passed" : "failed")); michael@0: michael@0: if (isconsistent) { michael@0: // Add a memory element to our set-of-support. michael@0: Element* element = michael@0: new nsRDFConMemberTestNode::Element(containerRes, michael@0: memberValue); michael@0: michael@0: if (! element) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: michael@0: inst->AddSupportingElement(element); michael@0: } michael@0: else { michael@0: // it's inconsistent. remove it. michael@0: aInstantiations.Erase(inst--); michael@0: } michael@0: michael@0: // We're done, go on to the next instantiation michael@0: continue; michael@0: } michael@0: michael@0: if (hasContainerBinding && rdfcontainer) { michael@0: // We've got a container assignment, and the container is michael@0: // bound to an RDF container. Add each member as a new michael@0: // instantiation. michael@0: nsCOMPtr elements; michael@0: rv = rdfcontainer->GetElements(getter_AddRefs(elements)); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: while (1) { michael@0: bool hasmore; michael@0: rv = elements->HasMoreElements(&hasmore); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: if (! hasmore) michael@0: break; michael@0: michael@0: nsCOMPtr isupports; michael@0: rv = elements->GetNext(getter_AddRefs(isupports)); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: nsCOMPtr node = do_QueryInterface(isupports); michael@0: if (! node) michael@0: return NS_ERROR_UNEXPECTED; michael@0: michael@0: #ifdef PR_LOGGING michael@0: if (PR_LOG_TEST(gXULTemplateLog, PR_LOG_DEBUG)) { michael@0: nsAutoString member; michael@0: nsXULContentUtils::GetTextForNode(node, member); michael@0: michael@0: PR_LOG(gXULTemplateLog, PR_LOG_DEBUG, michael@0: (" member => %s", NS_ConvertUTF16toUTF8(member).get())); michael@0: } michael@0: #endif michael@0: michael@0: Instantiation newinst = *inst; michael@0: newinst.AddAssignment(mMemberVariable, node); michael@0: michael@0: Element* element = michael@0: new nsRDFConMemberTestNode::Element(containerRes, node); michael@0: michael@0: if (! element) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: michael@0: newinst.AddSupportingElement(element); michael@0: aInstantiations.Insert(inst, newinst); michael@0: } michael@0: } michael@0: michael@0: if (hasMemberBinding) { michael@0: // Oh, this is so nasty. If we have a member assignment, then michael@0: // grovel through each one of our inbound arcs to see if michael@0: // any of them are ordinal properties (like an RDF michael@0: // container might have). If so, walk it backwards to get michael@0: // the container we're in. michael@0: nsCOMPtr arcsin; michael@0: rv = ds->ArcLabelsIn(memberValue, getter_AddRefs(arcsin)); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: while (1) { michael@0: nsCOMPtr property; michael@0: michael@0: { michael@0: bool hasmore; michael@0: rv = arcsin->HasMoreElements(&hasmore); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: if (! hasmore) michael@0: break; michael@0: michael@0: nsCOMPtr isupports; michael@0: rv = arcsin->GetNext(getter_AddRefs(isupports)); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: property = do_QueryInterface(isupports); michael@0: if (! property) michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: // Ordinal properties automagically indicate container michael@0: // membership as far as we're concerned. Note that michael@0: // we're *only* concerned with ordinal properties michael@0: // here: the next block will worry about the other michael@0: // membership properties. michael@0: bool isordinal; michael@0: rv = rdfc->IsOrdinalProperty(property, &isordinal); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: if (isordinal) { michael@0: // If we get here, we've found a property that michael@0: // indicates container membership leading *into* a michael@0: // member node. Find all the people that point to michael@0: // it, and call them containers. michael@0: nsCOMPtr sources; michael@0: rv = ds->GetSources(property, memberValue, true, michael@0: getter_AddRefs(sources)); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: while (1) { michael@0: bool hasmore; michael@0: rv = sources->HasMoreElements(&hasmore); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: if (! hasmore) michael@0: break; michael@0: michael@0: nsCOMPtr isupports; michael@0: rv = sources->GetNext(getter_AddRefs(isupports)); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: nsCOMPtr source = do_QueryInterface(isupports); michael@0: if (! source) michael@0: return NS_ERROR_UNEXPECTED; michael@0: michael@0: #ifdef PR_LOGGING michael@0: if (PR_LOG_TEST(gXULTemplateLog, PR_LOG_DEBUG)) { michael@0: const char* container; michael@0: source->GetValueConst(&container); michael@0: michael@0: PR_LOG(gXULTemplateLog, PR_LOG_DEBUG, michael@0: (" container => %s", container)); michael@0: } michael@0: #endif michael@0: michael@0: // Add a new instantiation michael@0: Instantiation newinst = *inst; michael@0: newinst.AddAssignment(mContainerVariable, source); michael@0: michael@0: Element* element = michael@0: new nsRDFConMemberTestNode::Element(source, michael@0: memberValue); michael@0: michael@0: if (! element) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: michael@0: newinst.AddSupportingElement(element); michael@0: michael@0: aInstantiations.Insert(inst, newinst); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: if ((hasContainerBinding && ! hasMemberBinding) || michael@0: (! hasContainerBinding && hasMemberBinding)) { michael@0: // it's an open ended query on the container or member. go michael@0: // through our containment properties to see if anything michael@0: // applies. michael@0: nsResourceSet& containmentProps = mProcessor->ContainmentProperties(); michael@0: for (nsResourceSet::ConstIterator property = containmentProps.First(); michael@0: property != containmentProps.Last(); michael@0: ++property) { michael@0: nsCOMPtr results; michael@0: if (hasContainerBinding) { michael@0: rv = ds->GetTargets(containerRes, *property, true, michael@0: getter_AddRefs(results)); michael@0: } michael@0: else { michael@0: rv = ds->GetSources(*property, memberValue, true, michael@0: getter_AddRefs(results)); michael@0: } michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: while (1) { michael@0: bool hasmore; michael@0: rv = results->HasMoreElements(&hasmore); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: if (! hasmore) michael@0: break; michael@0: michael@0: nsCOMPtr isupports; michael@0: rv = results->GetNext(getter_AddRefs(isupports)); michael@0: if (NS_FAILED(rv)) return rv; michael@0: michael@0: nsIAtom* variable; michael@0: nsCOMPtr value; michael@0: nsCOMPtr valueRes; michael@0: michael@0: if (hasContainerBinding) { michael@0: variable = mMemberVariable; michael@0: michael@0: value = do_QueryInterface(isupports); michael@0: NS_ASSERTION(value != nullptr, "member is not an nsIRDFNode"); michael@0: if (! value) continue; michael@0: michael@0: #ifdef PR_LOGGING michael@0: if (PR_LOG_TEST(gXULTemplateLog, PR_LOG_DEBUG)) { michael@0: nsAutoString s; michael@0: nsXULContentUtils::GetTextForNode(value, s); michael@0: michael@0: PR_LOG(gXULTemplateLog, PR_LOG_DEBUG, michael@0: (" member => %s", NS_ConvertUTF16toUTF8(s).get())); michael@0: } michael@0: #endif michael@0: } michael@0: else { michael@0: variable = mContainerVariable; michael@0: michael@0: valueRes = do_QueryInterface(isupports); michael@0: NS_ASSERTION(valueRes != nullptr, "container is not an nsIRDFResource"); michael@0: if (! valueRes) continue; michael@0: michael@0: value = valueRes; michael@0: michael@0: #ifdef PR_LOGGING michael@0: if (PR_LOG_TEST(gXULTemplateLog, PR_LOG_DEBUG)) { michael@0: const char* s; michael@0: valueRes->GetValueConst(&s); michael@0: michael@0: PR_LOG(gXULTemplateLog, PR_LOG_DEBUG, michael@0: (" container => %s", s)); michael@0: } michael@0: #endif michael@0: } michael@0: michael@0: // Copy the original instantiation, and add it to the michael@0: // instantiation set with the new assignment that we've michael@0: // introduced. Ownership will be transferred to the michael@0: Instantiation newinst = *inst; michael@0: newinst.AddAssignment(variable, value); michael@0: michael@0: Element* element; michael@0: if (hasContainerBinding) { michael@0: element = michael@0: new nsRDFConMemberTestNode::Element(containerRes, value); michael@0: } michael@0: else { michael@0: element = michael@0: new nsRDFConMemberTestNode::Element(valueRes, memberValue); michael@0: } michael@0: michael@0: if (! element) michael@0: return NS_ERROR_OUT_OF_MEMORY; michael@0: michael@0: newinst.AddSupportingElement(element); michael@0: michael@0: aInstantiations.Insert(inst, newinst); michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (! hasContainerBinding && ! hasMemberBinding) { michael@0: // Neither container nor member assignment! michael@0: if (!aCantHandleYet) { michael@0: nsXULContentUtils::LogTemplateError(ERROR_TEMPLATE_MEMBER_UNBOUND); michael@0: return NS_ERROR_UNEXPECTED; michael@0: } michael@0: michael@0: *aCantHandleYet = true; michael@0: return NS_OK; michael@0: } michael@0: michael@0: // finally, remove the "under specified" instantiation. michael@0: aInstantiations.Erase(inst--); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: bool michael@0: nsRDFConMemberTestNode::CanPropagate(nsIRDFResource* aSource, michael@0: nsIRDFResource* aProperty, michael@0: nsIRDFNode* aTarget, michael@0: Instantiation& aInitialBindings) const michael@0: { michael@0: nsresult rv; michael@0: michael@0: bool canpropagate = false; michael@0: michael@0: nsCOMPtr rdfc = michael@0: do_GetService("@mozilla.org/rdf/container-utils;1"); michael@0: michael@0: if (! rdfc) michael@0: return false; michael@0: michael@0: // We can certainly propagate ordinal properties michael@0: rv = rdfc->IsOrdinalProperty(aProperty, &canpropagate); michael@0: if (NS_FAILED(rv)) return false; michael@0: michael@0: if (! canpropagate) { michael@0: canpropagate = mProcessor->ContainmentProperties().Contains(aProperty); michael@0: } michael@0: michael@0: #ifdef PR_LOGGING michael@0: if (PR_LOG_TEST(gXULTemplateLog, PR_LOG_DEBUG)) { michael@0: const char* source; michael@0: aSource->GetValueConst(&source); michael@0: michael@0: const char* property; michael@0: aProperty->GetValueConst(&property); michael@0: michael@0: nsAutoString target; michael@0: nsXULContentUtils::GetTextForNode(aTarget, target); michael@0: michael@0: PR_LOG(gXULTemplateLog, PR_LOG_DEBUG, michael@0: ("nsRDFConMemberTestNode[%p]: CanPropagate([%s]==[%s]=>[%s]) => %s", michael@0: this, source, property, NS_ConvertUTF16toUTF8(target).get(), michael@0: canpropagate ? "true" : "false")); michael@0: } michael@0: #endif michael@0: michael@0: if (canpropagate) { michael@0: aInitialBindings.AddAssignment(mContainerVariable, aSource); michael@0: aInitialBindings.AddAssignment(mMemberVariable, aTarget); michael@0: return true; michael@0: } michael@0: michael@0: return false; michael@0: } michael@0: michael@0: void michael@0: nsRDFConMemberTestNode::Retract(nsIRDFResource* aSource, michael@0: nsIRDFResource* aProperty, michael@0: nsIRDFNode* aTarget) const michael@0: { michael@0: bool canretract = false; michael@0: michael@0: nsCOMPtr rdfc = michael@0: do_GetService("@mozilla.org/rdf/container-utils;1"); michael@0: michael@0: if (! rdfc) michael@0: return; michael@0: michael@0: // We can certainly retract ordinal properties michael@0: nsresult rv; michael@0: rv = rdfc->IsOrdinalProperty(aProperty, &canretract); michael@0: if (NS_FAILED(rv)) return; michael@0: michael@0: if (! canretract) { michael@0: canretract = mProcessor->ContainmentProperties().Contains(aProperty); michael@0: } michael@0: michael@0: if (canretract) { michael@0: mProcessor->RetractElement(Element(aSource, aTarget)); michael@0: } michael@0: }