From f812e01570aedd5033245a76846b5afc0063bc17 Mon Sep 17 00:00:00 2001 From: Benjamin Paassen Date: Wed, 11 Feb 2015 17:51:50 +0100 Subject: made isSubtree (fieldType) and primitivity orthogonal concepts: PRIMITIVE is no FieldType anymore. --- test/core/model/DomainTest.cpp | 66 ++++++++++++++++++++++-------------------- 1 file changed, 35 insertions(+), 31 deletions(-) (limited to 'test/core/model/DomainTest.cpp') diff --git a/test/core/model/DomainTest.cpp b/test/core/model/DomainTest.cpp index 32ef7f0..59062f0 100644 --- a/test/core/model/DomainTest.cpp +++ b/test/core/model/DomainTest.cpp @@ -145,9 +145,10 @@ TEST(Descriptor, pathToAdvanced) * 8.) E is transparent and has target as child in the default field * (longer path) * - * So the path start_field , E , E_field should be returned. + * So the path A_second_field, C, C_field should be returned. */ Manager mgr{1}; + Logger logger; Rooted sys{new SystemTypesystem(mgr)}; // Construct the domain Rooted domain{new Domain(mgr, sys, "nasty")}; @@ -162,8 +163,8 @@ TEST(Descriptor, pathToAdvanced) Rooted B{new StructuredClass( mgr, "B", domain, Cardinality::any(), {nullptr}, true, false)}; - Rooted C{ - new StructuredClass(mgr, "C", domain, Cardinality::any(), B, true, false)}; + Rooted C{new StructuredClass( + mgr, "C", domain, Cardinality::any(), B, true, false)}; Rooted D{new StructuredClass( mgr, "D", domain, Cardinality::any(), {nullptr}, true, false)}; @@ -175,31 +176,32 @@ TEST(Descriptor, pathToAdvanced) new StructuredClass(mgr, "target", domain, Cardinality::any())}; // We create two fields for A - Rooted A_field{new FieldDescriptor(mgr, A)}; + Rooted A_field = A->createFieldDescriptor(logger); A_field->addChild(target); - Rooted A_field2{new FieldDescriptor( - mgr, A, FieldDescriptor::FieldType::SUBTREE, "second")}; + Rooted A_field2 = A->createFieldDescriptor( + logger, FieldDescriptor::FieldType::SUBTREE, "second", false); A_field2->addChild(B); // We create no field for B // One for C - Rooted C_field{new FieldDescriptor(mgr, C)}; + Rooted C_field = C->createFieldDescriptor(logger); C_field->addChild(target); // one for start - Rooted start_field{new FieldDescriptor(mgr, start)}; + Rooted start_field = start->createFieldDescriptor(logger); start_field->addChild(D); // One for D - Rooted D_field{new FieldDescriptor(mgr, D)}; + Rooted D_field = D->createFieldDescriptor(logger); D_field->addChild(E); // One for E - Rooted E_field{new FieldDescriptor(mgr, E)}; + Rooted E_field = E->createFieldDescriptor(logger); E_field->addChild(target); + ASSERT_TRUE(domain->validate(logger)); #ifdef MANAGER_GRAPHVIZ_EXPORT // dump the manager state mgr.exportGraphviz("nastyDomain.dot"); @@ -313,8 +315,8 @@ TEST(Domain, validate) ASSERT_EQ(ValidationState::UNKNOWN, domain->getValidationState()); ASSERT_TRUE(domain->validate(logger)); // Let's add a primitive field (without a primitive type at first) - Rooted base_field{ - new FieldDescriptor(mgr, base, nullptr)}; + Rooted base_field = + base->createPrimitiveFieldDescriptor(nullptr, logger); // this should not be valid. ASSERT_EQ(ValidationState::UNKNOWN, domain->getValidationState()); ASSERT_FALSE(domain->validate(logger)); @@ -322,13 +324,6 @@ TEST(Domain, validate) base_field->setPrimitiveType(sys->getStringType()); ASSERT_EQ(ValidationState::UNKNOWN, domain->getValidationState()); ASSERT_TRUE(domain->validate(logger)); - // not anymore, however, if we tamper with the FieldType. - base_field->setFieldType(FieldDescriptor::FieldType::TREE); - ASSERT_EQ(ValidationState::UNKNOWN, domain->getValidationState()); - ASSERT_FALSE(domain->validate(logger)); - base_field->setFieldType(FieldDescriptor::FieldType::PRIMITIVE); - ASSERT_EQ(ValidationState::UNKNOWN, domain->getValidationState()); - ASSERT_TRUE(domain->validate(logger)); // add a subclass for our base class. Rooted sub{new StructuredClass(mgr, "sub", domain)}; // this should be valid in itself. @@ -338,7 +333,7 @@ TEST(Domain, validate) sub->setSuperclass(base, logger); ASSERT_EQ(ValidationState::UNKNOWN, domain->getValidationState()); ASSERT_TRUE(domain->validate(logger)); - // and still we we remove the subclass from the base class. + // and still if we remove the subclass from the base class. base->removeSubclass(sub, logger); ASSERT_EQ(ValidationState::UNKNOWN, domain->getValidationState()); ASSERT_TRUE(domain->validate(logger)); @@ -349,19 +344,11 @@ TEST(Domain, validate) ASSERT_TRUE(domain->validate(logger)); ASSERT_EQ(base, sub->getSuperclass()); // add a non-primitive field to the child class. - Rooted sub_field{new FieldDescriptor(mgr, sub)}; - // this should be valid - ASSERT_EQ(ValidationState::UNKNOWN, domain->getValidationState()); - ASSERT_TRUE(domain->validate(logger)); - // .. until we set a primitive type. - sub_field->setPrimitiveType(sys->getStringType()); + Rooted sub_field = sub->createFieldDescriptor(logger); + // this should not be valid because we allow no children. ASSERT_EQ(ValidationState::UNKNOWN, domain->getValidationState()); ASSERT_FALSE(domain->validate(logger)); - // and valid again if we unset it. - sub_field->setPrimitiveType(nullptr); - ASSERT_EQ(ValidationState::UNKNOWN, domain->getValidationState()); - ASSERT_TRUE(domain->validate(logger)); - // we should also be able to add a child and have it still be valid. + // we should also be able to add a child and make it valid. sub_field->addChild(base); ASSERT_EQ(ValidationState::UNKNOWN, domain->getValidationState()); ASSERT_TRUE(domain->validate(logger)); @@ -373,6 +360,23 @@ TEST(Domain, validate) sub_field->removeChild(base); ASSERT_EQ(ValidationState::UNKNOWN, domain->getValidationState()); ASSERT_TRUE(domain->validate(logger)); + // if we set a primitive type it should be invalid + sub_field->setPrimitiveType(sys->getStringType()); + ASSERT_EQ(ValidationState::UNKNOWN, domain->getValidationState()); + ASSERT_FALSE(domain->validate(logger)); + // and valid again if we unset it. + sub_field->setPrimitiveType(nullptr); + ASSERT_EQ(ValidationState::UNKNOWN, domain->getValidationState()); + ASSERT_TRUE(domain->validate(logger)); + // It should be invalid if we set another TREE field. + Rooted sub_field2 = sub->createFieldDescriptor( + logger, FieldDescriptor::FieldType::TREE, "test", false); + ASSERT_EQ(ValidationState::UNKNOWN, domain->getValidationState()); + ASSERT_FALSE(domain->validate(logger)); + // but valid again if we remove it + sub->removeFieldDescriptor(sub_field2); + ASSERT_EQ(ValidationState::UNKNOWN, domain->getValidationState()); + ASSERT_TRUE(domain->validate(logger)); } } } -- cgit v1.2.3 From 2f75ac166594b6bc2ea30901669304eca23174ec Mon Sep 17 00:00:00 2001 From: Benjamin Paassen Date: Wed, 11 Feb 2015 17:54:48 +0100 Subject: changed semantics of default field, now referring to the only TREE field. --- src/core/model/Domain.cpp | 133 +++++++++++++++----- src/core/model/Domain.hpp | 246 +++++++++++++------------------------ src/plugins/html/DemoOutput.cpp | 2 +- src/plugins/xml/XmlParser.cpp | 8 +- test/core/model/DomainTest.cpp | 2 +- test/core/model/TestAdvanced.hpp | 14 +-- test/plugins/xml/XmlParserTest.cpp | 6 +- 7 files changed, 209 insertions(+), 202 deletions(-) (limited to 'test/core/model/DomainTest.cpp') diff --git a/src/core/model/Domain.cpp b/src/core/model/Domain.cpp index 787f1ff..3fb525f 100644 --- a/src/core/model/Domain.cpp +++ b/src/core/model/Domain.cpp @@ -173,20 +173,38 @@ bool Descriptor::doValidate(Logger &logger) const } // ensure that no attribute with the key "name" exists. if (attributesDescriptor == nullptr) { - logger.error("This Descriptor has no Attribute specification!"); + logger.error(std::string("Descriptor \"") + getName() + + "\" has no Attribute specification!"); valid = false; } else { if (attributesDescriptor->hasAttribute("name")) { logger.error( - "This Descriptor has an attribute \"name\" which is a reserved " - "word!"); + std::string("Descriptor \"") + getName() + + "\" has an attribute \"name\" which is a reserved word!"); valid = false; } valid = valid & attributesDescriptor->validate(logger); } + // check that only one FieldDescriptor is of type TREE. + auto fds = Descriptor::getFieldDescriptors(); + bool hasTREE = false; + for (auto fd : fds) { + if (fd->getFieldType() == FieldDescriptor::FieldType::TREE) { + if (!hasTREE) { + hasTREE = true; + } else { + logger.error( + std::string("Descriptor \"") + getName() + + "\" has multiple TREE fields, which is not permitted", + *fd); + valid = false; + break; + } + } + } // check attributes and the FieldDescriptors - return valid & continueValidationCheckDuplicates(fieldDescriptors, logger); + return valid & continueValidationCheckDuplicates(fds, logger); } std::vector> Descriptor::pathTo( @@ -259,18 +277,39 @@ bool Descriptor::continuePath(Handle target, return found; } -ssize_t Descriptor::getFieldDescriptorIndex(const std::string &name) const +static ssize_t getFieldDescriptorIndex(const NodeVector &fds, + const std::string &name) { - size_t f = 0; - for (auto &fd : getFieldDescriptors()) { - if (fd->getName() == name) { + if (fds.empty()) { + return -1; + } + + if (name == DEFAULT_FIELD_NAME) { + if (fds.back()->getFieldType() == FieldDescriptor::FieldType::TREE) { + return fds.size() - 1; + } else { + /* The last field has to be the TREE field. If the last field does + * not have the FieldType TREE no TREE-field exists at all. So we + * return -1. + */ + return -1; + } + } + + for (size_t f = 0; f < fds.size(); f++) { + if (fds[f]->getName() == name) { return f; } - f++; } return -1; } +ssize_t Descriptor::getFieldDescriptorIndex(const std::string &name) const +{ + NodeVector fds = getFieldDescriptors(); + return ousia::getFieldDescriptorIndex(fds, name); +} + ssize_t Descriptor::getFieldDescriptorIndex(Handle fd) const { size_t f = 0; @@ -286,33 +325,54 @@ ssize_t Descriptor::getFieldDescriptorIndex(Handle fd) const Rooted Descriptor::getFieldDescriptor( const std::string &name) const { - for (auto &fd : getFieldDescriptors()) { - if (fd->getName() == name) { - return fd; - } + NodeVector fds = getFieldDescriptors(); + ssize_t idx = ousia::getFieldDescriptorIndex(fds, name); + if (idx != -1) { + return fds[idx]; + } else { + return nullptr; } - return nullptr; } -void Descriptor::addFieldDescriptor(Handle fd) +void Descriptor::addAndSortFieldDescriptor(Handle fd, + Logger &logger) { // only add it if we need to. - if (fieldDescriptors.find(fd) == fieldDescriptors.end()) { + auto fds = getFieldDescriptors(); + if (fds.find(fd) == fds.end()) { invalidate(); - fieldDescriptors.push_back(fd); + // check if the previous field is a tree field already. + if (!fds.empty() && + fds.back()->getFieldType() == FieldDescriptor::FieldType::TREE && + fd->getFieldType() != FieldDescriptor::FieldType::TREE) { + // if so we add the new field before the TREE field and log a + // warning. + + logger.warning( + std::string("Field \"") + fd->getName() + + "\" was declared after main field \"" + + fds.back()->getName() + + "\". The order of fields was changed to make the " + "main field the last field.", + *fd); + fieldDescriptors.insert(fieldDescriptors.end() - 1, fd); + } else { + fieldDescriptors.push_back(fd); + } } +} + +void Descriptor::addFieldDescriptor(Handle fd, Logger &logger) +{ + addAndSortFieldDescriptor(fd, logger); if (fd->getParent() == nullptr) { fd->setParent(this); } } -void Descriptor::moveFieldDescriptor(Handle fd) +void Descriptor::moveFieldDescriptor(Handle fd, Logger &logger) { - // only add it if we need to. - if (fieldDescriptors.find(fd) == fieldDescriptors.end()) { - invalidate(); - fieldDescriptors.push_back(fd); - } + addAndSortFieldDescriptor(fd, logger); Handle par = fd->getParent(); if (par != this) { if (par != nullptr) { @@ -494,18 +554,35 @@ void StructuredClass::removeSubclass(Handle sc, Logger &logger) sc->setSuperclass(nullptr, logger); } -const void StructuredClass::gatherFieldDescriptors( +void StructuredClass::gatherFieldDescriptors( NodeVector ¤t, - std::set &overriddenFields) const + std::set &overriddenFields, bool hasTREE) const { // append all FieldDescriptors that are not overridden. for (auto &f : Descriptor::getFieldDescriptors()) { if (overriddenFields.insert(f->getName()).second) { - current.push_back(f); + bool isTREE = f->getFieldType() == FieldDescriptor::FieldType::TREE; + if (hasTREE) { + if (!isTREE) { + /* + * If we already have a tree field it has to be at the end + * of the current vector. So ensure that all new non-TREE + * fields are inserted before the TREE field such that after + * this method the TREE field is still at the end. + */ + current.insert(current.end() - 1, f); + } + } else { + if (isTREE) { + hasTREE = true; + } + current.push_back(f); + } } } + // if we have a superclass, go there. if (superclass != nullptr) { - superclass->gatherFieldDescriptors(current, overriddenFields); + superclass->gatherFieldDescriptors(current, overriddenFields, hasTREE); } } @@ -514,7 +591,7 @@ NodeVector StructuredClass::getFieldDescriptors() const // in this case we return a NodeVector of Rooted entries without owner. NodeVector vec; std::set overriddenFields; - gatherFieldDescriptors(vec, overriddenFields); + gatherFieldDescriptors(vec, overriddenFields, false); return vec; } diff --git a/src/core/model/Domain.hpp b/src/core/model/Domain.hpp index 241c25d..43661c2 100644 --- a/src/core/model/Domain.hpp +++ b/src/core/model/Domain.hpp @@ -34,74 +34,46 @@ * * \code{.xml} * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * * * \endcode * * Note that we define one field as the TREE (meaning the main or default * document structure) and one mearly as SUBTREE, relating to supporting * information. You are not allowed to define more than one field of type - * "TREE". Accordingly for each StructuredClass in the main TREE there must be - * at least one possible primitive child or one TREE field. Otherwise the - * grammar would be nonterminal. For SUBTREE fields no children may define a - * TREE field and at least one permitted child must exist, either primitive or - * as another StructuredClass. + * "TREE". * - * The translation to context free grammars is as follows: + * The translation to a context free grammar is as follows: * * \code{.txt} * BOOK := BOOK_TREE @@ -128,21 +100,14 @@ * * \code{.xml} * - * - * - * - * - * - * - * - * - * - * ... - * - * - * - * - * + * + * + * + * + * + * ... + * + * * * \endcode * @@ -161,36 +126,36 @@ * * \code{.xml} * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * - * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * + * * * \endcode * @@ -227,25 +192,15 @@ static const std::string DEFAULT_FIELD_NAME = "$default"; * accordingly typed content without further descending in the Structure * Hierarchy. * - * As an example consider the "paragraph" StructuredClass, which might allow + * As an example consider the "text" StructuredClass, which might allow * the actual text content. Here is the according XML: * * \code{.xml} - * - * - * - * - * - * - * - * - * + * + * + * * \endcode * - * Accordingly the primitiveType field of a FieldDescriptor may only be - * defined if the type is set to "PRIMITIVE". If the type is something else - * at least one child must be defined and the primitiveType remains in an - * undefined state. */ class FieldDescriptor : public Node { friend Descriptor; @@ -370,11 +325,11 @@ public: } /** - * Returns true if and only if the type of this field is PRIMITIVE. + * Returns if this field is primitive. * - * @return true if and only if the type of this field is PRIMITIVE. + * @return true if and only if this field is primitive. */ - bool isPrimitive() const { return fieldType == FieldType::PRIMITIVE; } + bool isPrimitive() const { return primitive; } /** * Returns the primitive type of this field, which is only allowed to be @@ -449,8 +404,10 @@ private: Owned attributesDescriptor; NodeVector fieldDescriptors; - bool continuePath(Handle target, - std::vector> &path) const; + bool continuePath(Handle target, NodeVector &path, + bool start) const; + + void addAndSortFieldDescriptor(Handle fd, Logger &logger); protected: void doResolve(ResolutionState &state) override; @@ -539,20 +496,7 @@ public: * * @param fd is a FieldDescriptor. */ - void addFieldDescriptor(Handle fd); - - /** - * Adds the given FieldDescriptors to this Descriptor. This also sets the - * parent of each given FieldDescriptor if it is not set yet. - * - * @param fds are FieldDescriptors. - */ - void addFieldDescriptors(const std::vector> &fds) - { - for (Handle fd : fds) { - addFieldDescriptor(fd); - } - } + void addFieldDescriptor(Handle fd, Logger &logger); /** * Adds the given FieldDescriptor to this Descriptor. This also sets the @@ -561,21 +505,7 @@ public: * * @param fd is a FieldDescriptor. */ - void moveFieldDescriptor(Handle fd); - - /** - * Adds the given FieldDescriptors to this Descriptor. This also sets the - * parent of each given FieldDescriptor if it is not set to this Descriptor - * already and removes it from the old parent Descriptor. - * - * @param fds are FieldDescriptors. - */ - void moveFieldDescriptors(const std::vector> &fds) - { - for (Handle fd : fds) { - moveFieldDescriptor(fd); - } - } + void moveFieldDescriptor(Handle fd, Logger &logger); /** * Copies a FieldDescriptor that belongs to another Descriptor to this @@ -583,7 +513,7 @@ public: * * @param fd some FieldDescriptor belonging to another Descriptor. */ - void copyFieldDescriptor(Handle fd); + void copyFieldDescriptor(Handle fd, Logger &logger); /** * Removes the given FieldDescriptor from this Descriptor. This also sets @@ -751,9 +681,9 @@ private: /** * Helper method for getFieldDescriptors. */ - const void gatherFieldDescriptors( - NodeVector ¤t, - std::set &overriddenFields) const; + void gatherFieldDescriptors(NodeVector ¤t, + std::set &overriddenFields, + bool hasTREE) const; protected: bool doValidate(Logger &logger) const override; diff --git a/src/plugins/html/DemoOutput.cpp b/src/plugins/html/DemoOutput.cpp index d041c1d..cb34cbe 100644 --- a/src/plugins/html/DemoOutput.cpp +++ b/src/plugins/html/DemoOutput.cpp @@ -324,7 +324,7 @@ Rooted DemoHTMLTransformer::transformParagraph( if (childDescriptorName == "text") { Handle primitive = t->getField()[0].cast(); - if (primitive.isNull()) { + if (primitive == nullptr) { throw OusiaException("Text field is not primitive!"); } current->addChild(new xml::Text( diff --git a/src/plugins/xml/XmlParser.cpp b/src/plugins/xml/XmlParser.cpp index 22498f4..d7efa4d 100644 --- a/src/plugins/xml/XmlParser.cpp +++ b/src/plugins/xml/XmlParser.cpp @@ -603,7 +603,7 @@ public: [](Handle field, Handle parent, Logger &logger) { if (field != nullptr) { parent.cast()->addFieldDescriptor( - field.cast()); + field.cast(), logger); } }); } @@ -969,7 +969,7 @@ static const ParserState DomainField = .parents({&DomainStruct, &DomainAnnotation}) .createdNodeType(&RttiTypes::FieldDescriptor) .elementHandler(DomainFieldHandler::create) - .arguments({Argument::String("name", DEFAULT_FIELD_NAME), + .arguments({Argument::String("name", ""), Argument::Bool("isSubtree", false), Argument::Bool("optional", false)}); @@ -985,7 +985,7 @@ static const ParserState DomainStructPrimitive = .parents({&DomainStruct, &DomainAnnotation}) .createdNodeType(&RttiTypes::FieldDescriptor) .elementHandler(DomainPrimitiveHandler::create) - .arguments({Argument::String("name", DEFAULT_FIELD_NAME), + .arguments({Argument::String("name", ""), Argument::Bool("isSubtree", false), Argument::Bool("optional", false), Argument::String("type")}); @@ -1008,7 +1008,7 @@ static const ParserState DomainStructParentField = .parent(&DomainStructParent) .createdNodeType(&RttiTypes::FieldDescriptor) .elementHandler(DomainParentFieldHandler::create) - .arguments({Argument::String("name", DEFAULT_FIELD_NAME), + .arguments({Argument::String("name", ""), Argument::Bool("isSubtree", false), Argument::Bool("optional", false)}); diff --git a/test/core/model/DomainTest.cpp b/test/core/model/DomainTest.cpp index 59062f0..79b62f0 100644 --- a/test/core/model/DomainTest.cpp +++ b/test/core/model/DomainTest.cpp @@ -215,7 +215,7 @@ TEST(Descriptor, pathToAdvanced) ASSERT_TRUE(path[1]->isa(&RttiTypes::StructuredClass)); ASSERT_EQ("B", path[1]->getName()); ASSERT_TRUE(path[2]->isa(&RttiTypes::FieldDescriptor)); - ASSERT_EQ("$default", path[2]->getName()); + ASSERT_EQ("", path[2]->getName()); } TEST(StructuredClass, isSubclassOf) diff --git a/test/core/model/TestAdvanced.hpp b/test/core/model/TestAdvanced.hpp index 9c95400..575860b 100644 --- a/test/core/model/TestAdvanced.hpp +++ b/test/core/model/TestAdvanced.hpp @@ -55,11 +55,11 @@ static Rooted constructHeadingDomain(Manager &mgr, Cardinality card; card.merge({0, 1}); // set up heading StructuredClass. - Rooted heading{new StructuredClass( - mgr, "heading", domain, card, {nullptr}, true)}; - // as field want to copy the field of paragraph. + Rooted heading{ + new StructuredClass(mgr, "heading", domain, card, {nullptr}, true)}; + // as field want to reference the field of paragraph. Rooted p = resolveDescriptor(bookDomain, "paragraph"); - heading->copyFieldDescriptor(p->getFieldDescriptors()[0]); + heading->addFieldDescriptor(p->getFieldDescriptor(), logger); // create a new field for headings in each section type. std::vector secclasses{"book", "section", "subsection", "paragraph"}; @@ -88,8 +88,8 @@ static Rooted constructListDomain(Manager &mgr, Rooted item{new StructuredClass( mgr, "item", domain, Cardinality::any(), {nullptr}, false)}; - // as field we want to copy the field of paragraph. - item->copyFieldDescriptor(p->getFieldDescriptors()[0]); + // as field we want to reference the field of paragraph. + item->addFieldDescriptor(p->getFieldDescriptor(), logger); // set up list StructuredClasses. std::vector listTypes{"ol", "ul"}; for (auto &listType : listTypes) { @@ -157,7 +157,7 @@ static bool addAnnotation(Logger &logger, Handle doc, Handle parent, const std::string &text, const std::string &annoClass) { - Manager& mgr = parent->getManager(); + Manager &mgr = parent->getManager(); Rooted start{new Anchor(mgr, std::to_string(annoIdx++), parent)}; if (!addText(logger, doc, parent, text)) { return false; diff --git a/test/plugins/xml/XmlParserTest.cpp b/test/plugins/xml/XmlParserTest.cpp index ef95552..7d5c697 100644 --- a/test/plugins/xml/XmlParserTest.cpp +++ b/test/plugins/xml/XmlParserTest.cpp @@ -181,7 +181,7 @@ static void checkFieldDescriptor( static void checkFieldDescriptor( Handle desc, Handle parent, NodeVector children, - const std::string &name = DEFAULT_FIELD_NAME, + const std::string &name = "", FieldDescriptor::FieldType type = FieldDescriptor::FieldType::TREE, Handle primitiveType = nullptr, bool optional = false) { @@ -193,7 +193,7 @@ static void checkFieldDescriptor( static void checkFieldDescriptor( Handle desc, NodeVector children, - const std::string &name = DEFAULT_FIELD_NAME, + const std::string &name = "", FieldDescriptor::FieldType type = FieldDescriptor::FieldType::TREE, Handle primitiveType = nullptr, bool optional = false) { @@ -244,7 +244,7 @@ TEST(XmlParser, domainParsing) checkFieldDescriptor(subsection, {paragraph}); checkFieldDescriptor(paragraph, {text}); checkFieldDescriptor( - text, {}, DEFAULT_FIELD_NAME, FieldDescriptor::FieldType::PRIMITIVE, + text, {}, "", FieldDescriptor::FieldType::TREE, env.project->getSystemTypesystem()->getStringType(), false); // check parent handling using the headings domain. -- cgit v1.2.3 From 7daed2f8431e89e2bd041a54bc1bef8c45329092 Mon Sep 17 00:00:00 2001 From: Benjamin Paassen Date: Wed, 11 Feb 2015 17:55:04 +0100 Subject: improved pathto --- src/core/model/Domain.cpp | 30 ++++++++++++++++++------------ src/core/model/Domain.hpp | 3 +-- test/core/model/DomainTest.cpp | 7 ++++--- 3 files changed, 23 insertions(+), 17 deletions(-) (limited to 'test/core/model/DomainTest.cpp') diff --git a/src/core/model/Domain.cpp b/src/core/model/Domain.cpp index 3fb525f..228348d 100644 --- a/src/core/model/Domain.cpp +++ b/src/core/model/Domain.cpp @@ -207,16 +207,15 @@ bool Descriptor::doValidate(Logger &logger) const return valid & continueValidationCheckDuplicates(fds, logger); } -std::vector> Descriptor::pathTo( - Handle target) const +NodeVector Descriptor::pathTo(Handle target) const { - std::vector> path; - continuePath(target, path); - return path; + NodeVector path; + continuePath(target, path, true); + return std::move(path); } bool Descriptor::continuePath(Handle target, - std::vector> ¤tPath) const + NodeVector ¤tPath, bool start) const { // check if we are at the target already if (this == target) { @@ -225,27 +224,34 @@ bool Descriptor::continuePath(Handle target, // a variable to determine if we already found a solution bool found = false; // the currently optimal path. - std::vector> optimum; + NodeVector optimum; // use recursive depth-first search from the top to reach the given child // get the list of effective FieldDescriptors. NodeVector fields = getFieldDescriptors(); + Rooted thisHandle{const_cast(this)}; + for (auto &fd : fields) { for (auto &c : fd->getChildren()) { // check if a child is the target node. if (c == target) { // if we have made the connection, stop the search. + if (!start) { + currentPath.push_back(thisHandle); + } currentPath.push_back(fd); return true; } // look for transparent intermediate nodes. if (c->isTransparent()) { // copy the path. - std::vector> cPath = currentPath; + NodeVector cPath = currentPath; + if (!start) { + cPath.push_back(thisHandle); + } cPath.push_back(fd); - cPath.push_back(c); // recursion. - if (c->continuePath(target, cPath) && + if (c->continuePath(target, cPath, false) && (!found || optimum.size() > cPath.size())) { // look if this path is better than the current optimum. optimum = std::move(cPath); @@ -260,8 +266,8 @@ bool Descriptor::continuePath(Handle target, // if this is a StructuredClass we also can call the subclasses. for (auto &c : tis->getSubclasses()) { // copy the path. - std::vector> cPath = currentPath; - if (c->continuePath(target, cPath) && + NodeVector cPath = currentPath; + if (c->continuePath(target, cPath, false) && (!found || optimum.size() > cPath.size())) { // look if this path is better than the current optimum. optimum = std::move(cPath); diff --git a/src/core/model/Domain.hpp b/src/core/model/Domain.hpp index 43661c2..57e5602 100644 --- a/src/core/model/Domain.hpp +++ b/src/core/model/Domain.hpp @@ -589,8 +589,7 @@ public: * no such path can be constructed. * */ - std::vector> pathTo( - Handle childDescriptor) const; + NodeVector pathTo(Handle childDescriptor) const; }; /* * TODO: We should discuss Cardinalities one more time. Is it smart to define diff --git a/test/core/model/DomainTest.cpp b/test/core/model/DomainTest.cpp index 79b62f0..2e20c3b 100644 --- a/test/core/model/DomainTest.cpp +++ b/test/core/model/DomainTest.cpp @@ -101,7 +101,7 @@ TEST(Descriptor, pathTo) Rooted book = getClass("book", domain); Rooted section = getClass("section", domain); // get the path in between. - std::vector> path = book->pathTo(section); + NodeVector path = book->pathTo(section); ASSERT_EQ(1U, path.size()); ASSERT_TRUE(path[0]->isa(&RttiTypes::FieldDescriptor)); @@ -202,18 +202,19 @@ TEST(Descriptor, pathToAdvanced) E_field->addChild(target); ASSERT_TRUE(domain->validate(logger)); + #ifdef MANAGER_GRAPHVIZ_EXPORT // dump the manager state mgr.exportGraphviz("nastyDomain.dot"); #endif // and now we should be able to find the shortest path as suggested - std::vector> path = start->pathTo(target); + NodeVector path = start->pathTo(target); ASSERT_EQ(3U, path.size()); ASSERT_TRUE(path[0]->isa(&RttiTypes::FieldDescriptor)); ASSERT_EQ("second", path[0]->getName()); ASSERT_TRUE(path[1]->isa(&RttiTypes::StructuredClass)); - ASSERT_EQ("B", path[1]->getName()); + ASSERT_EQ("C", path[1]->getName()); ASSERT_TRUE(path[2]->isa(&RttiTypes::FieldDescriptor)); ASSERT_EQ("", path[2]->getName()); } -- cgit v1.2.3 From 1a9b7e81919e4bd52cbb2db2e9e91a244734ab2c Mon Sep 17 00:00:00 2001 From: Benjamin Paassen Date: Thu, 12 Feb 2015 17:39:53 +0100 Subject: restructured pathTo. Also fixed some issues with that method and made it more general. --- src/core/model/Domain.cpp | 187 +++++++++++++++++++++++++++++------------ src/core/model/Domain.hpp | 39 ++++++++- src/plugins/xml/XmlParser.cpp | 6 +- test/core/model/DomainTest.cpp | 22 +++-- 4 files changed, 185 insertions(+), 69 deletions(-) (limited to 'test/core/model/DomainTest.cpp') diff --git a/src/core/model/Domain.cpp b/src/core/model/Domain.cpp index 228348d..25b2528 100644 --- a/src/core/model/Domain.cpp +++ b/src/core/model/Domain.cpp @@ -16,6 +16,8 @@ along with this program. If not, see . */ +#include +#include #include #include @@ -207,80 +209,153 @@ bool Descriptor::doValidate(Logger &logger) const return valid & continueValidationCheckDuplicates(fds, logger); } -NodeVector Descriptor::pathTo(Handle target) const +struct PathState { + std::shared_ptr pred; + Node *node; + int length; + + PathState(std::shared_ptr pred, Node *node) + : pred(pred), node(node) + { + if (pred == nullptr) { + length = 1; + } else { + length = pred->length + 1; + } + } +}; + +static void constructPath(std::shared_ptr state, + NodeVector &vec) { - NodeVector path; - continuePath(target, path, true); - return std::move(path); + if (state->pred != nullptr) { + constructPath(state->pred, vec); + } + vec.push_back(state->node); } -bool Descriptor::continuePath(Handle target, - NodeVector ¤tPath, bool start) const +template +static NodeVector pathTo(const Descriptor *start, Logger &logger, + F finished) { - // check if we are at the target already - if (this == target) { - return true; + // state queue for breadth-first search. + std::queue> states; + { + // initially put every field descriptor on the queue. + NodeVector fields = start->getFieldDescriptors(); + + for (auto fd : fields) { + states.push(std::make_shared(nullptr, fd.get())); + } } - // a variable to determine if we already found a solution - bool found = false; - // the currently optimal path. - NodeVector optimum; - // use recursive depth-first search from the top to reach the given child - // get the list of effective FieldDescriptors. - NodeVector fields = getFieldDescriptors(); + // shortest path. + NodeVector shortest; + // set of visited nodes. + std::unordered_set visited; + while (!states.empty()) { + std::shared_ptr current = states.front(); + states.pop(); + // do not proceed if this node was already visited. + if (!visited.insert(current->node).second) { + continue; + } + // also do not proceed if we can't get better than the current shortest + // path anymore. + if (!shortest.empty() && current->length > shortest.size()) { + continue; + } + + bool fin = false; + if (current->node->isa(&RttiTypes::StructuredClass)) { + const StructuredClass *strct = + static_cast(current->node); + + // continue the search path via all fields. + NodeVector fields = strct->getFieldDescriptors(); + for (auto fd : fields) { + // if we found our target, break off the search in this branch. + if (finished(fd)) { + fin = true; + continue; + } + states.push(std::make_shared(current, fd.get())); + } - Rooted thisHandle{const_cast(this)}; + /* + * Furthermore we have to consider that all subclasses of this + * StructuredClass are allowed in place of this StructuredClass as + * well, so we continue the search for them as well. + */ - for (auto &fd : fields) { - for (auto &c : fd->getChildren()) { - // check if a child is the target node. - if (c == target) { - // if we have made the connection, stop the search. - if (!start) { - currentPath.push_back(thisHandle); + NodeVector subs = strct->getSubclasses(); + for (auto sub : subs) { + // if we found our target, break off the search in this branch. + if (finished(sub)) { + fin = true; + current = current->pred; + continue; + } + // We only continue our path via transparent classes. + if (sub->isTransparent()) { + states.push( + std::make_shared(current->pred, sub.get())); } - currentPath.push_back(fd); - return true; } - // look for transparent intermediate nodes. - if (c->isTransparent()) { - // copy the path. - NodeVector cPath = currentPath; - if (!start) { - cPath.push_back(thisHandle); + } else { + // otherwise this is a FieldDescriptor. + const FieldDescriptor *field = + static_cast(current->node); + // and we proceed by visiting all permitted children. + for (auto c : field->getChildren()) { + // if we found our target, break off the search in this branch. + if (finished(c)) { + fin = true; + continue; } - cPath.push_back(fd); - // recursion. - if (c->continuePath(target, cPath, false) && - (!found || optimum.size() > cPath.size())) { - // look if this path is better than the current optimum. - optimum = std::move(cPath); - found = true; + // We only allow to continue our path via transparent children. + if (c->isTransparent()) { + states.push(std::make_shared(current, c.get())); } } } - } - - if (isa(&RttiTypes::StructuredClass)) { - const StructuredClass *tis = static_cast(this); - // if this is a StructuredClass we also can call the subclasses. - for (auto &c : tis->getSubclasses()) { - // copy the path. - NodeVector cPath = currentPath; - if (c->continuePath(target, cPath, false) && - (!found || optimum.size() > cPath.size())) { - // look if this path is better than the current optimum. - optimum = std::move(cPath); - found = true; + // check if we are finished. + if (fin) { + // if so we look if we found a shorter path than the current minimum + if (shortest.empty() || current->length < shortest.size()) { + NodeVector newPath; + constructPath(current, newPath); + shortest = newPath; + } else if (current->length == shortest.size()) { + // if the length is the same the result is ambigous and we log + // an error. + NodeVector newPath; + constructPath(current, newPath); + logger.error( + std::string("Can not unambigously create a path from \"") + + start->getName() + "\"."); + logger.note("Dismissed the path:", SourceLocation{}, + MessageMode::NO_CONTEXT); + for (auto n : newPath) { + logger.note(n->getName()); + } } } } + return shortest; +} - // put the optimum in the given path reference. - currentPath = std::move(optimum); +NodeVector Descriptor::pathTo(Handle target, + Logger &logger) const +{ + return ousia::pathTo(this, logger, + [target](Handle n) { return n == target; }); +} - // return if we found something. - return found; +NodeVector Descriptor::pathTo(Handle field, + Logger &logger) const +{ + return ousia::pathTo(this, logger, + [field](Handle n) { return n == field; }); } static ssize_t getFieldDescriptorIndex(const NodeVector &fds, diff --git a/src/core/model/Domain.hpp b/src/core/model/Domain.hpp index 57e5602..ae5ba1d 100644 --- a/src/core/model/Domain.hpp +++ b/src/core/model/Domain.hpp @@ -404,9 +404,6 @@ private: Owned attributesDescriptor; NodeVector fieldDescriptors; - bool continuePath(Handle target, NodeVector &path, - bool start) const; - void addAndSortFieldDescriptor(Handle fd, Logger &logger); protected: @@ -581,6 +578,9 @@ public: * a path between book and section (via chapter), but chapter is not * transparent. Therefore that path is not allowed. * + * Implicitly this does a breadth-first search on the graph of + * StructuredClasses that are transparent. It also takes care of cycles. + * * @param childDescriptor is a supposedly valid child Descriptor of this * Descriptor. * @return either a path of FieldDescriptors and @@ -589,7 +589,38 @@ public: * no such path can be constructed. * */ - NodeVector pathTo(Handle childDescriptor) const; + NodeVector pathTo(Handle childDescriptor, + Logger &logger) const; + /** + * This tries to construct the shortest possible path of this Descriptor + * to the given FieldDescriptor. + * + * Implicitly this does a breadth-first search on the graph of + * StructuredClasses that are transparent. It also takes care of cycles. + * + * @param field is a FieldDescriptor that may be allowed as child of this + * Descriptor. + * @return either a path of FieldDescriptors and StructuredClasses + * between this Descriptor and the input FieldDescriptor or an + * empty vector if no such path can be constructed. + */ + NodeVector pathTo(Handle field, + Logger &logger) const; + /** + * This tries to construct the shortest possible path of this Descriptor + * to the given FieldDescriptor. + * + * Implicitly this does a breadth-first search on the graph of + * StructuredClasses that are transparent. It also takes care of cycles. + * + * @param fieldName is the name of a FieldDescriptor that may be allowed as + * child of this Descriptor. + * @return either a path of FieldDescriptors and StructuredClasses + * between this Descriptor and a FieldDescriptor with the + * given name or an empty vector if no such path can be + * constructed. + */ + NodeVector pathTo(const std::string &fieldName, Logger &logger) const; }; /* * TODO: We should discuss Cardinalities one more time. Is it smart to define diff --git a/src/plugins/xml/XmlParser.cpp b/src/plugins/xml/XmlParser.cpp index c51ca8c..63d9df5 100644 --- a/src/plugins/xml/XmlParser.cpp +++ b/src/plugins/xml/XmlParser.cpp @@ -131,8 +131,8 @@ public: preamble(parentNode, fieldName, parent, inField); // try to find a FieldDescriptor for the given tag if we are not in a - // field already. - // TODO: Consider fields of transparent classes + // field already. This does _not_ try to construct transparent paths + // in between. if (!inField && parent != nullptr && parent->getDescriptor()->hasField(name())) { Rooted field{new DocumentField( @@ -166,7 +166,7 @@ public: strct, args, name); } else { // calculate a path if transparent entities are needed in between. - auto path = parent->getDescriptor()->pathTo(strct); + auto path = parent->getDescriptor()->pathTo(strct, logger()); if (path.empty()) { throw LoggableException( std::string("An instance of \"") + strct->getName() + diff --git a/test/core/model/DomainTest.cpp b/test/core/model/DomainTest.cpp index 2e20c3b..0097900 100644 --- a/test/core/model/DomainTest.cpp +++ b/test/core/model/DomainTest.cpp @@ -91,7 +91,7 @@ Rooted getClass(const std::string name, Handle dom) TEST(Descriptor, pathTo) { // Start with some easy examples from the book domain. - Logger logger; + TerminalLogger logger{std::cout}; Manager mgr{1}; Rooted sys{new SystemTypesystem(mgr)}; // Get the domain. @@ -101,14 +101,14 @@ TEST(Descriptor, pathTo) Rooted book = getClass("book", domain); Rooted section = getClass("section", domain); // get the path in between. - NodeVector path = book->pathTo(section); + NodeVector path = book->pathTo(section, logger); ASSERT_EQ(1U, path.size()); ASSERT_TRUE(path[0]->isa(&RttiTypes::FieldDescriptor)); // get the text node. Rooted text = getClass("text", domain); // get the path between book and text via paragraph. - path = book->pathTo(text); + path = book->pathTo(text, logger); ASSERT_EQ(3U, path.size()); ASSERT_TRUE(path[0]->isa(&RttiTypes::FieldDescriptor)); ASSERT_TRUE(path[1]->isa(&RttiTypes::StructuredClass)); @@ -118,9 +118,19 @@ TEST(Descriptor, pathTo) // get the subsection node. Rooted subsection = getClass("subsection", domain); // try to get the path between book and subsection. - path = book->pathTo(subsection); + path = book->pathTo(subsection, logger); // this should be impossible. ASSERT_EQ(0U, path.size()); + + // try to construct the path between section and the text field. + path = section->pathTo(text->getFieldDescriptor(), logger); + ASSERT_EQ(4U, path.size()); + ASSERT_TRUE(path[0]->isa(&RttiTypes::FieldDescriptor)); + ASSERT_TRUE(path[1]->isa(&RttiTypes::StructuredClass)); + ASSERT_EQ("paragraph", path[1]->getName()); + ASSERT_TRUE(path[2]->isa(&RttiTypes::FieldDescriptor)); + ASSERT_TRUE(path[3]->isa(&RttiTypes::StructuredClass)); + ASSERT_EQ("text", path[3]->getName()); } TEST(Descriptor, pathToAdvanced) @@ -148,7 +158,7 @@ TEST(Descriptor, pathToAdvanced) * So the path A_second_field, C, C_field should be returned. */ Manager mgr{1}; - Logger logger; + TerminalLogger logger{std::cout}; Rooted sys{new SystemTypesystem(mgr)}; // Construct the domain Rooted domain{new Domain(mgr, sys, "nasty")}; @@ -209,7 +219,7 @@ TEST(Descriptor, pathToAdvanced) #endif // and now we should be able to find the shortest path as suggested - NodeVector path = start->pathTo(target); + NodeVector path = start->pathTo(target, logger); ASSERT_EQ(3U, path.size()); ASSERT_TRUE(path[0]->isa(&RttiTypes::FieldDescriptor)); ASSERT_EQ("second", path[0]->getName()); -- cgit v1.2.3 From 110fb7da850377e39b2879da44339dc936c266dc Mon Sep 17 00:00:00 2001 From: Benjamin Paassen Date: Thu, 12 Feb 2015 19:31:18 +0100 Subject: further revised pathTo. Now only the TREE field is used in further exploration. --- src/core/model/Domain.cpp | 50 +++++++++++++++++++++++++++--------------- src/core/model/Domain.hpp | 18 ++++++++++----- test/core/model/DomainTest.cpp | 36 ++++++++++++------------------ 3 files changed, 58 insertions(+), 46 deletions(-) (limited to 'test/core/model/DomainTest.cpp') diff --git a/src/core/model/Domain.cpp b/src/core/model/Domain.cpp index 25b2528..619454c 100644 --- a/src/core/model/Domain.cpp +++ b/src/core/model/Domain.cpp @@ -212,7 +212,7 @@ bool Descriptor::doValidate(Logger &logger) const struct PathState { std::shared_ptr pred; Node *node; - int length; + size_t length; PathState(std::shared_ptr pred, Node *node) : pred(pred), node(node) @@ -234,10 +234,12 @@ static void constructPath(std::shared_ptr state, vec.push_back(state->node); } -template static NodeVector pathTo(const Descriptor *start, Logger &logger, - F finished) + Handle target, bool &success) { + success = false; + // shortest path. + NodeVector shortest; // state queue for breadth-first search. std::queue> states; { @@ -245,11 +247,16 @@ static NodeVector pathTo(const Descriptor *start, Logger &logger, NodeVector fields = start->getFieldDescriptors(); for (auto fd : fields) { - states.push(std::make_shared(nullptr, fd.get())); + if (fd == target) { + // if we have found the target directly, return without search. + success = true; + return shortest; + } + if (fd->getFieldType() == FieldDescriptor::FieldType::TREE) { + states.push(std::make_shared(nullptr, fd.get())); + } } } - // shortest path. - NodeVector shortest; // set of visited nodes. std::unordered_set visited; while (!states.empty()) { @@ -270,15 +277,18 @@ static NodeVector pathTo(const Descriptor *start, Logger &logger, const StructuredClass *strct = static_cast(current->node); - // continue the search path via all fields. + // look through all fields. NodeVector fields = strct->getFieldDescriptors(); for (auto fd : fields) { // if we found our target, break off the search in this branch. - if (finished(fd)) { + if (fd == target) { fin = true; continue; } - states.push(std::make_shared(current, fd.get())); + // only continue in the TREE field. + if (fd->getFieldType() == FieldDescriptor::FieldType::TREE) { + states.push(std::make_shared(current, fd.get())); + } } /* @@ -290,7 +300,7 @@ static NodeVector pathTo(const Descriptor *start, Logger &logger, NodeVector subs = strct->getSubclasses(); for (auto sub : subs) { // if we found our target, break off the search in this branch. - if (finished(sub)) { + if (sub == target) { fin = true; current = current->pred; continue; @@ -308,7 +318,7 @@ static NodeVector pathTo(const Descriptor *start, Logger &logger, // and we proceed by visiting all permitted children. for (auto c : field->getChildren()) { // if we found our target, break off the search in this branch. - if (finished(c)) { + if (c == target) { fin = true; continue; } @@ -320,6 +330,7 @@ static NodeVector pathTo(const Descriptor *start, Logger &logger, } // check if we are finished. if (fin) { + success = true; // if so we look if we found a shorter path than the current minimum if (shortest.empty() || current->length < shortest.size()) { NodeVector newPath; @@ -332,7 +343,7 @@ static NodeVector pathTo(const Descriptor *start, Logger &logger, constructPath(current, newPath); logger.error( std::string("Can not unambigously create a path from \"") + - start->getName() + "\"."); + start->getName() + "\" to \"" + target->getName() + "\"."); logger.note("Dismissed the path:", SourceLocation{}, MessageMode::NO_CONTEXT); for (auto n : newPath) { @@ -347,15 +358,18 @@ static NodeVector pathTo(const Descriptor *start, Logger &logger, NodeVector Descriptor::pathTo(Handle target, Logger &logger) const { - return ousia::pathTo(this, logger, - [target](Handle n) { return n == target; }); + bool success = false; + return ousia::pathTo(this, logger, target, success); } -NodeVector Descriptor::pathTo(Handle field, - Logger &logger) const +std::pair, bool> Descriptor::pathTo( + Handle field, Logger &logger) const +{ + bool success = false; + NodeVector path = ousia::pathTo(this, logger, field, success); + return std::make_pair(path, success); +} { - return ousia::pathTo(this, logger, - [field](Handle n) { return n == field; }); } static ssize_t getFieldDescriptorIndex(const NodeVector &fds, diff --git a/src/core/model/Domain.hpp b/src/core/model/Domain.hpp index abe7a52..91d635e 100644 --- a/src/core/model/Domain.hpp +++ b/src/core/model/Domain.hpp @@ -593,19 +593,25 @@ public: Logger &logger) const; /** * This tries to construct the shortest possible path of this Descriptor - * to the given FieldDescriptor. + * to the given FieldDescriptor. Note that this method has the problem that + * an empty return path does NOT strictly imply that no such path could + * be constructed: We also return an empty vector if the given + * FieldDescriptor is a direct child. Therefore we also return a bool value + * indicating that the path is valid or not. + * * * Implicitly this does a breadth-first search on the graph of * StructuredClasses that are transparent. It also takes care of cycles. * * @param field is a FieldDescriptor that may be allowed as child of this * Descriptor. - * @return either a path of FieldDescriptors and StructuredClasses - * between this Descriptor and the input FieldDescriptor or an - * empty vector if no such path can be constructed. + * @return returns a tuple containing a path of FieldDescriptors and + * StructuredClasses between this Descriptor and the input + * FieldDescriptor and a bool value indicating if the + * construction was successful. */ - NodeVector pathTo(Handle field, - Logger &logger) const; + std::pair, bool> pathTo(Handle field, + Logger &logger) const; }; /* * TODO: We should discuss Cardinalities one more time. Is it smart to define diff --git a/test/core/model/DomainTest.cpp b/test/core/model/DomainTest.cpp index 0097900..672b2d1 100644 --- a/test/core/model/DomainTest.cpp +++ b/test/core/model/DomainTest.cpp @@ -123,7 +123,9 @@ TEST(Descriptor, pathTo) ASSERT_EQ(0U, path.size()); // try to construct the path between section and the text field. - path = section->pathTo(text->getFieldDescriptor(), logger); + auto res = section->pathTo(text->getFieldDescriptor(), logger); + ASSERT_TRUE(res.second); + path = res.first; ASSERT_EQ(4U, path.size()); ASSERT_TRUE(path[0]->isa(&RttiTypes::FieldDescriptor)); ASSERT_TRUE(path[1]->isa(&RttiTypes::StructuredClass)); @@ -144,15 +146,13 @@ TEST(Descriptor, pathToAdvanced) * * To achieve that we have the following structure: * 1.) The start class inherits from A. - * 2.) A has the target as child in the default field, but the default - * field is overridden in the start class. - * 3.) A has B as child in another field. - * 4.) B is transparent and has no children (but C as subclass) - * 5.) C is a subclass of B, transparent and has + * 2.) A has B as child in the default field. + * 3.) B is transparent and has no children (but C as subclass) + * 4.) C is a subclass of B, transparent and has * the target as child (shortest path). - * 6.) start has D as child in the default field. - * 7.) D is transparent has E as child in the default field. - * 8.) E is transparent and has target as child in the default field + * 5.) A has D as child in the default field. + * 6.) D is transparent has E as child in the default field. + * 7.) E is transparent and has target as child in the default field * (longer path) * * So the path A_second_field, C, C_field should be returned. @@ -185,30 +185,22 @@ TEST(Descriptor, pathToAdvanced) Rooted target{ new StructuredClass(mgr, "target", domain, Cardinality::any())}; - // We create two fields for A + // We create a field for A Rooted A_field = A->createFieldDescriptor(logger); + A_field->addChild(B); + A_field->addChild(D); - A_field->addChild(target); - Rooted A_field2 = A->createFieldDescriptor( - logger, FieldDescriptor::FieldType::SUBTREE, "second", false); - - A_field2->addChild(B); // We create no field for B // One for C Rooted C_field = C->createFieldDescriptor(logger); - C_field->addChild(target); - // one for start - Rooted start_field = start->createFieldDescriptor(logger); - start_field->addChild(D); // One for D Rooted D_field = D->createFieldDescriptor(logger); - D_field->addChild(E); + // One for E Rooted E_field = E->createFieldDescriptor(logger); - E_field->addChild(target); ASSERT_TRUE(domain->validate(logger)); @@ -222,7 +214,7 @@ TEST(Descriptor, pathToAdvanced) NodeVector path = start->pathTo(target, logger); ASSERT_EQ(3U, path.size()); ASSERT_TRUE(path[0]->isa(&RttiTypes::FieldDescriptor)); - ASSERT_EQ("second", path[0]->getName()); + ASSERT_EQ("", path[0]->getName()); ASSERT_TRUE(path[1]->isa(&RttiTypes::StructuredClass)); ASSERT_EQ("C", path[1]->getName()); ASSERT_TRUE(path[2]->isa(&RttiTypes::FieldDescriptor)); -- cgit v1.2.3 From 89f01a0a49f4fd23034d532b37d54d3f3f612082 Mon Sep 17 00:00:00 2001 From: Benjamin Paassen Date: Thu, 12 Feb 2015 19:31:50 +0100 Subject: added a method to retrieve all reachable default fields from a given descriptor. --- src/core/model/Domain.cpp | 99 ++++++++++++++++++++++++++++++++++++++++++ src/core/model/Domain.hpp | 12 +++++ test/core/model/DomainTest.cpp | 80 ++++++++++++++++++++++++++++++++++ 3 files changed, 191 insertions(+) (limited to 'test/core/model/DomainTest.cpp') diff --git a/src/core/model/Domain.cpp b/src/core/model/Domain.cpp index 619454c..6f33ebd 100644 --- a/src/core/model/Domain.cpp +++ b/src/core/model/Domain.cpp @@ -369,7 +369,106 @@ std::pair, bool> Descriptor::pathTo( NodeVector path = ousia::pathTo(this, logger, field, success); return std::make_pair(path, success); } + +template +static NodeVector collect(const Descriptor *start, F match) { + // result + NodeVector res; + // queue for breadth-first search of graph. + std::queue> q; + { + // initially put every field descriptor on the queue. + NodeVector fields = start->getFieldDescriptors(); + + for (auto fd : fields) { + // note matches. + if (match(fd)) { + res.push_back(fd); + } + if (fd->getFieldType() == FieldDescriptor::FieldType::TREE) { + q.push(fd); + } + } + } + // set of visited nodes. + std::unordered_set visited; + while (!q.empty()) { + Rooted n = q.front(); + q.pop(); + // do not proceed if this node was already visited. + if (!visited.insert(n.get()).second) { + continue; + } + + if (n->isa(&RttiTypes::StructuredClass)) { + Rooted strct = n.cast(); + + // look through all fields. + NodeVector fields = strct->getFieldDescriptors(); + for (auto fd : fields) { + // note matches. + if (match(fd)) { + res.push_back(fd); + } + // only continue in the TREE field. + if (fd->getFieldType() == FieldDescriptor::FieldType::TREE) { + q.push(fd); + } + } + + /* + * Furthermore we have to consider that all subclasses of this + * StructuredClass are allowed in place of this StructuredClass as + * well, so we continue the search for them as well. + */ + + NodeVector subs = strct->getSubclasses(); + for (auto sub : subs) { + // note matches. + if (match(sub)) { + res.push_back(sub); + } + // We only continue our search via transparent classes. + if (sub->isTransparent()) { + q.push(sub); + } + } + } else { + // otherwise this is a FieldDescriptor. + Rooted field = n.cast(); + // and we proceed by visiting all permitted children. + for (auto c : field->getChildren()) { + // note matches. + if (match(c)) { + res.push_back(c); + } + // We only continue our search via transparent children. + if (c->isTransparent()) { + q.push(c); + } + } + } + } + return res; +} + +NodeVector Descriptor::getDefaultFields() const +{ + // TODO: In principle a cast would be nicer here, but for now we copy. + NodeVector nodes = collect(this, [](Handle n) { + if (!n->isa(&RttiTypes::FieldDescriptor)) { + return false; + } + Handle f = n.cast(); + return f->getFieldType() == FieldDescriptor::FieldType::TREE && + f->isPrimitive(); + }); + NodeVector res; + for (auto n : nodes) { + res.push_back(n.cast()); + } + return res; } static ssize_t getFieldDescriptorIndex(const NodeVector &fds, diff --git a/src/core/model/Domain.hpp b/src/core/model/Domain.hpp index 91d635e..c277812 100644 --- a/src/core/model/Domain.hpp +++ b/src/core/model/Domain.hpp @@ -612,6 +612,18 @@ public: */ std::pair, bool> pathTo(Handle field, Logger &logger) const; + + /** + * Returns a vector of all TREE fields that are allowed as structure tree + * children of an instance of this Descriptor. This also makes use of + * transparency. + * The list is sorted by the number of transparent elements that have to be + * constructed to arrive at the respective FieldDescriptor. + * + * @return a vector of all TREE fields that are allowed as structure tree + * children of an instance of this Descriptor. + */ + NodeVector getDefaultFields() const; }; /* * TODO: We should discuss Cardinalities one more time. Is it smart to define diff --git a/test/core/model/DomainTest.cpp b/test/core/model/DomainTest.cpp index 672b2d1..83f290f 100644 --- a/test/core/model/DomainTest.cpp +++ b/test/core/model/DomainTest.cpp @@ -221,6 +221,86 @@ TEST(Descriptor, pathToAdvanced) ASSERT_EQ("", path[2]->getName()); } +TEST(Descriptor, getDefaultFields) +{ + // construct a domain with lots of default fields to test. + // start with a single structure class. + Manager mgr{1}; + TerminalLogger logger{std::cout}; + Rooted sys{new SystemTypesystem(mgr)}; + // Construct the domain + Rooted domain{new Domain(mgr, sys, "nasty")}; + + Rooted A{new StructuredClass( + mgr, "A", domain, Cardinality::any(), nullptr, false, true)}; + + // in this trivial case no field should be found. + ASSERT_TRUE(A->getDefaultFields().empty()); + + // create a field. + Rooted A_prim_field = + A->createPrimitiveFieldDescriptor(sys->getStringType(), logger); + // now we should find that. + auto fields = A->getDefaultFields(); + ASSERT_EQ(1, fields.size()); + ASSERT_EQ(A_prim_field, fields[0]); + + // remove that field from A and add it to another class. + + Rooted B{new StructuredClass( + mgr, "B", domain, Cardinality::any(), nullptr, false, true)}; + + B->moveFieldDescriptor(A_prim_field, logger); + + // new we shouldn't find the field anymore. + ASSERT_TRUE(A->getDefaultFields().empty()); + + // but we should find it again if we set B as superclass of A. + A->setSuperclass(B, logger); + fields = A->getDefaultFields(); + ASSERT_EQ(1, fields.size()); + ASSERT_EQ(A_prim_field, fields[0]); + + // and we should not be able to find it if we override the field. + Rooted A_field = A->createFieldDescriptor(logger); + ASSERT_TRUE(A->getDefaultFields().empty()); + + // add a transparent child class. + + Rooted C{new StructuredClass( + mgr, "C", domain, Cardinality::any(), nullptr, true, false)}; + A_field->addChild(C); + + // add a primitive field for it. + Rooted C_field = + C->createPrimitiveFieldDescriptor(sys->getStringType(), logger); + + // now we should find that. + fields = A->getDefaultFields(); + ASSERT_EQ(1, fields.size()); + ASSERT_EQ(C_field, fields[0]); + + // add another transparent child class to A with a daughter class that has + // in turn a subclass with a primitive field. + Rooted D{new StructuredClass( + mgr, "D", domain, Cardinality::any(), nullptr, true, false)}; + A_field->addChild(D); + Rooted D_field = D->createFieldDescriptor(logger); + Rooted E{new StructuredClass( + mgr, "E", domain, Cardinality::any(), nullptr, true, false)}; + D_field->addChild(E); + Rooted F{new StructuredClass( + mgr, "E", domain, Cardinality::any(), E, true, false)}; + Rooted F_field = + F->createPrimitiveFieldDescriptor(sys->getStringType(), logger); + + // now we should find both primitive fields, but the C field first. + fields = A->getDefaultFields(); + ASSERT_EQ(2, fields.size()); + ASSERT_EQ(C_field, fields[0]); + ASSERT_EQ(F_field, fields[1]); +} + TEST(StructuredClass, isSubclassOf) { // create an inheritance hierarchy. -- cgit v1.2.3 From 3ed124aeed2cb65b05f61224115366601ee3b05f Mon Sep 17 00:00:00 2001 From: Benjamin Paassen Date: Thu, 12 Feb 2015 22:36:38 +0100 Subject: added Descriptor::getPermittedChildren. --- src/core/model/Domain.cpp | 13 +++++++++++++ src/core/model/Domain.hpp | 12 ++++++++++++ test/core/model/DomainTest.cpp | 37 +++++++++++++++++++++++++++++++++++++ 3 files changed, 62 insertions(+) (limited to 'test/core/model/DomainTest.cpp') diff --git a/src/core/model/Domain.cpp b/src/core/model/Domain.cpp index 6f33ebd..3cc9755 100644 --- a/src/core/model/Domain.cpp +++ b/src/core/model/Domain.cpp @@ -471,6 +471,19 @@ NodeVector Descriptor::getDefaultFields() const return res; } +NodeVector Descriptor::getPermittedChildren() const +{ + // TODO: In principle a cast would be nicer here, but for now we copy. + NodeVector nodes = collect(this, [](Handle n) { + return n->isa(&RttiTypes::StructuredClass); + }); + NodeVector res; + for (auto n : nodes) { + res.push_back(n.cast()); + } + return res; +} + static ssize_t getFieldDescriptorIndex(const NodeVector &fds, const std::string &name) { diff --git a/src/core/model/Domain.hpp b/src/core/model/Domain.hpp index c277812..6bc2fba 100644 --- a/src/core/model/Domain.hpp +++ b/src/core/model/Domain.hpp @@ -624,6 +624,18 @@ public: * children of an instance of this Descriptor. */ NodeVector getDefaultFields() const; + + /** + * Returns a vector of all StructuredClasses that are allowed as children + * of an instance of this Descriptor in the structure tree. This also makes + * use of transparency. + * The list is sorted by the number of transparent elements that have to be + * constructed to arrive at the respective FieldDescriptor. + * + * @return a vector of all StructuredClasses that are allowed as children + * of an instance of this Descriptor in the structure tree. + */ + NodeVector getPermittedChildren() const; }; /* * TODO: We should discuss Cardinalities one more time. Is it smart to define diff --git a/test/core/model/DomainTest.cpp b/test/core/model/DomainTest.cpp index 83f290f..8fcbdf2 100644 --- a/test/core/model/DomainTest.cpp +++ b/test/core/model/DomainTest.cpp @@ -301,6 +301,43 @@ TEST(Descriptor, getDefaultFields) ASSERT_EQ(F_field, fields[1]); } +TEST(Descriptor, getPermittedChildren) +{ + // analyze the book domain. + TerminalLogger logger{std::cout}; + Manager mgr{1}; + Rooted sys{new SystemTypesystem(mgr)}; + // Get the domain. + Rooted domain = constructBookDomain(mgr, sys, logger); + // get the relevant classes. + Rooted book = getClass("book", domain); + Rooted section = getClass("section", domain); + Rooted paragraph = getClass("paragraph", domain); + Rooted text = getClass("text", domain); + /* + * as permitted children we expect section, paragraph and text in exactly + * that order. section should be before paragraph because of declaration + * order and text should be last because it needs a transparent paragraph + * in between. + */ + NodeVector children = book->getPermittedChildren(); + ASSERT_EQ(3, children.size()); + ASSERT_EQ(section, children[0]); + ASSERT_EQ(paragraph, children[1]); + ASSERT_EQ(text, children[2]); + + // Now we add a subclass to text. + Rooted sub{new StructuredClass( + mgr, "Subclass", domain, Cardinality::any(), text, true, false)}; + // And that should be in the result list as well now. + children = book->getPermittedChildren(); + ASSERT_EQ(4, children.size()); + ASSERT_EQ(section, children[0]); + ASSERT_EQ(paragraph, children[1]); + ASSERT_EQ(text, children[2]); + ASSERT_EQ(sub, children[3]); +} + TEST(StructuredClass, isSubclassOf) { // create an inheritance hierarchy. -- cgit v1.2.3