summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/core/model/Document.cpp128
-rw-r--r--src/core/model/Document.hpp43
-rw-r--r--src/core/model/Domain.hpp6
-rw-r--r--src/core/model/Node.cpp4
-rw-r--r--src/core/model/Node.hpp4
-rw-r--r--test/core/model/DocumentTest.cpp88
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.
+}
}
}