32#include <unordered_map>
34#include "../BaseTypes.h"
39#include "../Sequence.h"
44#ifndef DOXYGEN_SHOULD_IGNORE_THIS
47struct JsonSchemaTypeNames
49 static constexpr std::array<std::string_view, 1> value{ T::JsonSchemaTypeName };
53struct JsonSchemaTypeNames<sequence::NoteBase>
55 static constexpr std::array<std::string_view, 2> value{
56 sequence::Note::JsonSchemaTypeName,
57 sequence::KitNote::JsonSchemaTypeName
62bool matchesTypeName(std::string_view typeName)
64 for (
const auto name : JsonSchemaTypeNames<T>::value) {
65 if (name == typeName) {
73std::string typeNamesExpectationString()
75 std::ostringstream oss;
76 const auto& names = JsonSchemaTypeNames<T>::value;
77 const auto count = names.size();
79 oss <<
"expected one of ";
83 for (
size_t i = 0; i < count; ++i) {
87 oss <<
"\"" << names[i] <<
"\"";
98 using runtime_error::runtime_error;
116 template <
typename T>
117 void add(
const std::string&
id,
const T& value)
119 auto result = m_objectMap.emplace(
id, MappedLocation{ value.pointer(), T::JsonSchemaTypeName });
120 if (!result.second) {
121 mapping_error err(
"ID " + formatKeyString(
id) +
" already exists for type \"" + std::string(result.first->second.typeName)
122 +
"\" at " + result.first->second.location.to_string());
123 if (m_errorHandler) {
124 m_errorHandler.value()(err.what(), value);
134 void addEventToBeam(
const std::string& eventId,
const part::Beam& beam)
137 if (!result.second) {
138 mapping_error err(
"ID " + formatKeyString(eventId) +
" already exists in beam "
139 + result.first->second.location.location.to_string());
140 if (m_errorHandler) {
141 m_errorHandler.value()(err.what(), beam);
149 void setEventBeamStartLevel(
const std::string& eventId,
int level)
151 auto it = m_eventsInBeams.find(eventId);
152 if (it == m_eventsInBeams.end()) {
153 throw std::logic_error(
"Attempted to assign a beam start level to unmapped event " + eventId);
155 if (it->second.startLevel == 0) {
156 it->second.startLevel = level;
158 it->second.startLevel = std::min(it->second.startLevel, level);
163 void setEventOttavaShift(
const std::string& eventPointer,
int shift)
165 m_eventOttavaShift[eventPointer] = shift;
176 explicit EntityMap(std::weak_ptr<json> documentRoot,
const std::optional<ErrorHandler>& errorHandler = std::nullopt)
177 : m_root(documentRoot), m_errorHandler(errorHandler) {}
192 template <
typename T>
194 const std::string&
id,
195 const std::optional<Base>& errorLocation = std::nullopt)
const
197 auto it = m_objectMap.find(
id);
198 if (it == m_objectMap.end()) {
201 MNX_ASSERT_IF(!detail::matchesTypeName<T>(it->second.typeName)) {
203 "ID " + formatKeyString(
id) +
" has type \"" + std::string(it->second.typeName) +
204 "\", but " + detail::typeNamesExpectationString<T>() +
"."
206 if (m_errorHandler) {
207 m_errorHandler.value()(err.what(), errorLocation.value_or(
Document(root())));
211 return T(root(), it->second.location);
222 template <
typename T>
223 T
get(
const std::string&
id,
const std::optional<Base>& errorLocation = std::nullopt)
const
225 if (
auto v = tryGet<T>(
id, errorLocation)) {
226 return *std::move(v);
228 mapping_error err(
"ID " + formatKeyString(
id) +
" not found in ID mapping");
229 if (m_errorHandler) {
230 m_errorHandler.value()(err.what(), errorLocation.value_or(
Document(root())));
246 template <
typename T>
247 size_t getIndexOf(
const std::string&
id,
const std::optional<Base>& errorLocation = std::nullopt)
const
249 static_assert(std::is_base_of_v<ArrayElementObject, T>,
250 "getIndexOf<T> requires T to derive from ArrayElementObject");
252 auto v = get<T>(
id, errorLocation);
253 return v.calcArrayIndex();
258 template <
typename T>
261 const auto it = m_objectMap.find(
id);
262 if (it == m_objectMap.end()) {
265 return detail::matchesTypeName<T>(it->second.typeName);
273 if (
const auto& eventId = event.id()) {
274 const auto it = m_eventsInBeams.find(eventId.value());
275 if (it != m_eventsInBeams.end()) {
278 "The beam mapping for eventId " + formatKeyString(eventId.value())
279 +
" was mapped to an object of type \"" + std::string(it->second.location.typeName) +
"\"."
281 if (m_errorHandler) {
282 m_errorHandler.value()(err.what(), event);
286 return part::Beam(root(), it->second.location.location);
295 const auto it = m_eventsInBeams.find(eventId);
296 if (it == m_eventsInBeams.end()) {
299 return it->second.startLevel;
315 m_eventsInBeams.clear();
316 m_eventOttavaShift.clear();
317 m_lyricLineOrder.clear();
323 const auto it = m_eventOttavaShift.find(event.
pointer().to_string());
324 if (it == m_eventOttavaShift.end()) {
334 return shift.value();
341 {
return m_lyricLineOrder; }
344 std::weak_ptr<json> m_root;
345 std::optional<ErrorHandler> m_errorHandler;
347 std::shared_ptr<json> root()
const
349 std::shared_ptr<json> result = m_root.lock();
351 throw std::runtime_error(
"ID mapping is invalid because the document was destroyed.");
356 using MappedLocation =
struct
359 std::string_view typeName;
361 std::unordered_map<std::string, MappedLocation> m_objectMap;
362 struct BeamMappingEntry
364 MappedLocation location;
367 std::unordered_map<std::string, BeamMappingEntry> m_eventsInBeams;
368 std::unordered_map<std::string, int> m_eventOttavaShift;
369 std::vector<std::string> m_lyricLineOrder;
371 static std::string formatKeyString(
const std::string& key) {
372 return "\"" + key +
"\"";
json_pointer pointer() const
Returns the json_pointer for this node.
Definition BaseTypes.h:260
Represents an MNX document and provides methods for loading and saving.
Definition Document.h:103
Contains information about each level of bea.
Definition Part.h:40
static constexpr std::string_view JsonSchemaTypeName
required for mapping
Definition Part.h:61
Represents a musical event within a sequence.
Definition Sequence.h:418
Provides type-safe ID-based lookup for elements in an MNX document.
Definition EntityMap.h:109
bool exists(const std::string &id) const
Returns whether the specified ID exists in the mapping with type T.
Definition EntityMap.h:259
std::optional< part::Beam > tryGetBeam(const sequence::Event &event) const
Get the beam for an event, if it is mapped.
Definition EntityMap.h:271
std::optional< int > tryGetOttavaShift(const sequence::Event &event) const
Retrieve the ottava shift for an event (if known).
Definition EntityMap.h:321
std::optional< int > tryGetBeamStartLevel(const std::string &eventId) const
Return the secondary beam depth that starts at an event ID, if any.
Definition EntityMap.h:293
int getBeamStartLevel(const std::string &eventId) const
Return the beam start level for an event ID or 0 if none.
Definition EntityMap.h:303
const std::vector< std::string > & getLyricLineOrder() const
Retrieve the lyric line order.
Definition EntityMap.h:340
EntityMap(std::weak_ptr< json > documentRoot, const std::optional< ErrorHandler > &errorHandler=std::nullopt)
Constructs the index for a given document.
Definition EntityMap.h:176
size_t getIndexOf(const std::string &id, const std::optional< Base > &errorLocation=std::nullopt) const
Returns the array index of an object identified by ID.
Definition EntityMap.h:247
void clear()
Clears all mapped items.
Definition EntityMap.h:312
std::optional< T > tryGet(const std::string &id, const std::optional< Base > &errorLocation=std::nullopt) const
Attempts to look up an object by string ID.
Definition EntityMap.h:193
int getOttavaShift(const sequence::Event &event) const
Retrieve the ottava shift for an event. Returns 0 if not cached.
Definition EntityMap.h:331
T get(const std::string &id, const std::optional< Base > &errorLocation=std::nullopt) const
Looks up an object by string ID.
Definition EntityMap.h:223
base class for mapping error exceptions
Definition EntityMap.h:97
json::json_pointer json_pointer
JSON pointer class for MNX.
Definition BaseTypes.h:68