/* -*- 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 the rule network classes. To Do. - Constrain() & Propagate() still feel like they are poorly named. - As do Instantiation and InstantiationSet. - Make InstantiationSet share and do copy-on-write. - Make things iterative, instead of recursive. */ #include "nscore.h" #include "nsCOMPtr.h" #include "plhash.h" #include "mozilla/Logging.h" #include "nsString.h" #include "nsUnicharUtils.h" #include "nsXULContentUtils.h" #include "nsRuleNetwork.h" #include "nsXULTemplateResultSetRDF.h" #include "nsRDFConMemberTestNode.h" #include "nsRDFPropertyTestNode.h" using namespace mozilla; extern LazyLogModule gXULTemplateLog; //---------------------------------------------------------------------- // // nsRuleNetwork // nsresult MemoryElementSet::Add(MemoryElement* aElement) { for (ConstIterator element = First(); element != Last(); ++element) { if (*element == *aElement) { // We've already got this element covered. Since Add() // assumes ownership, and we aren't going to need this, // just nuke it. delete aElement; return NS_OK; } } List* list = new List; list->mElement = aElement; list->mRefCnt = 1; list->mNext = mElements; mElements = list; return NS_OK; } //---------------------------------------------------------------------- nsresult nsAssignmentSet::Add(const nsAssignment& aAssignment) { NS_PRECONDITION(! HasAssignmentFor(aAssignment.mVariable), "variable already bound"); // XXXndeakin should this just silently fail? if (HasAssignmentFor(aAssignment.mVariable)) return NS_ERROR_UNEXPECTED; List* list = new List(aAssignment); list->mRefCnt = 1; list->mNext = mAssignments; mAssignments = list; return NS_OK; } int32_t nsAssignmentSet::Count() const { int32_t count = 0; for (ConstIterator assignment = First(); assignment != Last(); ++assignment) ++count; return count; } bool nsAssignmentSet::HasAssignment(nsIAtom* aVariable, nsIRDFNode* aValue) const { for (ConstIterator assignment = First(); assignment != Last(); ++assignment) { if (assignment->mVariable == aVariable && assignment->mValue == aValue) return true; } return false; } bool nsAssignmentSet::HasAssignmentFor(nsIAtom* aVariable) const { for (ConstIterator assignment = First(); assignment != Last(); ++assignment) { if (assignment->mVariable == aVariable) return true; } return false; } bool nsAssignmentSet::GetAssignmentFor(nsIAtom* aVariable, nsIRDFNode** aValue) const { for (ConstIterator assignment = First(); assignment != Last(); ++assignment) { if (assignment->mVariable == aVariable) { *aValue = assignment->mValue; NS_IF_ADDREF(*aValue); return true; } } *aValue = nullptr; return false; } bool nsAssignmentSet::Equals(const nsAssignmentSet& aSet) const { if (aSet.mAssignments == mAssignments) return true; // If they have a different number of assignments, then they're different. if (Count() != aSet.Count()) return false; // XXX O(n^2)! Ugh! nsCOMPtr value; for (ConstIterator assignment = First(); assignment != Last(); ++assignment) { if (! aSet.GetAssignmentFor(assignment->mVariable, getter_AddRefs(value))) return false; if (assignment->mValue != value) return false; } return true; } //---------------------------------------------------------------------- PLHashNumber Instantiation::Hash(const void* aKey) { const Instantiation* inst = static_cast(aKey); PLHashNumber result = 0; nsAssignmentSet::ConstIterator last = inst->mAssignments.Last(); for (nsAssignmentSet::ConstIterator assignment = inst->mAssignments.First(); assignment != last; ++assignment) result ^= assignment->Hash(); return result; } int Instantiation::Compare(const void* aLeft, const void* aRight) { const Instantiation* left = static_cast(aLeft); const Instantiation* right = static_cast(aRight); return *left == *right; } //---------------------------------------------------------------------- // // InstantiationSet // InstantiationSet::InstantiationSet() { mHead.mPrev = mHead.mNext = &mHead; MOZ_COUNT_CTOR(InstantiationSet); } InstantiationSet::InstantiationSet(const InstantiationSet& aInstantiationSet) { mHead.mPrev = mHead.mNext = &mHead; // XXX replace with copy-on-write foo ConstIterator last = aInstantiationSet.Last(); for (ConstIterator inst = aInstantiationSet.First(); inst != last; ++inst) Append(*inst); MOZ_COUNT_CTOR(InstantiationSet); } InstantiationSet& InstantiationSet::operator=(const InstantiationSet& aInstantiationSet) { // XXX replace with copy-on-write foo Clear(); ConstIterator last = aInstantiationSet.Last(); for (ConstIterator inst = aInstantiationSet.First(); inst != last; ++inst) Append(*inst); return *this; } void InstantiationSet::Clear() { Iterator inst = First(); while (inst != Last()) Erase(inst++); } InstantiationSet::Iterator InstantiationSet::Insert(Iterator aIterator, const Instantiation& aInstantiation) { List* newelement = new List(); if (newelement) { newelement->mInstantiation = aInstantiation; aIterator.mCurrent->mPrev->mNext = newelement; newelement->mNext = aIterator.mCurrent; newelement->mPrev = aIterator.mCurrent->mPrev; aIterator.mCurrent->mPrev = newelement; } return aIterator; } InstantiationSet::Iterator InstantiationSet::Erase(Iterator aIterator) { Iterator result = aIterator; ++result; aIterator.mCurrent->mNext->mPrev = aIterator.mCurrent->mPrev; aIterator.mCurrent->mPrev->mNext = aIterator.mCurrent->mNext; delete aIterator.mCurrent; return result; } bool InstantiationSet::HasAssignmentFor(nsIAtom* aVariable) const { return !Empty() ? First()->mAssignments.HasAssignmentFor(aVariable) : false; } //---------------------------------------------------------------------- // // ReteNode // // The basic node in the network. // //---------------------------------------------------------------------- // // TestNode // // to do: // - FilterInstantiations() is poorly named // TestNode::TestNode(TestNode* aParent) : mParent(aParent) { } nsresult TestNode::Propagate(InstantiationSet& aInstantiations, bool aIsUpdate, bool& aTakenInstantiations) { MOZ_LOG(gXULTemplateLog, LogLevel::Debug, ("TestNode[%p]: Propagate() begin", this)); aTakenInstantiations = false; nsresult rv = FilterInstantiations(aInstantiations, nullptr); if (NS_FAILED(rv)) return rv; // if there is more than one child, each will need to be supplied with the // original set of instantiations from this node, so create a copy in this // case. If there is only one child, optimize and just pass the // instantiations along to the child without copying bool shouldCopy = (mKids.Count() > 1); // See the header file for details about how instantiation ownership works. if (! aInstantiations.Empty()) { ReteNodeSet::Iterator last = mKids.Last(); for (ReteNodeSet::Iterator kid = mKids.First(); kid != last; ++kid) { MOZ_LOG(gXULTemplateLog, LogLevel::Debug, ("TestNode[%p]: Propagate() passing to child %p", this, kid.operator->())); // create a copy of the instantiations if (shouldCopy) { bool owned = false; InstantiationSet* instantiations = new InstantiationSet(aInstantiations); rv = kid->Propagate(*instantiations, aIsUpdate, owned); if (!owned) delete instantiations; if (NS_FAILED(rv)) return rv; } else { rv = kid->Propagate(aInstantiations, aIsUpdate, aTakenInstantiations); if (NS_FAILED(rv)) return rv; } } } MOZ_LOG(gXULTemplateLog, LogLevel::Debug, ("TestNode[%p]: Propagate() end", this)); return NS_OK; } nsresult TestNode::Constrain(InstantiationSet& aInstantiations) { nsresult rv; MOZ_LOG(gXULTemplateLog, LogLevel::Debug, ("TestNode[%p]: Constrain() begin", this)); // if the cantHandleYet flag is set by FilterInstantiations, // there isn't enough information yet available to fill in. // For this, continue the constrain all the way to the top // and then call FilterInstantiations again afterwards. This // should fill in any missing information. bool cantHandleYet = false; rv = FilterInstantiations(aInstantiations, &cantHandleYet); if (NS_FAILED(rv)) return rv; if (mParent && (!aInstantiations.Empty() || cantHandleYet)) { // if we still have instantiations, or if the instantiations // could not be filled in yet, then ride 'em on up to the // parent to narrow them. MOZ_LOG(gXULTemplateLog, LogLevel::Debug, ("TestNode[%p]: Constrain() passing to parent %p", this, mParent)); rv = mParent->Constrain(aInstantiations); if (NS_SUCCEEDED(rv) && cantHandleYet) rv = FilterInstantiations(aInstantiations, nullptr); } else { MOZ_LOG(gXULTemplateLog, LogLevel::Debug, ("TestNode[%p]: Constrain() failed", this)); rv = NS_OK; } MOZ_LOG(gXULTemplateLog, LogLevel::Debug, ("TestNode[%p]: Constrain() end", this)); return rv; } //---------------------------------------------------------------------- ReteNodeSet::ReteNodeSet() : mNodes(nullptr), mCount(0), mCapacity(0) { } ReteNodeSet::~ReteNodeSet() { Clear(); } nsresult ReteNodeSet::Add(ReteNode* aNode) { NS_PRECONDITION(aNode != nullptr, "null ptr"); if (! aNode) return NS_ERROR_NULL_POINTER; if (mCount >= mCapacity) { int32_t capacity = mCapacity + 4; ReteNode** nodes = new ReteNode*[capacity]; if (! nodes) return NS_ERROR_OUT_OF_MEMORY; for (int32_t i = mCount - 1; i >= 0; --i) nodes[i] = mNodes[i]; delete[] mNodes; mNodes = nodes; mCapacity = capacity; } mNodes[mCount++] = aNode; return NS_OK; } nsresult ReteNodeSet::Clear() { delete[] mNodes; mNodes = nullptr; mCount = mCapacity = 0; return NS_OK; }