#include "IfcFile.h" #include "IfcLogger.h" IfcParse::parse_context::~parse_context() { for (auto& t : tokens_) { boost::apply_visitor([](auto& v) { if constexpr (std::is_same_v, parse_context*>) { delete v; } }, t); } } IfcParse::parse_context& IfcParse::parse_context::push() { auto* pc = new IfcParse::parse_context; tokens_.push_back(pc); return *pc; } void IfcParse::parse_context::push(Token t) { tokens_.push_back(t); } void IfcParse::parse_context::push(IfcUtil::IfcBaseClass* inst) { tokens_.push_back(inst); } namespace { template struct is_type_in_variant; // Specialization when there are multiple types in the variant template struct is_type_in_variant, T> { static constexpr bool value = std::is_same::value || is_type_in_variant, T>::value; }; // Specialization when there is only one type left in the variant template struct is_type_in_variant, T> { static constexpr bool value = std::is_same::value; }; template constexpr bool is_type_in_variant_v = is_type_in_variant::value; struct InstanceReference { int v; operator int() const { return v; } }; template void dispatch_token(int instance_id, int attribute_id, IfcParse::Token t, IfcParse::declaration* decl, Fn fn) { if (t.type == IfcParse::Token_BINARY) { fn(IfcParse::TokenFunc::asBinary(t)); } else if (t.type == IfcParse::Token_BOOL) { fn(IfcParse::TokenFunc::asBool(t)); } else if (t.type == IfcParse::Token_ENUMERATION) { auto& s = IfcParse::TokenFunc::asStringRef(t); if (decl && decl->as_enumeration_type()) { try { fn(EnumerationReference(decl->as_enumeration_type(), decl->as_enumeration_type()->lookup_enum_offset(s))); } catch (IfcParse::IfcException& e) { Logger::Error("An enumeration literal '" + s + "' is not valid for type '" + decl->name() + "' at offset " + std::to_string(t.startPos)); } } else { Logger::Error("An enumeration literal '" + s + "' is not expected at attribute index '" + std::to_string(attribute_id) + "' at offset " + std::to_string(t.startPos)); } } else if (t.type == IfcParse::Token_FLOAT) { fn(IfcParse::TokenFunc::asFloat(t)); } else if (t.type == IfcParse::Token_IDENTIFIER) { fn(IfcParse::reference_or_simple_type{ InstanceReference{ IfcParse::TokenFunc::asIdentifier(t) } }); } else if (t.type == IfcParse::Token_INT) { fn(IfcParse::TokenFunc::asInt(t)); } else if (t.type == IfcParse::Token_STRING) { fn(IfcParse::TokenFunc::asStringRef(t)); } else if (t.type == IfcParse::Token_OPERATOR && t.value_char == '*') { // This is only in place for the validator fn(Derived{}); } } template void construct_(int instance_id, int attribute_id, IfcParse::parse_context& p, const IfcParse::aggregation_type* aggr, Fn fn) { if (p.tokens_.empty()) { // @todo instead of ugly if-else we could also default initialize the respective // variant types below. if (aggr) { auto aggr_type = IfcUtil::make_aggregate(IfcUtil::from_parameter_type(aggr->type_of_element())); if (aggr_type == IfcUtil::Argument_AGGREGATE_OF_INT) { fn(std::vector{}); } else if (aggr_type == IfcUtil::Argument_AGGREGATE_OF_DOUBLE) { fn(std::vector{}); } else if (aggr_type == IfcUtil::Argument_AGGREGATE_OF_STRING) { fn(std::vector{}); } else if (aggr_type == IfcUtil::Argument_AGGREGATE_OF_BINARY) { fn(std::vector>{}); } else if (aggr_type == IfcUtil::Argument_AGGREGATE_OF_ENTITY_INSTANCE) { fn(aggregate_of_instance::ptr(new aggregate_of_instance)); } else if (aggr_type == IfcUtil::Argument_AGGREGATE_OF_AGGREGATE_OF_INT) { fn(std::vector>{}); } else if (aggr_type == IfcUtil::Argument_AGGREGATE_OF_AGGREGATE_OF_DOUBLE) { fn(std::vector>{}); } else if (aggr_type == IfcUtil::Argument_AGGREGATE_OF_AGGREGATE_OF_ENTITY_INSTANCE) { fn(aggregate_of_aggregate_of_instance::ptr(new aggregate_of_aggregate_of_instance)); } } return; } typedef boost::variant< Blank, std::vector, std::vector, std::vector, std::vector>, std::vector, std::vector>, std::vector>, std::vector> > possible_aggregation_types_t; possible_aggregation_types_t aggregate_storage; auto append_to_aggregate_storage = [&aggregate_storage](const auto& v) { if constexpr (is_type_in_variant_v>>) { if (aggregate_storage.which() == 0) { aggregate_storage = std::vector>{ v }; } else { if (auto* vec_ptr = boost::get>>(&aggregate_storage)) { vec_ptr->push_back(v); } else { if constexpr (std::is_same_v, int>) { auto* vec_ptr2 = boost::get>(&aggregate_storage); if (vec_ptr2) { // double[] + int vec_ptr2->push_back((double) v); } } if constexpr (std::is_same_v, double>) { auto* vec_ptr2 = boost::get>(&aggregate_storage); if (vec_ptr2) { // int[] -> double[] + double std::vector ps(vec_ptr2->begin(), vec_ptr2->end()); ps.push_back(v); aggregate_storage = ps; } } if constexpr (std::is_same_v, std::vector>) { auto* vec_ptr2 = boost::get>>(&aggregate_storage); if (vec_ptr2) { // double[][] + int[] std::vector vd(v.begin(), v.end()); vec_ptr2->push_back(vd); } } if constexpr (std::is_same_v, std::vector>) { auto* vec_ptr2 = boost::get>>(&aggregate_storage); if (vec_ptr2) { // int[][] -> double[][] + double[] std::vector> vvd; for (auto& vv : *vec_ptr2) { std::vector vd(vv.begin(), vv.end()); vvd.push_back(vd); } vvd.push_back(v); aggregate_storage = vvd; } } // @todo would be cool if we can trace this back to file offset auto current = boost::apply_visitor([](auto v) { if constexpr (!std::is_same_v) { return std::string(typeid(typename decltype(v)::value_type).name()); } else { // Cannot occur as aggregate_storage.which() == 0 // is another branch several statements up. But is // needed for consistency of return type. return std::string{}; } }, aggregate_storage); Logger::Error("Inconsistent aggregate valuation while attempting to append " + std::string(typeid(decltype(v)).name()) + " to an aggregate of " + current); // @todo boolean -> logical upgrade // wait a second... there are no aggregate of bool / logical in the schema.. // // if constexpr (std::is_same_v, bool>) { // auto* vec_ptr = boost::get(&aggregate_storage); // vec_ptr->push_back(v); // } // if constexpr (std::is_same_v, boost::tribool>) { // auto* vec_ptr = boost::get(&aggregate_storage); // std::vector ps(vec_ptr->begin(), vec_ptr->end()); // ps.push_back(v); // aggregate_storage = ps; // } } } } else { // @todo would be cool if we can trace this back to file offset Logger::Error(std::string("Aggregates of ") + typeid(decltype(v)).name() + " are not supported in the IfcOpenShell parser"); } }; for (auto& t : p.tokens_) { boost::apply_visitor([&aggregate_storage, &append_to_aggregate_storage, aggr, instance_id, attribute_id](const auto& v) { if constexpr (std::is_same_v, IfcParse::Token>) { // @todo get aggregate of enumeration dispatch_token(instance_id, attribute_id, v, aggr && aggr->type_of_element()->as_named_type() ? aggr->type_of_element()->as_named_type()->declared_type() : nullptr, append_to_aggregate_storage); } else if constexpr (std::is_same_v, IfcParse::parse_context*>) { // nested list if constexpr (Depth < 3) { construct_(instance_id, attribute_id, *v, nullptr, append_to_aggregate_storage); } } else { append_to_aggregate_storage(IfcParse::reference_or_simple_type{ v }); } }, t); } boost::apply_visitor(fn, aggregate_storage); } } IfcEntityInstanceData IfcParse::parse_context::construct(int name, unresolved_references& references_to_resolve, const IfcParse::declaration* decl, boost::optional expected_size) { std::vector parameter_types; std::unique_ptr transient_named_type; if ((decl != nullptr) && (decl->as_type_declaration() != nullptr)) { parameter_types = { decl->as_type_declaration()->declared_type() }; } else if ((decl != nullptr) && (decl->as_enumeration_type() != nullptr)) { transient_named_type.reset(new IfcParse::named_type(const_cast(decl))); parameter_types = { &*transient_named_type }; } else if ((decl != nullptr) && (decl->as_entity() != nullptr)) { auto entity_attrs = decl->as_entity()->all_attributes(); std::transform( entity_attrs.begin(), entity_attrs.end(), std::back_inserter(parameter_types), [](auto* attr) { return attr->type_of_attribute(); } ); } if (((decl != nullptr) && (tokens_.size() != parameter_types.size())) || expected_size && *expected_size != tokens_.size()) { size_t expected = expected_size ? *expected_size : parameter_types.size(); Logger::Warning("Expected " + std::to_string(expected) + " attribute values, found " + std::to_string(tokens_.size()) + " for instance #" + std::to_string(name > 0 ? name : 0)); } if (tokens_.empty()) { return IfcEntityInstanceData(in_memory_attribute_storage(0)); } in_memory_attribute_storage storage(decl != nullptr ? (std::min)(parameter_types.size(), tokens_.size()) : tokens_.size() ); auto it = tokens_.begin(); auto kt = parameter_types.begin(); for (; it != tokens_.end() && ((decl == nullptr) || kt != parameter_types.end()); ++it) { auto& token = *it; // @todo coerce to expected type, e.g empty -> std::vector, bool -> logical const IfcParse::parameter_type* param_type = nullptr; if (decl != nullptr) { param_type = *kt; } auto index = (uint8_t) std::distance(tokens_.begin(), it); boost::apply_visitor([this, &storage, name, &references_to_resolve, index, param_type](const auto& v) { if constexpr (std::is_same_v, IfcParse::Token>) { dispatch_token(name, index, v, param_type && param_type->as_named_type() ? param_type->as_named_type()->declared_type() : nullptr, [this, &storage, name, &references_to_resolve, index](auto v) { if constexpr (std::is_same_v, IfcParse::reference_or_simple_type>) { if (name > 0) { references_to_resolve.push_back(std::make_pair( // @todo previously this was storage but apparently the // pointer is not constant with the moving and temporary nature // maybe it ought to be and in that case a pointer is more direct MutableAttributeValue{ name, index }, v )); } } else { storage.set(index, v); } }); } else if constexpr (std::is_same_v, IfcParse::parse_context*>) { const auto *pt = param_type; if (pt) { while (pt->as_named_type() && pt->as_named_type()->declared_type()->as_type_declaration()) { pt = pt->as_named_type()->declared_type()->as_type_declaration()->declared_type(); } } construct_<0>(name, index, *v, pt ? pt->as_aggregation_type() : nullptr, [this, &storage, name, &references_to_resolve, index](const auto& v) { if constexpr (std::is_same_v, std::vector>) { if (name > 0) { references_to_resolve.push_back({ {name, index }, v }); } } else if constexpr (std::is_same_v, std::vector>>) { if (name > 0) { references_to_resolve.push_back({ {name, index }, v }); } } else { storage.set(index, v); } }); } else { storage.set(index, v); } }, token); if (decl != nullptr) { ++kt; } } return IfcEntityInstanceData(std::move(storage)); } IfcUtil::IfcBaseClass* IfcParse::impl::rocks_db_file_storage::rocksdb_instance_iterator::operator*() const { auto it = storage_->byid_.find(*read_id_()); if (it != storage_->byid_.end()) { // @todo define an implicit std::to_string() in all map adapters with leading 0s auto jt = storage_->instance_cache_.find(it->second); if (jt != storage_->instance_cache_.end()) { return jt->second; } else { return storage_->assert_existance(it->second); } } } const IfcParse::declaration* IfcParse::impl::rocks_db_file_storage::rocksdb_types_iterator::operator*() const { return storage_->file->schema()->declarations()[*read_id_()]; } IfcUtil::IfcBaseClass* IfcParse::impl::rocks_db_file_storage::assert_existance(size_t instanceId) { std::string v; rocksdb::Status s = db->Get(rocksdb::ReadOptions{}, "i|" + std::to_string(instanceId) + "|t", &v); if (s.ok()) { size_t s; memcpy(&s, v.data(), sizeof(size_t)); auto decl = file->schema()->declarations()[s]; IfcEntityInstanceData data(rocks_db_attribute_storage{}); auto inst = file->schema()->instantiate(decl, std::move(data)); inst->id_ = instanceId; instance_cache_.insert({ inst->identity(), inst }); byid_.insert({ inst->id(), inst->identity() }); return inst; } throw std::runtime_error(""); } #include "rocksdb/merge_operator.h" namespace { class ConcatenateIdMergeOperator : public rocksdb::AssociativeMergeOperator { public: virtual bool Merge(const rocksdb::Slice& key, const rocksdb::Slice* existing_value, const rocksdb::Slice& value, std::string* new_value, rocksdb::Logger* logger) const override { if (existing_value) { new_value->assign(existing_value->data(), existing_value->size()); new_value->append(value.data(), value.size()); } else { new_value->assign(value.data(), value.size()); } return true; } virtual const char* Name() const override { return "ConcatenateIdMergeOperator"; } }; } // @todo naming IfcParse::impl::rocks_db_file_storage::rocks_db_file_storage(const std::string& filepath, IfcParse::IfcFile* ffile) : file(ffile) // @todo db is not initialized here yet , byguid_internal_(db, "g|") , byguid_(&byguid_internal_, [this](size_t v) { return assert_existance(v); }, [](IfcUtil::IfcBaseClass* v) { return v->identity(); }) , byid_(db, "d|") , bytype_(db, "t|") { rocksdb::Options options; options.create_if_missing = true; options.merge_operator.reset(new ConcatenateIdMergeOperator()); rocksdb::Status status = rocksdb::DB::Open(options, filepath, &db); } IfcUtil::IfcBaseClass* IfcParse::impl::rocks_db_file_storage::instance_by_id(int id) { // @todo rename assert_existance() -> instance_by_id(); return assert_existance(id); } void IfcParse::impl::rocks_db_file_storage::process_deletion_inverse(IfcUtil::IfcBaseClass* inst) { auto id = inst->id(); { // compute next prefix that does not start with v|{id} auto prefix = "v|" + id; auto it = db->NewIterator(rocksdb::ReadOptions()); it->Seek(prefix); while (it->Valid()) { it->Next(); if (!it->key().starts_with(prefix)) { break; } } rocksdb::WriteBatch batch; batch.DeleteRange(prefix, it->key()); db->Write(rocksdb::WriteOptions{}, &batch); } // This is based on traversal which needs instances to still be contained in the map. // another option would be to keep byid intact for the remainder of this loop aggregate_of_instance::ptr entity_attributes = traverse(inst, 1); for (aggregate_of_instance::it it = entity_attributes->begin(); it != entity_attributes->end(); ++it) { IfcUtil::IfcBaseClass* entity_attribute = *it; if (entity_attribute == inst) { continue; } const unsigned int name = entity_attribute->id(); // Do not update inverses for simple types (which have id()==0 in IfcOpenShell). if (name != 0) { // Find instances entity -> other // and update inverses from entity into other { auto prefix = "v|" + name; auto it = db->NewIterator(rocksdb::ReadOptions()); it->Seek(prefix); while (it->Valid() && it->key().starts_with(prefix)) { std::string s = it->value().ToString(); // Iterator are snapshotted? So don't get invalidated? std::vector vals(s.size() / sizeof(size_t)); memcpy(vals.data(), s.data(), s.size()); vals.erase(std::find(vals.begin(), vals.end(), (size_t)id)); s.resize(vals.size() * sizeof(size_t)); memcpy(s.data(), vals.data(), s.size()); db->Put(rocksdb::WriteOptions{}, it->key(), s); } } } } } IfcUtil::IfcBaseClass* IfcParse::impl::in_memory_file_storage::instance_by_id(int id) { auto it = byid_.find(id); if (it == byid_.end()) { throw IfcException("Instance #" + boost::lexical_cast(id) + " not found"); } return it->second; }