MNX Document Model
Loading...
Searching...
No Matches
EntityMap.h
1/*
2 * Copyright (C) 2025, Robert Patterson
3 *
4 * Permission is hereby granted, free of charge, to any person obtaining a copy
5 * of this software and associated documentation files (the "Software"), to deal
6 * in the Software without restriction, including without limitation the rights
7 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 * copies of the Software, and to permit persons to whom the Software is
9 * furnished to do so, subject to the following conditions:
10 *
11 * The above copyright notice and this permission notice shall be included in
12 * all copies or substantial portions of the Software.
13 *
14 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 * THE SOFTWARE.
21 */
22#pragma once
23
24#include <algorithm>
25#include <array>
26#include <memory>
27#include <optional>
28#include <sstream>
29#include <string>
30#include <tuple>
31#include <type_traits>
32#include <unordered_map>
33
34#include "../BaseTypes.h"
35#include "../Global.h"
36#include "../Layout.h"
37#include "../Part.h"
38#include "../Score.h"
39#include "../Sequence.h"
40
41namespace mnx::util {
42
43namespace detail {
44#ifndef DOXYGEN_SHOULD_IGNORE_THIS
45
46template <typename T>
47struct JsonSchemaTypeNames
48{
49 static constexpr std::array<std::string_view, 1> value{ T::JsonSchemaTypeName };
50};
51
52template <>
53struct JsonSchemaTypeNames<sequence::NoteBase>
54{
55 static constexpr std::array<std::string_view, 2> value{
56 sequence::Note::JsonSchemaTypeName,
57 sequence::KitNote::JsonSchemaTypeName
58 };
59};
60
61template <typename T>
62bool matchesTypeName(std::string_view typeName)
63{
64 for (const auto name : JsonSchemaTypeNames<T>::value) {
65 if (name == typeName) {
66 return true;
67 }
68 }
69 return false;
70}
71
72template <typename T>
73std::string typeNamesExpectationString()
74{
75 std::ostringstream oss;
76 const auto& names = JsonSchemaTypeNames<T>::value;
77 const auto count = names.size();
78 if (count > 1) {
79 oss << "expected one of ";
80 } else {
81 oss << "expected ";
82 }
83 for (size_t i = 0; i < count; ++i) {
84 if (i > 0) {
85 oss << ", ";
86 }
87 oss << "\"" << names[i] << "\"";
88 }
89 return oss.str();
90}
91
92#endif // DOXYGEN_SHOULD_IGNORE_THIS
93} // namespace detail
94
96class mapping_error : public std::runtime_error
97{
98 using runtime_error::runtime_error;
99};
100
109{
110public:
113 {
114 std::string measureId;
116 std::optional<unsigned> graceIndex;
117
119 MappedPosition(const std::string& measureId, const FractionValue& fraction,
120 std::optional<unsigned> graceIndex = std::nullopt)
122 {
123 }
124
127 : measureId(position.measure()), fraction(position.position().fraction()), graceIndex(position.position().graceIndex())
128 {
129 }
130 };
131
132private:
138 template <typename T>
139 void add(const std::string& id, const T& value)
140 {
141 auto result = m_objectMap.emplace(id, MappedLocation{ value.pointer(), T::JsonSchemaTypeName });
142 if (!result.second) {
143 mapping_error err("ID " + formatKeyString(id) + " already exists for type \"" + std::string(result.first->second.typeName)
144 + "\" at " + result.first->second.location.to_string());
145 if (m_errorHandler) {
146 m_errorHandler.value()(err.what(), value);
147 } else {
148 throw err;
149 }
150 }
151 }
152
156 void addEventToBeam(const std::string& eventId, const part::Beam& beam)
157 {
158 auto result = m_eventsInBeams.emplace(eventId, BeamMappingEntry{ MappedLocation{ beam.pointer(), part::Beam::JsonSchemaTypeName }, 0 });
159 if (!result.second) {
160 mapping_error err("ID " + formatKeyString(eventId) + " already exists in beam "
161 + result.first->second.location.location.to_string());
162 if (m_errorHandler) {
163 m_errorHandler.value()(err.what(), beam);
164 } else {
165 throw err;
166 }
167 }
168 }
169
171 void setEventBeamStartLevel(const std::string& eventId, int level)
172 {
173 auto it = m_eventsInBeams.find(eventId);
174 if (it == m_eventsInBeams.end()) {
175 throw std::logic_error("Attempted to assign a beam start level to unmapped event " + eventId);
176 }
177 if (it->second.startLevel == 0) {
178 it->second.startLevel = level;
179 } else {
180 it->second.startLevel = std::min(it->second.startLevel, level);
181 }
182 }
183
185 void setEventOttavaShift(const std::string& eventPointer, int shift)
186 {
187 m_eventOttavaShift[eventPointer] = shift;
188 }
189
191 void setEventPosition(const std::string& eventPointer, const MappedPosition& position)
192 {
193 m_eventPositions.insert_or_assign(eventPointer, position);
194 }
195
196 friend class mnx::Document;
197
198public:
199
205 explicit EntityMap(std::weak_ptr<json> documentRoot, const std::optional<ErrorHandler>& errorHandler = std::nullopt)
206 : m_root(documentRoot), m_errorHandler(errorHandler) {}
207
221 template <typename T>
222 std::optional<T> tryGet(
223 const std::string& id,
224 const std::optional<Base>& errorLocation = std::nullopt) const
225 {
226 auto it = m_objectMap.find(id);
227 if (it == m_objectMap.end()) {
228 return std::nullopt;
229 }
230 MNX_ASSERT_IF(!detail::matchesTypeName<T>(it->second.typeName)) {
231 mapping_error err(
232 "ID " + formatKeyString(id) + " has type \"" + std::string(it->second.typeName) +
233 "\", but " + detail::typeNamesExpectationString<T>() + "."
234 );
235 if (m_errorHandler) {
236 m_errorHandler.value()(err.what(), errorLocation.value_or(Document(root())));
237 }
238 throw err;
239 }
240 return T(root(), it->second.location);
241 }
242
251 template <typename T>
252 T get(const std::string& id, const std::optional<Base>& errorLocation = std::nullopt) const
253 {
254 if (auto v = tryGet<T>(id, errorLocation)) {
255 return *std::move(v);
256 }
257 mapping_error err("ID " + formatKeyString(id) + " not found in ID mapping");
258 if (m_errorHandler) {
259 m_errorHandler.value()(err.what(), errorLocation.value_or(Document(root())));
260 }
261 throw err;
262 }
263
275 template <typename T>
276 size_t getIndexOf(const std::string& id, const std::optional<Base>& errorLocation = std::nullopt) const
277 {
278 static_assert(std::is_base_of_v<ArrayElementObject, T>,
279 "getIndexOf<T> requires T to derive from ArrayElementObject");
280
281 auto v = get<T>(id, errorLocation);
282 return v.calcArrayIndex();
283 }
284
285
287 template <typename T>
288 bool exists(const std::string& id) const
289 {
290 const auto it = m_objectMap.find(id);
291 if (it == m_objectMap.end()) {
292 return false;
293 }
294 return detail::matchesTypeName<T>(it->second.typeName);
295 }
296
300 std::optional<part::Beam> tryGetBeam(const sequence::Event& event) const
301 {
302 if (const auto& eventId = event.id()) {
303 const auto it = m_eventsInBeams.find(eventId.value());
304 if (it != m_eventsInBeams.end()) {
305 MNX_ASSERT_IF(it->second.location.typeName != part::Beam::JsonSchemaTypeName) {
306 mapping_error err(
307 "The beam mapping for eventId " + formatKeyString(eventId.value())
308 + " was mapped to an object of type \"" + std::string(it->second.location.typeName) + "\"."
309 );
310 if (m_errorHandler) {
311 m_errorHandler.value()(err.what(), event);
312 }
313 throw err;
314 }
315 return part::Beam(root(), it->second.location.location);
316 }
317 }
318 return std::nullopt;
319 }
320
322 [[nodiscard]] std::optional<int> tryGetBeamStartLevel(const std::string& eventId) const
323 {
324 const auto it = m_eventsInBeams.find(eventId);
325 if (it == m_eventsInBeams.end()) {
326 return std::nullopt;
327 }
328 return it->second.startLevel;
329 }
330
332 [[nodiscard]] int getBeamStartLevel(const std::string& eventId) const
333 {
334 if (const auto level = tryGetBeamStartLevel(eventId)) {
335 return *level;
336 }
337 return 0;
338 }
339
341 void clear()
342 {
343 m_objectMap.clear();
344 m_eventsInBeams.clear();
345 m_eventPositions.clear();
346 m_eventOttavaShift.clear();
347 m_lyricLineOrder.clear();
348 }
349
351 [[nodiscard]] std::optional<int> tryGetOttavaShift(const sequence::Event& event) const
352 {
353 const auto it = m_eventOttavaShift.find(event.pointer().to_string());
354 if (it == m_eventOttavaShift.end()) {
355 return std::nullopt;
356 }
357 return -it->second;
358 }
359
361 [[nodiscard]] int getOttavaShift(const sequence::Event& event) const
362 {
363 if (auto shift = tryGetOttavaShift(event)) {
364 return shift.value();
365 }
366 return 0;
367 }
368
370 [[nodiscard]] std::optional<MappedPosition> tryGetEventPosition(const sequence::Event& event) const
371 {
372 const auto it = m_eventPositions.find(event.pointer().to_string());
373 if (it == m_eventPositions.end()) {
374 return std::nullopt;
375 }
376 return it->second;
377 }
378
380 [[nodiscard]] MappedPosition getEventPosition(const sequence::Event& event) const
381 {
382 if (auto position = tryGetEventPosition(event)) {
383 return *std::move(position);
384 }
385 throw std::logic_error("No mapped position cached for event at " + event.pointer().to_string());
386 }
387
400 [[nodiscard]] int comparePositions(const MappedPosition& lhs,
401 const MappedPosition& rhs,
402 bool rhsIncludesTrailingGrace = false) const;
403
405 [[nodiscard]] const std::vector<std::string>& getLyricLineOrder() const
406 { return m_lyricLineOrder; }
407
408private:
409 std::weak_ptr<json> m_root;
410 std::optional<ErrorHandler> m_errorHandler;
411
412 std::shared_ptr<json> root() const
413 {
414 std::shared_ptr<json> result = m_root.lock();
415 if (!result) {
416 throw std::runtime_error("ID mapping is invalid because the document was destroyed.");
417 }
418 return result;
419 }
420
421 using MappedLocation = struct
422 {
423 json_pointer location;
424 std::string_view typeName;
425 };
426 std::unordered_map<std::string, MappedLocation> m_objectMap;
427 struct BeamMappingEntry
428 {
429 MappedLocation location;
430 int startLevel{0};
431 };
432 std::unordered_map<std::string, BeamMappingEntry> m_eventsInBeams;
433 std::unordered_map<std::string, MappedPosition> m_eventPositions;
434 std::unordered_map<std::string, int> m_eventOttavaShift;
435 std::vector<std::string> m_lyricLineOrder;
436
437 static std::string formatKeyString(const std::string& key) {
438 return "\"" + key + "\"";
439 }
440
441};
442
443} // namespace mnx::util
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
Represents a system on a page in a score.
Definition CommonClasses.h:550
Contains information about each level of beam.
Definition Part.h:97
static constexpr std::string_view JsonSchemaTypeName
required for mapping
Definition Part.h:118
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:288
std::optional< part::Beam > tryGetBeam(const sequence::Event &event) const
Get the beam for an event, if it is mapped.
Definition EntityMap.h:300
std::optional< int > tryGetOttavaShift(const sequence::Event &event) const
Retrieve the ottava shift for an event (if known).
Definition EntityMap.h:351
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:322
int getBeamStartLevel(const std::string &eventId) const
Return the beam start level for an event ID or 0 if none.
Definition EntityMap.h:332
MappedPosition getEventPosition(const sequence::Event &event) const
Retrieve the cached mapped position for an event.
Definition EntityMap.h:380
const std::vector< std::string > & getLyricLineOrder() const
Retrieve the lyric line order.
Definition EntityMap.h:405
EntityMap(std::weak_ptr< json > documentRoot, const std::optional< ErrorHandler > &errorHandler=std::nullopt)
Constructs the index for a given document.
Definition EntityMap.h:205
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:276
void clear()
Clears all mapped items.
Definition EntityMap.h:341
int comparePositions(const MappedPosition &lhs, const MappedPosition &rhs, bool rhsIncludesTrailingGrace=false) const
Compare two mapped positions using global measure order and grace-note targeting.
Definition Implementations.cpp:828
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:222
int getOttavaShift(const sequence::Event &event) const
Retrieve the ottava shift for an event. Returns 0 if not cached.
Definition EntityMap.h:361
T get(const std::string &id, const std::optional< Base > &errorLocation=std::nullopt) const
Looks up an object by string ID.
Definition EntityMap.h:252
std::optional< MappedPosition > tryGetEventPosition(const sequence::Event &event) const
Retrieve the cached mapped position for an event (if known).
Definition EntityMap.h:370
base class for mapping error exceptions
Definition EntityMap.h:97
json::json_pointer json_pointer
JSON pointer class for MNX.
Definition BaseTypes.h:68
Represents a detached arithmetic fraction with normalization.
Definition CommonClasses.h:74
Canonical position value used by EntityMap for measure-relative comparisons and caches.
Definition EntityMap.h:113
MappedPosition(const std::string &measureId, const FractionValue &fraction, std::optional< unsigned > graceIndex=std::nullopt)
Constructor function.
Definition EntityMap.h:119
std::optional< unsigned > graceIndex
the grace index of the event, if any
Definition EntityMap.h:116
MappedPosition(const MeasureRhythmicPosition &position)
Constructor function from MeasureRhythmicPosition.
Definition EntityMap.h:126
FractionValue fraction
the rhythmic position within the measure
Definition EntityMap.h:115
std::string measureId
the measure id of the measure
Definition EntityMap.h:114