/* Ousía Copyright (C) 2014, 2015 Benjamin Paaßen, Andreas Stöckel This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #include "Document.hpp" #include #include #include #include namespace ousia { /* Class DocumentEntity */ void DocumentEntity::invalidateSubInstance() { if (subInst->isa(&RttiTypes::StructuredEntity)) { subInst.cast()->invalidate(); } else { subInst.cast()->invalidate(); } } DocumentEntity::DocumentEntity(Handle subInst, Handle descriptor, Variant attributes) : subInst(subInst), // initialize descriptor as nullptr first and then set it right attributes(std::move(attributes)) { // insert empty vectors for each field. if (descriptor != nullptr) { setDescriptor(descriptor); } } void DocumentEntity::setDescriptor(Handle d) { // check if we have to do anything. if (descriptor == d) { return; } invalidateSubInstance(); descriptor = subInst->acquire(d); // clear the fields vector. fields.clear(); // fill it again. for (size_t f = 0; f < descriptor->getFieldDescriptors().size(); f++) { fields.push_back(NodeVector(subInst)); } } bool DocumentEntity::doValidate(Logger &logger) const { // if we have no descriptor, this is invalid. if (descriptor == nullptr) { logger.error("This entity has no descriptor!", *subInst); // in this case we have to stop the validation process, because without // a constructor we can not check anything else. return false; } // if we have an invalid descriptor we can not proceed either. if (!descriptor->validate(logger)) { return false; } // check the attribute primitive content. bool valid; if (descriptor->getAttributesDescriptor() == nullptr) { valid = getAttributes() == nullptr; } else { valid = descriptor->getAttributesDescriptor()->isValid(getAttributes(), logger); } /* * 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 fieldDescs = descriptor->getFieldDescriptors(); // iterate over every field for (unsigned int f = 0; f < fields.size(); f++) { // we have a special check for primitive fields. if (fieldDescs[f]->isPrimitive()) { switch (fields[f].size()) { case 0: if (!fieldDescs[f]->isOptional()) { logger.error(std::string("Primitive Field \"") + fieldDescs[f]->getName() + "\" had no content!", *subInst); valid = false; } continue; case 1: break; default: logger.error(std::string("Primitive Field \"") + fieldDescs[f]->getName() + "\" had more than one child!", *subInst); valid = false; continue; } // if we are here we know that exactly one child exists. if (!fields[f][0]->isa(&RttiTypes::DocumentPrimitive)) { logger.error(std::string("Primitive Field \"") + fieldDescs[f]->getName() + "\" has non primitive content!", *subInst); valid = false; } else { Handle primitive = fields[f][0].cast(); valid = valid & fieldDescs[f]->getPrimitiveType()->isValid( primitive->getContent(), logger); } continue; } std::unordered_set childClasses; { NodeVector tmp = fieldDescs[f]->getChildrenWithSubclasses(); for (auto s : tmp) { childClasses.insert(s.get()); } } // 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 (fieldDescs[f]->isOptional()) { continue; } /* * if it is not optional we have to check if zero is a valid * cardinality. */ for (auto childClass : childClasses) { const size_t min = childClass->getCardinality().asCardinality().min(); if (min > 0) { logger.error( std::string("Field \"") + fieldDescs[f]->getName() + "\" was empty but needs at least " + std::to_string(min) + " elements of class \"" + childClass->getName() + "\" according to the definition of \"" + descriptor->getName() + "\"", *subInst); valid = false; } } continue; } // store the actual numbers of children for each child class in a map std::unordered_map nums; // iterate over every actual child of this field for (auto child : fields[f]) { // check if the parent reference is correct. if (child->getParent() != subInst) { logger.error(std::string("A child of field \"") + fieldDescs[f]->getName() + "\" has the wrong parent reference!", *child); valid = false; } if (child->isa(&RttiTypes::Anchor)) { // Anchors are uninteresting and can be ignored. continue; } if (child->isa(&RttiTypes::DocumentPrimitive)) { logger.error(std::string("Non-primitive Field \"") + fieldDescs[f]->getName() + "\" had primitive content!", *child); valid = false; continue; } // otherwise this is a StructuredEntity Handle c = child.cast(); StructuredClass *classPtr = c->getDescriptor().cast().get(); // check if its class is allowed. if (childClasses.find(classPtr) == childClasses.end()) { logger.error( std::string("An instance of \"") + c->getDescriptor()->getName() + "\" is not allowed as child of an instance of \"" + descriptor->getName() + "\" in field \"" + fieldDescs[f]->getName() + "\"", *child); valid = false; continue; } // note the number of occurences for this class and all // superclasses, because a subclass instance should count for // superclasses as well. while (classPtr != nullptr && childClasses.find(classPtr) != childClasses.end()) { const auto &n = nums.find(classPtr); if (n != nums.end()) { n->second++; } else { nums.emplace(classPtr, 1); } classPtr = classPtr->getSuperclass().get(); } } // now check if the cardinalities are right. for (auto childClass : childClasses) { const auto &n = nums.find(childClass); unsigned int num = 0; if (n != nums.end()) { num = n->second; } if (!childClass->getCardinality().asCardinality().contains(num)) { logger.error(std::string("Field \"") + fieldDescs[f]->getName() + "\" had " + std::to_string(num) + " elements of class \"" + childClass->getName() + "\", which is invalid according to the " "definition of \"" + descriptor->getName() + "\"", *subInst); valid = false; continue; } } } // go into recursion. for (auto f : fields) { for (auto n : f) { valid = valid & n->validate(logger); } } return valid; } void DocumentEntity::setAttributes(const Variant &a) { invalidateSubInstance(); attributes = a; } static int enforceGetFieldDescriptorIndex(Handle desc, const std::string &fieldName) { ssize_t idx = desc->getFieldDescriptorIndex(fieldName); if (idx == -1) { throw OusiaException(std::string("Descriptor \"") + desc->getName() + "\" has no field with the name \"" + fieldName + "\""); } return idx; } static int enforceGetFieldDescriptorIndex( Handle desc, Handle fieldDescriptor) { ssize_t idx = desc->getFieldDescriptorIndex(fieldDescriptor); if (idx == -1) { throw OusiaException(std::string("Descriptor \"") + desc->getName() + "\" does not reference the given field \"" + fieldDescriptor->getName() + "\""); } return idx; } const NodeVector &DocumentEntity::getField( const std::string &fieldName) const { return fields[enforceGetFieldDescriptorIndex(descriptor, fieldName)]; } const NodeVector &DocumentEntity::getField( Handle fieldDescriptor) const { return fields[enforceGetFieldDescriptorIndex(descriptor, fieldDescriptor)]; } const NodeVector &DocumentEntity::getField( const size_t &idx) const { if (idx >= fields.size()) { throw OusiaException(std::string("Descriptor \"") + descriptor->getName() + "\" does not have enough fields for index \"" + std::to_string(idx) + "\"."); } return fields[idx]; } void DocumentEntity::addStructureNode(Handle s, const int &i) { // only add the new node if we don't have it already. auto it = fields[i].find(s); if (it == fields[i].end()) { invalidateSubInstance(); fields[i].push_back(s); } Handle par = s->getParent(); if (par != subInst) { // if a previous parent existed, remove the StructureNode from it if (par != nullptr) { if (par->isa(&RttiTypes::StructuredEntity)) { par.cast()->removeStructureNode(s); } else { par.cast()->removeStructureNode(s); } } s->setParent(subInst); } } void DocumentEntity::addStructureNode(Handle s, Handle fieldDescriptor) { addStructureNode( s, enforceGetFieldDescriptorIndex(descriptor, fieldDescriptor)); } void DocumentEntity::addStructureNodes( const std::vector> &ss, Handle fieldDescriptor) { const int i = enforceGetFieldDescriptorIndex(descriptor, fieldDescriptor); for (Handle s : ss) { addStructureNode(s, i); } } void DocumentEntity::addStructureNode(Handle s, const std::string &fieldName) { addStructureNode(s, enforceGetFieldDescriptorIndex(descriptor, fieldName)); } void DocumentEntity::addStructureNodes( const std::vector> &ss, const std::string &fieldName) { const int idx = enforceGetFieldDescriptorIndex(descriptor, fieldName); for (Handle s : ss) { addStructureNode(s, idx); } } bool DocumentEntity::removeStructureNodeFromField(Handle s, const int &i) { auto it = fields[i].find(s); if (it != fields[i].end()) { invalidateSubInstance(); fields[i].erase(it); s->setParent(nullptr); return true; } return false; } bool DocumentEntity::removeStructureNodeFromField(Handle s, const std::string &fieldName) { return removeStructureNodeFromField( s, enforceGetFieldDescriptorIndex(descriptor, fieldName)); } bool DocumentEntity::removeStructureNodeFromField( Handle s, Handle fieldDescriptor) { return removeStructureNodeFromField( s, enforceGetFieldDescriptorIndex(descriptor, fieldDescriptor)); } bool DocumentEntity::removeStructureNode(Handle s) { for (auto field : fields) { auto it = field.find(s); if (it != field.end()) { invalidateSubInstance(); field.erase(it); s->setParent(nullptr); return true; } } return false; } Rooted DocumentEntity::createChildStructuredEntity( Handle descriptor, Variant attributes, const std::string &fieldName, std::string name) { return Rooted{new StructuredEntity( subInst->getManager(), subInst, descriptor, std::move(attributes), fieldName, std::move(name))}; } Rooted DocumentEntity::createChildDocumentPrimitive( Variant content, const std::string &fieldName) { return Rooted{new DocumentPrimitive( subInst->getManager(), subInst, std::move(content), fieldName)}; } Rooted DocumentEntity::createChildAnchor(const std::string &fieldName) { return Rooted{ new Anchor(subInst->getManager(), subInst, fieldName)}; } /* Class StructureNode */ bool StructureNode::doValidate(Logger &logger) const { bool valid = true; // check name if (!getName().empty()) { valid = validateName(logger); } // check the parent. if (getParent() == nullptr) { logger.error("The parent is not set!", *this); valid = false; } if (!getParent()->isa(&RttiTypes::StructuredEntity) && !getParent()->isa(&RttiTypes::AnnotationEntity) && !getParent()->isa(&RttiTypes::Document)) { logger.error("The parent does not have a valid type!", *this); valid = false; } return valid; } StructureNode::StructureNode(Manager &mgr, std::string name, Handle parent, const std::string &fieldName) : Node(mgr, std::move(name), parent) { if (parent->isa(&RttiTypes::StructuredEntity)) { parent.cast()->addStructureNode(this, fieldName); } else if (parent->isa(&RttiTypes::AnnotationEntity)) { parent.cast()->addStructureNode(this, fieldName); } else { throw OusiaException("The proposed parent was no DocumentEntity!"); } } /* Class StructuredEntity */ StructuredEntity::StructuredEntity(Manager &mgr, Handle doc, Handle descriptor, Variant attributes, std::string name) : StructureNode(mgr, std::move(name), doc), DocumentEntity(this, descriptor, std::move(attributes)) { doc->setRoot(this); } StructuredEntity::StructuredEntity(Manager &mgr, Handle parent, Handle descriptor, Variant attributes, std::string name) : StructureNode(mgr, std::move(name), parent), DocumentEntity(this, descriptor, std::move(attributes)) { } bool StructuredEntity::doValidate(Logger &logger) const { // check the validity as a StructureNode and as a DocumentEntity. return StructureNode::doValidate(logger) & DocumentEntity::doValidate(logger); } /* Class Anchor */ bool Anchor::doValidate(Logger &logger) const { bool valid = true; // check name if (!getName().empty()) { logger.error( "This anchor has a name! Anchors should only be referred to by " "reference, not by name!", *this); valid = false; } if (annotation == nullptr) { // this is valid but should throw a warning. logger.warning("This anchor is disconnected.", *this); } return valid & StructureNode::doValidate(logger); } void Anchor::setAnnotation(Handle anno, bool start) { if (annotation == anno) { return; } invalidate(); // unset the old reference. if (annotation != nullptr) { if (isStart()) { annotation->setStart(nullptr); } else { annotation->setEnd(nullptr); } } annotation = acquire(anno); // set the new reference. if (anno != nullptr) { if (start) { anno->setStart(this); } else { anno->setEnd(this); } } } bool Anchor::isStart() const { if (annotation == nullptr) { return false; } return annotation->getStart() == this; } bool Anchor::isEnd() const { if (annotation == nullptr) { return false; } return annotation->getEnd() == this; } /* Class AnnotationEntity */ AnnotationEntity::AnnotationEntity(Manager &mgr, Handle parent, Handle descriptor, Handle start, Handle end, Variant attributes, std::string name) : Node(mgr, std::move(name), parent), DocumentEntity(this, descriptor, attributes) { if (parent != nullptr) { parent->addAnnotation(this); } setStart(start); setEnd(end); } bool AnnotationEntity::doValidate(Logger &logger) const { bool valid = true; // check name if (!getName().empty()) { valid = valid & validateName(logger); } // check if this AnnotationEntity is correctly registered at its document. if (getParent() == nullptr) { logger.error("The parent is not set!", *this); valid = false; } else if (!getParent()->isa(&RttiTypes::Document)) { logger.error("The parent is not a document!", *this); valid = false; } else { Handle doc = getParent().cast(); bool found = false; for (auto &a : doc->getAnnotations()) { if (a == this) { found = true; break; } } if (!found) { logger.error("This annotation was not registered at the document.", *this); valid = false; } // check if the Anchors are part of the right document. if (start == nullptr) { logger.error("This annotation has no start Anchor!", *this); valid = false; } else if (!doc->hasChild(start)) { logger.error( "This annotations start anchor was not part of the same " "document!", *this); valid = false; } if (end == nullptr) { logger.error("This annotation has no end Anchor!", *this); valid = false; } else if (!doc->hasChild(end)) { logger.error( "This annotations end anchor was not part of the same " "document!", *this); valid = false; } } // check if the Anchors reference this AnnotationEntity correctly. if (start != nullptr) { if (start->getAnnotation() != this) { logger.error( "This annotations start anchor does not have the correct " "annotation as parent!", *this); valid = false; } } if (end != nullptr) { if (end->getAnnotation() != this) { logger.error( "This annotations end anchor does not have the correct " "annotation as parent!", *this); valid = false; } } // check the validity as a DocumentEntity. return valid & DocumentEntity::doValidate(logger); } void AnnotationEntity::setStart(Handle s) { if (start == s) { return; } invalidate(); start = acquire(s); s->setAnnotation(this, true); } void AnnotationEntity::setEnd(Handle e) { if (end == e) { return; } invalidate(); end = acquire(e); e->setAnnotation(this, false); } /* Class Document */ void Document::doResolve(ResolutionState &state) { continueResolveComposita(annotations, annotations.getIndex(), state); if (root != nullptr) { continueResolveCompositum(root, state); } continueResolveReferences(domains, state); } bool Document::doValidate(Logger &logger) const { // An empty document is always invalid. TODO: Is this a smart choice? bool valid = true; if (root == nullptr) { logger.error("This document is empty (it has no root)!", *this); valid = false; } else { // check if the root is allowed to be a root. if (!root->getDescriptor() .cast() ->hasRootPermission()) { logger.error(std::string("A node of type \"") + root->getDescriptor()->getName() + "\" is not allowed to be the Document root!", *root); valid = false; } // check if it has this document as parent. if (root->getParent() != this) { logger.error( "The document root does not have the document as parent!", *root); valid = false; } // then call validate on the root valid = valid & root->validate(logger); } // call validate on the AnnotationEntities return valid & continueValidation(annotations, logger); } void Document::doReference(Handle node) { if (node->isa(&RttiTypes::Domain)) { referenceDomain(node.cast()); } } RttiSet Document::doGetReferenceTypes() const { return RttiSet{&RttiTypes::Domain}; } Rooted Document::createRootStructuredEntity( Handle descriptor, Variant attributes, std::string name) { return Rooted{ new StructuredEntity(getManager(), Handle{this}, descriptor, attributes, std::move(name))}; } void Document::addAnnotation(Handle a) { // only add it if we need to. if (annotations.find(a) == annotations.end()) { invalidate(); annotations.push_back(a); } Handle par = a->getParent(); if (par != this) { if (par != nullptr) { // remove the StructuredClass from the old parent. par.cast()->removeAnnotation(a); } a->setParent(this); } } void Document::addAnnotations(const std::vector> &as) { for (Handle a : as) { addAnnotation(a); } } bool Document::removeAnnotation(Handle a) { auto it = annotations.find(a); if (it != annotations.end()) { invalidate(); annotations.erase(it); a->setParent(nullptr); return true; } return false; } Rooted Document::createChildAnnotation( Handle descriptor, Handle start, Handle end, Variant attributes, std::string name) { return Rooted{ new AnnotationEntity(getManager(), this, descriptor, start, end, attributes, std::move(name))}; } bool Document::hasChild(Handle s) const { Rooted parent = s->getParent(); if (parent->isa(&RttiTypes::StructureNode)) { return hasChild(parent.cast()); } else if (parent->isa(&RttiTypes::AnnotationEntity)) { Handle a = parent.cast(); return this == a->getParent(); } else if (parent->isa(&RttiTypes::Document)) { return this == parent; } return false; } void Document::setRoot(Handle rt) { if (rt == nullptr || root == rt) { return; } if (root != nullptr) { root->setParent(nullptr); } root = acquire(rt); if (rt->getParent() != this) { rt->setParent(this); } invalidate(); } /* Type registrations */ namespace RttiTypes { const Rtti Document = RttiBuilder("Document") .parent(&RootNode) .composedOf({&AnnotationEntity, &StructuredEntity}); const Rtti StructureNode = RttiBuilder("StructureNode").parent(&Node); const Rtti StructuredEntity = RttiBuilder("StructuredEntity") .parent(&StructureNode) .composedOf({&StructuredEntity, &DocumentPrimitive, &Anchor}); const Rtti DocumentPrimitive = RttiBuilder( "DocumentPrimitive").parent(&StructureNode); const Rtti Anchor = RttiBuilder("Anchor").parent(&StructureNode); const Rtti AnnotationEntity = RttiBuilder("AnnotationEntity") .parent(&Node) .composedOf({&StructuredEntity, &DocumentPrimitive, &Anchor}); } }