|
- /*
- ==============================================================================
-
- This file is part of the JUCE library.
- Copyright (c) 2022 - Raw Material Software Limited
-
- JUCE is an open source library subject to commercial or open-source
- licensing.
-
- The code included in this file is provided under the terms of the ISC license
- http://www.isc.org/downloads/software-support-policy/isc-license. Permission
- To use, copy, modify, and/or distribute this software for any purpose with or
- without fee is hereby granted provided that the above copyright notice and
- this permission notice appear in all copies.
-
- JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
- EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
- DISCLAIMED.
-
- ==============================================================================
- */
-
- namespace juce
- {
-
- /**
- Options that control conversion from arbitrary types to juce::var.
-
- @see ToVar
-
- @tags{Core}
- */
- class ToVarOptions
- {
- public:
- /** By default, conversion will serialise the type using the marshallingVersion defined for
- that type. Setting an explicit version allows the type to be serialised as an earlier
- version.
- */
- [[nodiscard]] ToVarOptions withExplicitVersion (std::optional<int> x) const { return withMember (*this, &ToVarOptions::explicitVersion, x); }
-
- /** By default, conversion will include version information for any type with a non-null
- marshallingVersion. Setting versionIncluded to false will cause the version info to be
- omitted, which is useful in situations where the version information is not needed
- (e.g. when presenting transient information to the user, rather than writing data to
- disk that must be deserialised in the future).
- */
- [[nodiscard]] ToVarOptions withVersionIncluded (bool x) const { return withMember (*this, &ToVarOptions::versionIncluded, x); }
-
- /** @see withExplicitVersion() */
- [[nodiscard]] auto getExplicitVersion() const { return explicitVersion; }
-
- /** @see withVersionIncluded(). */
- [[nodiscard]] auto getVersionIncluded() const { return versionIncluded; }
-
- private:
- std::optional<std::optional<int>> explicitVersion;
- bool versionIncluded = true;
- };
-
- /**
- Allows converting an object of arbitrary type to var.
-
- To use this, you must first ensure that the type passed to convert is set up for serialisation.
- For details of what this entails, see the docs for SerialisationTraits.
-
- In short, the constant 'marshallingVersion', and either the single function 'serialise()', or
- the function pair 'load()' and 'save()' must be defined for the type. These may be defined
- as public members of the type T itself, or as public members of juce::SerialisationTraits<T>,
- which is a specialisation of the SerialisationTraits template struct for the type T.
-
- @see FromVar
-
- @tags{Core}
- */
- class ToVar
- {
- public:
- using Options = ToVarOptions;
-
- /** Attempts to convert the argument to a var using the serialisation utilities specified for
- that type.
-
- This will return a non-null optional if conversion succeeds, or nullopt if conversion fails.
- */
- template <typename T>
- static std::optional<var> convert (const T& t, const Options& options = {})
- {
- return Visitor::convert (t, options);
- }
-
- private:
- class Visitor
- {
- public:
- template <typename T>
- static std::optional<var> convert (const T& t, const Options& options)
- {
- constexpr auto fallbackVersion = detail::ForwardingSerialisationTraits<T>::marshallingVersion;
- const auto versionToUse = options.getExplicitVersion()
- .value_or (fallbackVersion);
-
- if (versionToUse > fallbackVersion)
- {
- // The requested explicit version is higher than the declared version of the type.
- return std::nullopt;
- }
-
- Visitor visitor { versionToUse, options.getVersionIncluded() };
- detail::doSave (visitor, t);
- return visitor.value;
- }
-
- std::optional<int> getVersion() const { return version; }
-
- template <typename... Ts>
- void operator() (Ts&&... ts)
- {
- (visit (std::forward<Ts> (ts)), ...);
- }
-
- private:
- Visitor (const std::optional<int>& explicitVersion, bool includeVersion)
- : version (explicitVersion),
- value ([&]() -> var
- {
- if (! (version.has_value() && includeVersion))
- return var();
-
- auto obj = std::make_unique<DynamicObject>();
- obj->setProperty ("__version__", *version);
- return obj.release();
- }()),
- versionIncluded (includeVersion) {}
-
- template <typename T>
- void visit (const T& t)
- {
- if constexpr (std::is_integral_v<T>)
- {
- push ((int64) t);
- }
- else if constexpr (std::is_floating_point_v<T>)
- {
- push ((double) t);
- }
- else if (auto converted = convert (t))
- {
- push (*converted);
- }
- else
- {
- value.reset();
- }
- }
-
- template <typename T>
- void visit (const Named<T>& named)
- {
- if (! value.has_value())
- return;
-
- if (value == var())
- value = new DynamicObject;
-
- auto* obj = value->getDynamicObject();
-
- if (obj == nullptr)
- {
- // Serialisation failure! This may be caused by archiving a primitive or
- // SerialisationSize, and then attempting to archive a named pair to the same
- // archive instance.
- // When using named pairs, *all* items serialised with a particular archiver must be
- // named pairs.
- jassertfalse;
-
- value.reset();
- return;
- }
-
- if (! trySetProperty (*obj, named))
- value.reset();
- }
-
- template <typename T>
- void visit (const SerialisationSize<T>&)
- {
- push (Array<var>{});
- }
-
- void visit (const bool& t)
- {
- push (t);
- }
-
- void visit (const String& t)
- {
- push (t);
- }
-
- void visit (const var& t)
- {
- push (t);
- }
-
- template <typename T>
- std::optional<var> convert (const T& t)
- {
- return convert (t, Options{}.withVersionIncluded (versionIncluded));
- }
-
- void push (var v)
- {
- if (! value.has_value())
- return;
-
- if (*value == var())
- *value = v;
- else if (auto* array = value->getArray())
- array->add (v);
- else
- value.reset();
- }
-
- template <typename T>
- bool trySetProperty (DynamicObject& obj, const Named<T>& n)
- {
- if (const auto converted = convert (n.value))
- {
- obj.setProperty (Identifier (std::string (n.name)), *converted);
- return true;
- }
-
- return false;
- }
-
- std::optional<int> version;
- std::optional<var> value;
- bool versionIncluded = true;
- };
- };
-
- //==============================================================================
- /**
- Allows converting a var to an object of arbitrary type.
-
- To use this, you must first ensure that the type passed to convert is set up for serialisation.
- For details of what this entails, see the docs for SerialisationTraits.
-
- In short, the constant 'marshallingVersion', and either the single function 'serialise()', or
- the function pair 'load()' and 'save()' must be defined for the type. These may be defined
- as public members of the type T itself, or as public members of juce::SerialisationTraits<T>,
- which is a specialisation of the SerialisationTraits template struct for the type T.
-
- @see ToVar
-
- @tags{Core}
- */
- class FromVar
- {
- public:
- /** Attempts to convert a var to an instance of type T.
-
- This will return a non-null optional if conversion succeeds, or nullopt if conversion fails.
- */
- template <typename T>
- static std::optional<T> convert (const var& v)
- {
- return Visitor::convert<T> (v);
- }
-
- private:
- class Visitor
- {
- public:
- template <typename T>
- static std::optional<T> convert (const var& v)
- {
- const auto version = [&]() -> std::optional<int>
- {
- if (auto* obj = v.getDynamicObject())
- if (obj->hasProperty ("__version__"))
- return (int) obj->getProperty ("__version__");
-
- return std::nullopt;
- }();
-
- Visitor visitor { version, v };
- T t{};
- detail::doLoad (visitor, t);
- return ! visitor.failed ? std::optional<T> (std::move (t))
- : std::nullopt;
- }
-
- std::optional<int> getVersion() const { return version; }
-
- template <typename... Ts>
- void operator() (Ts&&... ts)
- {
- (visit (std::forward<Ts> (ts)), ...);
- }
-
- private:
- Visitor (std::optional<int> vn, const var& i)
- : version (vn), input (i) {}
-
- template <typename T>
- void visit (T& t)
- {
- if constexpr (std::is_integral_v<T>)
- {
- readPrimitive (std::in_place_type<int64>, t);
- }
- else if constexpr (std::is_floating_point_v<T>)
- {
- readPrimitive (std::in_place_type<double>, t);
- }
- else
- {
- auto node = getNodeToRead();
-
- if (! node.has_value())
- return;
-
- auto converted = convert<T> (*node);
-
- if (converted.has_value())
- t = *converted;
- else
- failed = true;
- }
- }
-
- template <typename T>
- void visit (const Named<T>& named)
- {
- auto node = getNodeToRead();
-
- if (! node.has_value())
- return;
-
- auto* obj = node->getDynamicObject();
-
- failed = obj == nullptr || ! tryGetProperty (*obj, named);
- }
-
- template <typename T>
- void visit (const SerialisationSize<T>& t)
- {
- if (failed)
- return;
-
- if (auto* array = input.getArray())
- {
- t.size = static_cast<T> (array->size());
- currentArrayIndex = 0;
- }
- else
- {
- failed = true;
- }
- }
-
- void visit (bool& t)
- {
- readPrimitive (std::in_place_type<bool>, t);
- }
-
- void visit (String& t)
- {
- readPrimitive (std::in_place_type<String>, t);
- }
-
- void visit (var& t)
- {
- t = input;
- }
-
- static std::optional<double> pullTyped (std::in_place_type_t<double>, const var& source)
- {
- return source.isDouble() ? std::optional<double> ((double) source) : std::nullopt;
- }
-
- static std::optional<int64> pullTyped (std::in_place_type_t<int64>, const var& source)
- {
- return source.isInt() || source.isInt64() ? std::optional<int64> ((int64) source) : std::nullopt;
- }
-
- static std::optional<bool> pullTyped (std::in_place_type_t<bool>, const var& source)
- {
- return std::optional<bool> ((bool) source);
- }
-
- static std::optional<String> pullTyped (std::in_place_type_t<String>, const var& source)
- {
- return source.isString() ? std::optional<String> (source.toString()) : std::nullopt;
- }
-
- std::optional<var> getNodeToRead()
- {
- if (failed)
- return std::nullopt;
-
- if (currentArrayIndex == std::numeric_limits<size_t>::max())
- return input;
-
- const auto* array = input.getArray();
-
- if (array == nullptr)
- return input;
-
- if ((int) currentArrayIndex < array->size())
- return array->getReference ((int) currentArrayIndex++);
-
- failed = true;
- return std::nullopt;
- }
-
- template <typename TypeToRead, typename T>
- void readPrimitive (std::in_place_type_t<TypeToRead> tag, T& t)
- {
- auto node = getNodeToRead();
-
- if (! node.has_value())
- return;
-
- auto typed = pullTyped (tag, *node);
-
- if (typed.has_value())
- t = static_cast<T> (*typed);
- else
- failed = true;
- }
-
- template <typename T>
- static bool tryGetProperty (const DynamicObject& obj, const Named<T>& n)
- {
- const Identifier identifier (String (n.name.data(), n.name.size()));
-
- if (! obj.hasProperty (identifier))
- return false;
-
- const auto converted = convert<T> (obj.getProperty (identifier));
-
- if (! converted.has_value())
- return false;
-
- n.value = *converted;
- return true;
- }
-
- std::optional<int> version;
- var input;
- size_t currentArrayIndex = std::numeric_limits<size_t>::max();
- bool failed = false;
- };
- };
-
- //==============================================================================
- /**
- This template-overloaded class can be used to convert between var and custom types.
-
- If not specialised, the variant converter will attempt to use serialisation functions
- if they are detected for the given type.
- For details of what this entails, see the docs for SerialisationTraits.
-
- In short, the constant 'marshallingVersion', and either the single function 'serialise()', or
- the function pair 'load()' and 'save()' must be defined for the type. These may be defined
- as public members of the type T itself, or as public members of juce::SerialisationTraits<T>,
- which is a specialisation of the SerialisationTraits template struct for the type T.
-
- @see ToVar, FromVar
-
- @tags{Core}
- */
- template <typename Type>
- struct VariantConverter
- {
- static Type fromVar (const var& v)
- {
- return static_cast<Type> (v);
- }
-
- static var toVar (const Type& t)
- {
- return t;
- }
- };
-
- #ifndef DOXYGEN
-
- template <>
- struct VariantConverter<String>
- {
- static String fromVar (const var& v) { return v.toString(); }
- static var toVar (const String& s) { return s; }
- };
-
- #endif
-
- /**
- A helper type that can be used to implement specialisations of VariantConverter that use
- FromVar::convert and ToVar::convert internally.
-
- If you've already implemented SerialisationTraits for a specific type, and don't want to write
- a custom VariantConverter that duplicates that implementation, you can instead write:
- @code
- template <>
- struct juce::VariantConverter<MyType> : public juce::StrictVariantConverter<MyType> {};
- @endcode
-
- @tags{Core}
- */
- template <typename Type>
- struct StrictVariantConverter
- {
- static_assert (detail::serialisationKind<Type> != detail::SerialisationKind::none);
-
- static Type fromVar (const var& v)
- {
- auto converted = FromVar::convert<Type> (v);
- jassert (converted.has_value());
- return std::move (converted).value_or (Type{});
- }
-
- static var toVar (const Type& t)
- {
- auto converted = ToVar::convert<> (t);
- jassert (converted.has_value());
- return std::move (converted).value_or (var{});
- }
- };
-
- } // namespace juce
|