diff options
-rw-r--r-- | src/core/model/Document.cpp | 128 | ||||
-rw-r--r-- | src/core/model/Document.hpp | 43 | ||||
-rw-r--r-- | src/core/model/Domain.hpp | 6 | ||||
-rw-r--r-- | src/core/model/Node.cpp | 4 | ||||
-rw-r--r-- | src/core/model/Node.hpp | 4 | ||||
-rw-r--r-- | test/core/model/DocumentTest.cpp | 88 |
6 files changed, 214 insertions, 59 deletions
diff --git a/src/core/model/Document.cpp b/src/core/model/Document.cpp index f817845..5ca257b 100644 --- a/src/core/model/Document.cpp +++ b/src/core/model/Document.cpp @@ -92,28 +92,83 @@ int DocumentEntity::getFieldDescriptorIndex( } } +void DocumentEntity::addStructureNode(Handle<StructureNode> s, + const std::string &fieldName) +{ + if (subInst->isa(RttiTypes::StructuredEntity)) { + const StructuredEntity *s = + static_cast<const StructuredEntity *>(subInst); + s->invalidate(); + } else { + const AnnotationEntity *a = + static_cast<const AnnotationEntity *>(subInst); + a->invalidate(); + } + fields[getFieldDescriptorIndex(fieldName, true)].push_back(s); +} + +DocumentEntity::DocumentEntity(Handle<Node> subInst, + Handle<Descriptor> descriptor, + Variant attributes) + : subInst(subInst.get()), + descriptor(subInst->acquire(descriptor)), + attributes(std::move(attributes)) +{ + // insert empty vectors for each field. + if (!descriptor.isNull()) { + NodeVector<FieldDescriptor> fieldDescs; + if (descriptor->isa(RttiTypes::StructuredClass)) { + fieldDescs = descriptor.cast<StructuredClass>() + ->getEffectiveFieldDescriptors(); + } else { + fieldDescs = descriptor->getFieldDescriptors(); + } + for (size_t f = 0; f < fieldDescs.size(); f++) { + fields.push_back(NodeVector<StructureNode>(subInst)); + } + } +} + bool DocumentEntity::doValidate(Logger &logger) const { + // if we have no descriptor, this is invalid. + if (descriptor == nullptr) { + logger.error("This DocumentEntity has no descriptor!"); + return false; + } // TODO: check the validated form of Attributes + // TODO: Check if descriptor is registered at the Document? + + /* + * generate the set of effective fields. This is trivial for + * AnnotationEntities, but in the case of StructuredEntities we have to + * gather all fields of superclasses as well, that have not been + * overridden in the subclasses. + */ + NodeVector<FieldDescriptor> fieldDescs; + if (descriptor->isa(RttiTypes::StructuredClass)) { + fieldDescs = + descriptor.cast<StructuredClass>()->getEffectiveFieldDescriptors(); + } else { + fieldDescs = descriptor->getFieldDescriptors(); + } // iterate over every field for (unsigned int f = 0; f < fields.size(); f++) { // we can do a faster check if this field is empty. if (fields[f].size() == 0) { // if this field is optional, an empty field is valid anyways. - if (descriptor->getFieldDescriptors()[f]->optional) { + if (fieldDescs[f]->optional) { continue; } /* - * if it is not optional we have to chack if zero is a valid + * if it is not optional we have to check if zero is a valid * cardinality. */ - for (auto &ac : - descriptor->getFieldDescriptors()[f]->getChildren()) { + for (auto &ac : fieldDescs[f]->getChildren()) { const size_t min = ac->getCardinality().min(); if (min > 0) { logger.error( - std::string("Field ") + - descriptor->getFieldDescriptors()[f]->getName() + + std::string("Field ") + fieldDescs[f]->getName() + " was empty but needs at least " + std::to_string(min) + " elements of class " + ac->getName() + " according to the definition of " + @@ -126,7 +181,7 @@ bool DocumentEntity::doValidate(Logger &logger) const // create a set of allowed classes identified by their unique id. std::set<ManagedUid> accs; - for (auto &ac : descriptor->getFieldDescriptors()[f]->getChildren()) { + for (auto &ac : fieldDescs[f]->getChildren()) { accs.insert(ac->getUid()); } // store the actual numbers of children for each child class in a map @@ -134,11 +189,11 @@ bool DocumentEntity::doValidate(Logger &logger) const // iterate over every actual child of this DocumentEntity for (auto &rc : fields[f]) { - if (!rc->isa(RttiTypes::Anchor)) { + if (rc->isa(RttiTypes::Anchor)) { // Anchors are uninteresting and can be ignored. continue; } - if (!rc->isa(RttiTypes::DocumentPrimitive)) { + if (rc->isa(RttiTypes::DocumentPrimitive)) { // For DocumentPrimitives we have to check the content type. // TODO: Do that! continue; @@ -154,8 +209,7 @@ bool DocumentEntity::doValidate(Logger &logger) const * child of a permitted class. */ if (!allowed) { - for (auto &ac : - descriptor->getFieldDescriptors()[f]->getChildren()) { + for (auto &ac : fieldDescs[f]->getChildren()) { if (c->getDescriptor() .cast<StructuredClass>() ->isSubclassOf(ac)) { @@ -169,7 +223,7 @@ bool DocumentEntity::doValidate(Logger &logger) const c->getDescriptor()->getName() + " is not allowed as child of an instance of " + descriptor->getName() + " in field " + - descriptor->getFieldDescriptors()[f]->getName()); + fieldDescs[f]->getName()); return false; } // note the number of occurences. @@ -182,7 +236,7 @@ bool DocumentEntity::doValidate(Logger &logger) const } // now check if the cardinalities are right. - for (auto &ac : descriptor->getFieldDescriptors()[f]->getChildren()) { + for (auto &ac : fieldDescs[f]->getChildren()) { const auto &n = nums.find(ac->getUid()); unsigned int num = 0; if (n != nums.end()) { @@ -190,8 +244,7 @@ bool DocumentEntity::doValidate(Logger &logger) const } if (!ac->getCardinality().contains(num)) { logger.error( - std::string("Field ") + - descriptor->getFieldDescriptors()[f]->getName() + " had " + + std::string("Field ") + fieldDescs[f]->getName() + " had " + std::to_string(num) + " elements of class " + ac->getName() + ", which is invalid according to the definition of " + @@ -245,6 +298,12 @@ bool StructuredEntity::doValidate(Logger &logger) const if (getParent() == nullptr) { return false; } + // check name + if (getName() != "") { + if (!validateName(logger)) { + return false; + } + } // check the validity as a DocumentEntity. return DocumentEntity::doValidate(logger); } @@ -281,6 +340,12 @@ bool AnnotationEntity::doValidate(Logger &logger) const logger.error("This annotation was not registered at the document."); return false; } + // check name + if (getName() != "") { + if (!validateName(logger)) { + return false; + } + } // check if the Anchors are part of the right document. if (!doc->hasChild(start)) { return false; @@ -308,26 +373,23 @@ void Document::doResolve(ResolutionState &state) bool Document::doValidate(Logger &logger) const { - if (root != nullptr) { - // check if the root is allowed to be a root. - if (!root->getDescriptor().cast<StructuredClass>()->root) { - logger.error(std::string("A node of type ") + - root->getDescriptor()->getName() + - " is not allowed to be the Document root!"); - return false; - } - // then call validate on the root - if (!root->validate(logger)) { - return false; - } + // An empty document is always invalid. TODO: Is this a smart choice? + if (root == nullptr) { + return false; } - // call validate on the AnnotationEntities - for (auto &a : annotations) { - if (!a->validate(logger)) { - return false; - } + // check if the root is allowed to be a root. + if (!root->getDescriptor().cast<StructuredClass>()->root) { + logger.error(std::string("A node of type ") + + root->getDescriptor()->getName() + + " is not allowed to be the Document root!"); + return false; } - return true; + // then call validate on the root + if (!root->validate(logger)) { + return false; + } + // call validate on the AnnotationEntities + return continueValidation(annotations, logger); } bool Document::hasChild(Handle<StructureNode> s) const diff --git a/src/core/model/Document.hpp b/src/core/model/Document.hpp index d9729c3..80da260 100644 --- a/src/core/model/Document.hpp +++ b/src/core/model/Document.hpp @@ -144,6 +144,12 @@ class DocumentEntity { friend StructureNode; private: + /* + * this is a rather dirty method that should not be used in other cases: + * We store a pointer to the Node instance that inherits from + * DocumentEntity. + */ + const Node *subInst; Owned<Descriptor> descriptor; const Variant attributes; std::vector<NodeVector<StructureNode>> fields; @@ -156,10 +162,7 @@ private: protected: void addStructureNode(Handle<StructureNode> s, - const std::string &fieldName = "") - { - fields[getFieldDescriptorIndex(fieldName, true)].push_back(s); - } + const std::string &fieldName = ""); bool doValidate(Logger &logger) const; @@ -169,7 +172,7 @@ public: * from Node. Therefore we need to have a handle to the subclass Node * instance to create NodeVectors and Owned references. * - * @param owner is a handle to the subclass instance + * @param subInst is a handle to the subclass instance * (e.g. StructuredEntity), such that the fields vectors * and the descriptor reference can be obtained. * @param descriptor is the Descriptor for this DocumentEntity, which will @@ -177,19 +180,8 @@ public: * @param attributes is a Map Variant adhering to the attribute StructType * in the given descriptor. */ - DocumentEntity(Handle<Node> owner, Handle<Descriptor> descriptor, - Variant attributes = {}) - : descriptor(owner->acquire(descriptor)), - attributes(std::move(attributes)) - { - // insert empty vectors for each field. - if (!descriptor.isNull()) { - for (size_t f = 0; f < descriptor->getFieldDescriptors().size(); - f++) { - fields.push_back(NodeVector<StructureNode>(owner)); - } - } - } + DocumentEntity(Handle<Node> subInst, Handle<Descriptor> descriptor, + Variant attributes = {}); /** * Returns the Descriptor for this DocumentEntity. @@ -509,6 +501,8 @@ public: * */ class AnnotationEntity : public Node, public DocumentEntity { + friend DocumentEntity; + private: Owned<Anchor> start; Owned<Anchor> end; @@ -584,7 +578,11 @@ public: /** * Sets the root StructuredEntity of this Document. */ - void setRoot(Handle<StructuredEntity> root) { this->root = acquire(root); }; + void setRoot(Handle<StructuredEntity> root) + { + invalidate(); + this->root = acquire(root); + }; /** * Returns the root StructuredEntity of this Document. @@ -617,13 +615,18 @@ public: /** * Adds a Domain reference to this Document. */ - void addDomain(Handle<Domain> d) { domains.push_back(d); } + void addDomain(Handle<Domain> d) + { + invalidate(); + domains.push_back(d); + } /** * Adds multiple Domain references to this Document. */ void addDomains(const std::vector<Handle<Domain>> &d) { + invalidate(); domains.insert(domains.end(), d.begin(), d.end()); } diff --git a/src/core/model/Domain.hpp b/src/core/model/Domain.hpp index b192c11..734447e 100644 --- a/src/core/model/Domain.hpp +++ b/src/core/model/Domain.hpp @@ -467,7 +467,11 @@ public: std::vector<Rooted<Node>> pathTo( Handle<StructuredClass> childDescriptor) const; }; - +/* + * TODO: We should discuss Cardinalities one more time. Is it smart to define + * cardinalities independent of context? Should we not have at least have the + * possibility to define it context-dependently? + */ typedef RangeSet<size_t> Cardinality; /** diff --git a/src/core/model/Node.cpp b/src/core/model/Node.cpp index be13d42..d069eee 100644 --- a/src/core/model/Node.cpp +++ b/src/core/model/Node.cpp @@ -380,7 +380,7 @@ bool Node::validateName(Logger &logger) const return true; } -void Node::invalidate() +void Node::invalidate() const { // Only perform the invalidation if necessary if (validationState != ValidationState::UNKNOWN) { @@ -391,7 +391,7 @@ void Node::invalidate() } } -void Node::markInvalid() +void Node::markInvalid() const { // Do not override the validationState if we're currently in the validation // procedure, try to mark the parent node as invalid diff --git a/src/core/model/Node.hpp b/src/core/model/Node.hpp index 79f38b8..4fc7672 100644 --- a/src/core/model/Node.hpp +++ b/src/core/model/Node.hpp @@ -321,12 +321,12 @@ protected: * changed such that a new validation run has to be made. Also informs the * parent node about the invalidation. */ - void invalidate(); + void invalidate() const; /** * This method should be called if a Node finds itself in an invalid state. */ - void markInvalid(); + void markInvalid() const; /** * The convention for this function is as follows: diff --git a/test/core/model/DocumentTest.cpp b/test/core/model/DocumentTest.cpp index 4b0447d..d8ccef1 100644 --- a/test/core/model/DocumentTest.cpp +++ b/test/core/model/DocumentTest.cpp @@ -31,7 +31,7 @@ namespace ousia { namespace model { -TEST(Document, testDocumentConstruction) +TEST(Document, construct) { // Construct Manager TerminalLogger logger{std::cerr, true}; @@ -109,5 +109,91 @@ TEST(Document, testDocumentConstruction) } } } + +TEST(Document, validate) +{ + // Let's start with a trivial domain and a trivial document. + TerminalLogger logger{std::cerr, true}; + Manager mgr{1}; + Rooted<SystemTypesystem> sys{new SystemTypesystem(mgr)}; + Rooted<Domain> domain{new Domain(mgr, sys, "trivial")}; + Cardinality single; + single.merge({1}); + // Set up the "root" StructuredClass. + Rooted<StructuredClass> rootClass{new StructuredClass( + mgr, "root", domain, single, {nullptr}, {nullptr}, false, true)}; + + // set up a document for it. + { + // first an invalid one, which is empty. + Rooted<Document> doc{new Document(mgr, "myDoc.oxd")}; + doc->addDomain(domain); + ASSERT_FALSE(doc->validate(logger)); + // then add a root, which should make it valid. + Rooted<StructuredEntity> root = + buildRootStructuredEntity(doc, logger, {"root"}); + ASSERT_TRUE(doc->validate(logger)); + } + + // now let's extend the rootClass with a default field. + Rooted<FieldDescriptor> rootField{new FieldDescriptor(mgr, rootClass)}; + // and add a child class for it. + Rooted<StructuredClass> childClass{ + new StructuredClass(mgr, "child", domain, single)}; + rootField->addChild(childClass); + { + /* + * now check again: Because the child has the cardinality {1} our + * document should be invalid again. + */ + Rooted<Document> doc{new Document(mgr, "myDoc.oxd")}; + doc->addDomain(domain); + Rooted<StructuredEntity> root = + buildRootStructuredEntity(doc, logger, {"root"}); + ASSERT_FALSE(doc->validate(logger)); + // but it should get valid if we add a proper child. + buildStructuredEntity(doc, logger, root, {"child"}); + ASSERT_TRUE(doc->validate(logger)); + // and it should get invalid again if we add one more child. + buildStructuredEntity(doc, logger, root, {"child"}); + ASSERT_FALSE(doc->validate(logger)); + } + /* + * Add a further extension to the domain: We add a subclass to child. + */ + Rooted<StructuredClass> childSubClass{new StructuredClass( + mgr, "childSub", domain, single, {nullptr}, childClass)}; + { + /* + * A document with one instance of the Child subclass should be valid. + */ + Rooted<Document> doc{new Document(mgr, "myDoc.oxd")}; + doc->addDomain(domain); + Rooted<StructuredEntity> root = + buildRootStructuredEntity(doc, logger, {"root"}); + buildStructuredEntity(doc, logger, root, {"childSub"}); + ASSERT_TRUE(doc->validate(logger)); + } + /* + * Make it even more complicated: child gets a field for further child + * instances now. + */ + Rooted<FieldDescriptor> childField{new FieldDescriptor(mgr, childClass)}; + childField->addChild(childClass); + { + /* + * Now a document with one instance of the Child subclass should be + * invalid, because it has no children of itself. + */ + Rooted<Document> doc{new Document(mgr, "myDoc.oxd")}; + doc->addDomain(domain); + Rooted<StructuredEntity> root = + buildRootStructuredEntity(doc, logger, {"root"}); + buildStructuredEntity(doc, logger, root, {"childSub"}); + ASSERT_FALSE(doc->validate(logger)); + } + // TODO: Override child field in childSub such that an empty childSub is + // valid. +} } } |