Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions phlex/model/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ cet_make_library(
layer_path.cpp
product_store.cpp
products.cpp
data_product_concept.cpp
data_product_concept_registry.cpp
product_specification.cpp
LIBRARIES
PUBLIC
Expand Down Expand Up @@ -45,6 +47,8 @@ install(
product_store.hpp
products.hpp
type_id.hpp
data_product_concept.hpp
data_product_concept_registry.hpp
DESTINATION include/phlex/model
)

Expand Down
39 changes: 39 additions & 0 deletions phlex/model/data_product_concept.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#include "phlex/model/data_product_concept.hpp"

#include <stdexcept>
#include <typeinfo>

namespace phlex::experimental {

data_product_concept::data_product_concept(std::string name) : name_(std::move(name))
{
if (name_.empty()) {
throw std::invalid_argument("Data product concept name cannot be empty");
}
}

std::string const& data_product_concept::name() const noexcept { return name_; }

std::unordered_set<concrete_product_id> const& data_product_concept::concrete_types()
const noexcept
{
return concrete_types_;
}

void data_product_concept::add_concrete_type(concrete_product_id type)
{
concrete_types_.insert(type);
}

void data_product_concept::add_concrete_types(
std::unordered_set<concrete_product_id> const& types)
{
concrete_types_.insert(types.begin(), types.end());
}

bool data_product_concept::has_concrete_type(concrete_product_id type) const
{
return concrete_types_.find(type) != concrete_types_.end();
}

} // namespace phlex::experimental
50 changes: 50 additions & 0 deletions phlex/model/data_product_concept.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
#ifndef PHLEX_MODEL_DATA_PRODUCT_CONCEPT_HPP
#define PHLEX_MODEL_DATA_PRODUCT_CONCEPT_HPP

#include "phlex/model/identifier.hpp"
#include "phlex/phlex_model_export.hpp"

#include <string>
#include <typeindex>
#include <typeinfo>
#include <unordered_set>
#include <vector>

namespace phlex::experimental {

// Type alias for std::type_index, representing a concrete data product type.
using concrete_product_id = std::type_index;

// Represents a data product concept.
//
// A data product concept is a named category that groups related concrete
// data product types. For example, "hit" could be a concept that encompasses
// multiple hit types (HitV1, HitV2, etc.).
//
// Each data_product_concept has:
// - A unique name
// - A set of concrete types that model this concept
class PHLEX_MODEL_EXPORT data_product_concept {
public:
explicit data_product_concept(std::string name);

std::string const& name() const noexcept;

std::unordered_set<concrete_product_id> const& concrete_types() const noexcept;

void add_concrete_type(concrete_product_id type);

void add_concrete_types(std::unordered_set<concrete_product_id> const& types);

bool has_concrete_type(concrete_product_id type) const;

bool operator==(data_product_concept const&) const = default;

private:
std::string name_;
std::unordered_set<concrete_product_id> concrete_types_;
};

} // namespace phlex::experimental

#endif // PHLEX_MODEL_DATA_PRODUCT_CONCEPT_HPP
99 changes: 99 additions & 0 deletions phlex/model/data_product_concept_registry.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
#include "phlex/model/data_product_concept_registry.hpp"

#include <algorithm>
#include <mutex>
#include <stdexcept>
#include <utility>

namespace phlex::experimental {

bool data_product_concept_registry::register_concept(std::unique_ptr<data_product_concept> con)
{
if (!con) {
throw std::invalid_argument{"Cannot register null concept"};
}
auto name = con->name(); // Copy the name before moving from con.

std::unique_lock lock{mutex_};
if (auto* existing = find_concept_unlocked(name)) {
existing->add_concrete_types(con->concrete_types());
return false;
}
concepts_.emplace(std::move(name), std::move(con));
return true;
}

bool data_product_concept_registry::add_concrete_type(std::string const& name,
concrete_product_id type)
{
std::unique_lock lock{mutex_};
if (auto* existing = find_concept_unlocked(name)) {
existing->add_concrete_type(type);
return false;
}

auto con = std::make_unique<data_product_concept>(name);
con->add_concrete_type(type);
concepts_.emplace(name, std::move(con));
return true;
}

data_product_concept* data_product_concept_registry::find_concept_unlocked(
std::string const& name) const
{
auto it = concepts_.find(name);
if (it != concepts_.end()) {
return it->second.get();
}
return nullptr;
}

data_product_concept const* data_product_concept_registry::find_concept(
std::string const& name) const
{
std::shared_lock lock{mutex_};
return find_concept_unlocked(name);
}

data_product_concept* data_product_concept_registry::find_concept(std::string const& name)
{
std::shared_lock lock{mutex_};
return find_concept_unlocked(name);
}

std::vector<std::string> data_product_concept_registry::all_concept_names() const
{
std::shared_lock lock{mutex_};
std::vector<std::string> names;
names.reserve(concepts_.size());
for (auto const& [name, _] : concepts_) {
names.push_back(name);
}
std::sort(names.begin(), names.end());
return names;
}

bool data_product_concept_registry::type_matches_concept(std::string const& concept_name,
concrete_product_id type) const
{
std::shared_lock lock{mutex_};
auto const* con = find_concept_unlocked(concept_name);
if (!con) {
return false;
}
return con->has_concrete_type(type);
}

data_product_concept const* data_product_concept_registry::get_concept_for_type(
concrete_product_id type) const
{
std::shared_lock lock{mutex_};
for (auto const& [name, con] : concepts_) {
if (con->has_concrete_type(type)) {
return con.get();
}
}
return nullptr;
}

} // namespace phlex::experimental
92 changes: 92 additions & 0 deletions phlex/model/data_product_concept_registry.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
#ifndef PHLEX_MODEL_DATA_PRODUCT_CONCEPT_REGISTRY_HPP
#define PHLEX_MODEL_DATA_PRODUCT_CONCEPT_REGISTRY_HPP

#include "phlex/model/data_product_concept.hpp"
#include "phlex/phlex_model_export.hpp"

#include <memory>
#include <shared_mutex>
#include <string>
#include <unordered_map>
#include <vector>

namespace phlex::experimental {

// Registry for data product concepts.
//
// This registry stores data_product_concept instances and allows
// lookup by name. Each concept can be associated with one or more
// concrete data product types.
//
// All public operations are individually safe for concurrent invocation:
// a registry-level std::shared_mutex protects the map and every access to
// a stored concept's concrete-type set that is made through the registry's
// own methods. This guarantee does not extend past the return of a call.
// In particular, the non-const find_concept overload and
// get_concept_for_type return a pointer to a stored concept; the caller
// must not dereference or mutate through that pointer concurrently with a
// registry write, or concurrently with another caller's mutation via a
// similarly obtained pointer, since such access is not covered by the
// registry's lock.
class PHLEX_MODEL_EXPORT data_product_concept_registry {
public:
data_product_concept_registry() = default;
~data_product_concept_registry() = default;

// Register a data product concept.
//
// Returns true if a new concept was inserted. Returns false if a
// concept with the same name was already registered, in which case
// the incoming concept's concrete types are merged into the existing
// concept's set and the incoming concept is discarded.
//
// Throws std::invalid_argument if con is null.
bool register_concept(std::unique_ptr<data_product_concept> con);

// Register a concrete data product type with the concept of the given name.
// If necessary, a new concept is added to the registry.
// Return true if a new concept was registered.
bool add_concrete_type(std::string const& name, concrete_product_id type);

// Find a data product concept by name.
//
// The returned pointer is not safe to dereference (or, for the
// non-const overload, to mutate through) concurrently with a registry
// write after this method returns.
data_product_concept const* find_concept(std::string const& name) const;
data_product_concept* find_concept(std::string const& name);

// Get all registered concept names.
std::vector<std::string> all_concept_names() const;

// Check if a concrete type is associated with a concept.
bool type_matches_concept(std::string const& concept_name, concrete_product_id type) const;

// Get the concept associated with a concrete type.
//
// The returned pointer is not safe to dereference concurrently with a
// registry write after this method returns.
data_product_concept const* get_concept_for_type(concrete_product_id type) const;

// data_product_concept_registry objects can be neither copied nor moved.
data_product_concept_registry(data_product_concept_registry const&) = delete;
data_product_concept_registry& operator=(data_product_concept_registry const&) = delete;
data_product_concept_registry(data_product_concept_registry&&) noexcept = delete;
data_product_concept_registry& operator=(data_product_concept_registry&&) noexcept = delete;

private:
// Assumes the caller already holds mutex_ (shared or exclusive).
// Performs a plain, unlocked lookup. This overload is const yet returns
// a non-const pointer (legal, since the pointee is reached through
// std::unique_ptr const&::get()); a single helper serves both the
// const and non-const find_concept overloads, so the const overload's
// constness is nominal only as far as the pointee is concerned.
data_product_concept* find_concept_unlocked(std::string const& name) const;

mutable std::shared_mutex mutex_;
std::unordered_map<std::string, std::unique_ptr<data_product_concept>> concepts_;
};

} // namespace phlex::experimental

#endif // PHLEX_MODEL_DATA_PRODUCT_CONCEPT_REGISTRY_HPP
2 changes: 2 additions & 0 deletions phlex/model/fwd.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ namespace phlex::detail {
}

namespace phlex::experimental {
class data_product_concept;
class data_product_concept_registry;
class product_store;

using product_store_const_ptr = std::shared_ptr<product_store const>;
Expand Down
17 changes: 17 additions & 0 deletions test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,23 @@ cet_test(
phlex::model_internal
phlex::utilities_internal
)
cet_test(
data_product_concept
USE_CATCH2_MAIN
SOURCE
data_product_concept_test.cpp
LIBRARIES
phlex::model
)
cet_test(
data_product_concept_registry
USE_CATCH2_MAIN
SOURCE
data_product_concept_registry_test.cpp
LIBRARIES
phlex::model
TBB::tbb
)
cet_test(data_cell_index USE_CATCH2_MAIN SOURCE data_cell_index_test.cpp LIBRARIES
phlex::model_internal
)
Expand Down
Loading
Loading