// // Copyright 2016 Pixar // // Licensed under the Apache License, Version 2.0 (the "Apache License") // with the following modification; you may not use this file except in // compliance with the Apache License and the following modification to it: // Section 6. Trademarks. is deleted and replaced with: // // 6. Trademarks. This License does not grant permission to use the trade // names, trademarks, service marks, or product names of the Licensor // and its affiliates, except as required to comply with Section 4(c) of // the License and to reproduce the content of the NOTICE file. // // You may obtain a copy of the Apache License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the Apache License with the above modification is // distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY // KIND, either express or implied. See the Apache License for the specific // language governing permissions and limitations under the Apache License. // #ifndef PXR_BASE_VT_DICTIONARY_H #define PXR_BASE_VT_DICTIONARY_H /// \file vt/dictionary.h #include "pxr/pxr.h" #include "pxr/base/vt/api.h" #include "pxr/base/vt/value.h" #include "pxr/base/tf/diagnostic.h" #include "pxr/base/tf/hash.h" #include "pxr/base/tf/mallocTag.h" #include #include #include #include #include #include PXR_NAMESPACE_OPEN_SCOPE /// \defgroup group_vtdict_functions VtDictionary Functions /// Functions for manipulating VtDictionary objects. /// \class VtDictionary /// /// A map with string keys and VtValue values. /// /// VtDictionary converts to and from a python dictionary as long /// as each element contains either /// - another VtDictionary (converts to a nested dictionary) /// - std::vector (converts to a nested list) /// - VtValue with one of the supported Vt Types. /// /// For a list of functions that can manipulate VtDictionary objects, see the /// \link group_vtdict_functions VtDictionary Functions \endlink group page . /// class VtDictionary { typedef std::map> _Map; std::unique_ptr<_Map> _dictMap; public: // The iterator class, used to make both const and non-const iterators. // Currently only forward traversal is supported. In order to support lazy // allocation, VtDictionary's Map pointer (_dictMap) must be nullable, // but that would break the VtDictionary iterators. So instead, VtDictionary // uses this Iterator class, which considers an iterator to an empty // VtDictionary to be the same as an iterator at the end of a VtDictionary // (i.e. if Iterator's _dictMap pointer is null, that either means that the // VtDictionary is empty, or the Iterator is at the end of a VtDictionary // that contains values). template class Iterator : public boost::iterator_adaptor, UnderlyingIterator> { public: // Default constructor creates an Iterator equivalent to end() (i.e. // UnderlyingMapPtr is null) Iterator() : Iterator::iterator_adaptor_(UnderlyingIterator()) , _underlyingMap(0) {} // Copy constructor (also allows for converting non-const to const). template Iterator(Iterator const &other) : Iterator::iterator_adaptor_(other.base()) , _underlyingMap(other._underlyingMap) {} private: // Private constructor allowing the find, begin and insert methods // to create and return the proper Iterator. Iterator(UnderlyingMapPtr m, UnderlyingIterator i) : Iterator::iterator_adaptor_(i) , _underlyingMap(m) { if (m && i == m->end()) _underlyingMap = 0; } friend class boost::iterator_core_access; friend class VtDictionary; UnderlyingIterator GetUnderlyingIterator(UnderlyingMapPtr map) const { TF_AXIOM(!_underlyingMap || _underlyingMap == map); return (!_underlyingMap) ? map->end() : this->base(); } // Fundamental functionality to implement the iterator. // boost::iterator_adaptor will invoke these as necessary to implement // the full iterator public interface. // Increments the underlying iterator, and sets the underlying map to // null when the iterator reaches the end of the map. void increment() { if (!_underlyingMap) { TF_FATAL_ERROR("Attempted invalid increment operation on a " "VtDictionary iterator"); return; } if (++this->base_reference() == _underlyingMap->end()) { _underlyingMap = 0; } } // Equality comparison. Iterators are considered equal if: // 1) They both point to empty VtDictionaries // 2) They both point to the end() of a VtDictionary // - or- // 3) They both point to the same VtDictionary and their // boost::iterator_adaptors' base() iterators are the same // In cases 1 and 2 above, _underlyingMap will be null template bool equal(Iterator const& i) const { if (_underlyingMap == i._underlyingMap) if (!_underlyingMap || this->base() == i.base()) return true; return false; } UnderlyingMapPtr _underlyingMap; }; TF_MALLOC_TAG_NEW("Vt", "VtDictionary"); typedef _Map::key_type key_type; typedef _Map::mapped_type mapped_type; typedef _Map::value_type value_type; typedef _Map::allocator_type allocator_type; typedef _Map::size_type size_type; typedef Iterator<_Map*, _Map::iterator> iterator; typedef Iterator<_Map const*, _Map::const_iterator> const_iterator; /// Creates an empty \p VtDictionary. VtDictionary() {} /// Creates an empty \p VtDictionary with at least \p size buckets. explicit VtDictionary(int size) {} /// Creates a \p VtDictionary with a copy of a range. template VtDictionary(_InputIterator f, _InputIterator l){ TfAutoMallocTag2 tag("Vt", "VtDictionary::VtDictionary (range)"); insert(f, l); } /// Creates a copy of the supplied \p VtDictionary VT_API VtDictionary(VtDictionary const& other); /// Creates a new VtDictionary by moving the supplied \p VtDictionary. VT_API VtDictionary(VtDictionary && other) = default; /// Creates a new VtDictionary from a braced initializer list. VT_API VtDictionary(std::initializer_list init); /// Copy assignment operator VT_API VtDictionary& operator=(VtDictionary const& other); /// Move assignment operator VT_API VtDictionary& operator=(VtDictionary && other) = default; /// Returns a reference to the \p VtValue that is associated with a /// particular key. VT_API VtValue& operator[](const std::string& key); /// Counts the number of elements whose key is \p key. VT_API size_type count(const std::string& key) const; /// Counts the number of elements whose key is \p key. VT_API size_type count(const char* key) const; /// Erases the element whose key is \p key. VT_API size_type erase(const std::string& key); /// Erases the element pointed to by \p it. VT_API void erase(iterator it); /// Erases all elements in a range. VT_API void erase(iterator f, iterator l); /// Erases all of the elements. VT_API void clear(); /// Finds an element whose key is \p key. VT_API iterator find(const std::string& key); /// Finds an element whose key is \p key. VT_API iterator find(const char* key); /// Finds an element whose key is \p key. VT_API const_iterator find(const std::string& key) const; /// Finds an element whose key is \p key. VT_API const_iterator find(const char* key) const; /// Returns an \p iterator pointing to the beginning of the \p VtDictionary. VT_API iterator begin(); /// Returns an \p iterator pointing to the beginning of the \p VtDictionary. VT_API const_iterator begin() const; /// Returns an \p iterator pointing to the end of the \p VtDictionary. VT_API iterator end(); /// Returns an \p iterator pointing to the end of the \p VtDictionary. VT_API const_iterator end() const; /// Returns the size of the VtDictionary. VT_API size_type size() const; /// \c true if the \p VtDictionary's size is 0. VT_API bool empty() const; /// Swaps the contents of two \p VtDictionaries. VT_API void swap(VtDictionary& dict); // Global overload for swap for unqualified calls in generic code. friend void swap(VtDictionary &lhs, VtDictionary &rhs) { lhs.swap(rhs); } friend size_t hash_value(VtDictionary const &dict) { // Hash empty dict as zero. if (dict.empty()) return 0; // Otherwise hash the map. return boost::hash_value(*dict._dictMap); } /// Inserts a range into the \p VtDictionary. template void insert(_InputIterator f, _InputIterator l) { TfAutoMallocTag2 tag("Vt", "VtDictionary::insert (range)"); if (f != l) { _CreateDictIfNeeded(); _dictMap->insert(f, l); } } /// Inserts \p obj into the \p VtDictionary. VT_API std::pair insert(const value_type& obj); /// Return a pointer to the value at \p keyPath if one exists. \p keyPath /// is a delimited string of sub-dictionary names. Key path elements are /// produced by calling TfStringTokenize() with \p keyPath and /// \p delimiters. \p keyPath may identify a leaf element or an entire /// sub-dictionary. Return null if no such element at \p keyPath exists. VT_API VtValue const * GetValueAtPath(std::string const &keyPath, char const *delimiters = ":") const; /// Return a pointer to the value at \p keyPath if one exists. \p keyPath /// may identify a leaf element or an entire sub-dictionary. Return null if /// no such element at \p keyPath exists. VT_API VtValue const * GetValueAtPath(std::vector const &keyPath) const; /// Set the value at \p keyPath to \p value. \p keyPath is a delimited /// string of sub-dictionary names. Key path elements are produced by /// calling TfStringTokenize() with \p keyPath and \p delimiters. Create /// sub-dictionaries as necessary according to the path elements in /// \p keyPath. If \p keyPath identifies a full sub-dictionary, replace the /// entire sub-dictionary with \p value. VT_API void SetValueAtPath(std::string const &keyPath, VtValue const &value, char const *delimiters = ":"); /// Set the value at \p keyPath to \p value. Create sub-dictionaries as /// necessary according to the path elements in \p keyPath. If \p keyPath /// identifies a full sub-dictionary, replace the entire sub-dictionary with /// \p value. VT_API void SetValueAtPath(std::vector const &keyPath, VtValue const &value); /// Erase the value at \a keyPath. \p keyPath is a delimited string of /// sub-dictionary names. Key path elements are produced by calling /// TfStringTokenize() with \p keyPath and \p delimiters. If no such /// element exists at \p keyPath, do nothing. If \p keyPath identifies a /// sub-dictionary, erase the entire sub-dictionary. VT_API void EraseValueAtPath(std::string const &keyPath, char const *delimiters = ":"); /// Erase the value at \a keyPath. If no such element exists at \p keyPath, /// do nothing. If \p keyPath identifies a sub-dictionary, erase the entire /// sub-dictionary. VT_API void EraseValueAtPath(std::vector const &keyPath); private: void _SetValueAtPathImpl(std::vector::const_iterator curKeyElem, std::vector::const_iterator keyElemEnd, VtValue const &value); void _EraseValueAtPathImpl( std::vector::const_iterator curKeyElem, std::vector::const_iterator keyElemEnd); void _CreateDictIfNeeded(); }; /// Equality comparison. VT_API bool operator==(VtDictionary const &, VtDictionary const &); VT_API bool operator!=(VtDictionary const &, VtDictionary const &); /// Write the contents of a VtDictionary to a stream, formatted like "{ 'key1': /// value1, 'key2': value2 }". VT_API std::ostream &operator<<(std::ostream &, VtDictionary const &); // // Return a const reference to an empty VtDictionary. // VT_API VtDictionary const &VtGetEmptyDictionary(); /// Returns true if \p dictionary contains \p key and the corresponding value /// is of type \p T. /// \ingroup group_vtdict_functions /// template bool VtDictionaryIsHolding( const VtDictionary &dictionary, const std::string &key ) { VtDictionary::const_iterator i = dictionary.find(key); if ( i == dictionary.end() ) { return false; } return i->second.IsHolding(); } /// \overload template bool VtDictionaryIsHolding( const VtDictionary &dictionary, const char *key ) { VtDictionary::const_iterator i = dictionary.find(key); if ( i == dictionary.end() ) { return false; } return i->second.IsHolding(); } /// Return a value held in a VtDictionary by reference. /// /// If \p key is in \p dictionary and the corresponding value is of type /// \p T, returns a reference to the value. /// /// \remark If \p key is not in \p dictionary, or the value for \p key is of /// the wrong type, a fatal error occurs, so clients should always call /// VtDictionaryIsHolding first. /// /// \ingroup group_vtdict_functions template const T & VtDictionaryGet( const VtDictionary &dictionary, const std::string &key ) { VtDictionary::const_iterator i = dictionary.find(key); if (ARCH_UNLIKELY(i == dictionary.end())) { TF_FATAL_ERROR("Attempted to get value for key '" + key + "', which is not in the dictionary."); } return i->second.Get(); } /// \overload template const T & VtDictionaryGet( const VtDictionary &dictionary, const char *key ) { VtDictionary::const_iterator i = dictionary.find(key); if (ARCH_UNLIKELY(i == dictionary.end())) { TF_FATAL_ERROR("Attempted to get value for key '%s', " "which is not in the dictionary.", key); } return i->second.Get(); } // This is an internal holder class that is used in the version of // VtDictionaryGet that takes a default. template struct Vt_DefaultHolder { explicit Vt_DefaultHolder(T const &t) : val(t) {} T const &val; }; // This internal class has a very unusual assignment operator that returns an // instance of Vt_DefaultHolder, holding any type T. This is used to get the // "VtDefault = X" syntax for VtDictionaryGet. struct Vt_DefaultGenerator { template Vt_DefaultHolder operator=(T const &t) { return Vt_DefaultHolder(t); } }; // This is a global stateless variable used to get the VtDefault = X syntax in // VtDictionaryGet. extern VT_API Vt_DefaultGenerator VtDefault; /// Return a value held in a VtDictionary, or a default value either if the /// supplied key is missing or if the types do not match. /// /// For example, this code will get a bool value under key "key" if "key" has a /// boolean value in the dictionary. If there is no such key, or the value /// under the key is not a bool, the specified default (false) is returned. /// /// \code /// bool val = VtDictionaryGet(dict, "key", VtDefault = false); /// \endcode /// /// \ingroup group_vtdict_functions template T VtDictionaryGet( const VtDictionary &dictionary, const std::string &key, Vt_DefaultHolder const &def ) { VtDictionary::const_iterator i = dictionary.find(key); if (i == dictionary.end() || !i->second.IsHolding()) return def.val; return i->second.UncheckedGet(); } /// \overload template T VtDictionaryGet( const VtDictionary &dictionary, const char *key, Vt_DefaultHolder const &def ) { VtDictionary::const_iterator i = dictionary.find(key); if (i == dictionary.end() || !i->second.IsHolding()) return def.val; return i->second.UncheckedGet(); } /// Creates a dictionary containing \p strong composed over \p weak. /// /// The new dictionary will contain all key-value pairs from \p strong /// together with the key-value pairs from \p weak whose keys are not in \p /// strong. /// /// If \p coerceToWeakerOpinionType is \c true then coerce a strong value to /// the weaker value's type, if there is a weaker value. This is mainly /// intended to promote to enum types. /// /// \ingroup group_vtdict_functions VT_API VtDictionary VtDictionaryOver(const VtDictionary &strong, const VtDictionary &weak, bool coerceToWeakerOpinionType = false); /// Updates \p strong to become \p strong composed over \p weak. /// /// The updated contents of \p strong will be all key-value pairs from \p /// strong together with the key-value pairs from \p weak whose keys are not in /// \p strong. /// /// If \p coerceToWeakerOpinionType is \c true then coerce a strong value to /// the weaker value's type, if there is a weaker value. This is mainly /// intended to promote to enum types. /// /// \ingroup group_vtdict_functions VT_API void VtDictionaryOver(VtDictionary *strong, const VtDictionary &weak, bool coerceToWeakerOpinionType = false); /// Updates \p weak to become \p strong composed over \p weak. /// /// The updated contents of \p weak will be all key-value pairs from \p strong /// together with the key-value pairs from \p weak whose keys are not in \p /// strong. /// /// If \p coerceToWeakerOpinionType is \c true then coerce a strong value to /// the weaker value's type, if there is a weaker value. This is mainly /// intended to promote to enum types. /// /// \ingroup group_vtdict_functions VT_API void VtDictionaryOver(const VtDictionary &strong, VtDictionary *weak, bool coerceToWeakerOpinionType = false); /// Returns a dictionary containing \p strong recursively composed over \p /// weak. /// /// The new dictionary will be all key-value pairs from \p strong together /// with the key-value pairs from \p weak whose keys are not in \p strong. /// /// If a value for a key is in turn a dictionary, and both \a strong and \a /// weak have values for that key, then the result may not contain strong's /// exact value for the subdict. Rather, the result will contain a subdict /// that is the result of a recursive call to this method. Hence, the /// subdict, too, will contain values from \a weak that are not found in \a /// strong. /// /// If \p coerceToWeakerOpinionType is \c true then coerce a strong value to /// the weaker value's type, if there is a weaker value. This is mainly /// intended to promote to enum types. /// /// \ingroup group_vtdict_functions VT_API VtDictionary VtDictionaryOverRecursive(const VtDictionary &strong, const VtDictionary &weak, bool coerceToWeakerOpinionType = false); /// Updates \p strong to become \p strong composed recursively over \p weak. /// /// The updated contents of \p strong will be all key-value pairs from \p /// strong together with the key-value pairs from \p weak whose keys are not /// in \p strong. /// /// If a value for a key is in turn a dictionary, and both \a strong and \a /// weak have values for that key, then \a strong's subdict may not be left /// untouched. Rather, the dictionary will be replaced by the result of a /// recursive call to this method in which \a strong's subdictionary will have /// entries added if they are contained in \a weak but not in \a strong /// /// If \p coerceToWeakerOpinionType is \c true then coerce a strong value to /// the weaker value's type, if there is a weaker value. This is mainly /// intended to promote to enum types. /// /// \ingroup group_vtdict_functions VT_API void VtDictionaryOverRecursive(VtDictionary *strong, const VtDictionary &weak, bool coerceToWeakerOpinionType = false); /// Updates \p weak to become \p strong composed recursively over \p weak. /// /// The updated contents of \p weak will be all key-value pairs from \p strong /// together with the key-value pairs from \p weak whose keys are not in \p /// strong. /// /// If a value is in turn a dictionary, the dictionary in \a weak may not be /// replaced wholesale by that of \a strong. Rather, the dictionary will be /// replaced by the result of a recursive call to this method in which \a /// weak's subdictionary is recursively overlayed by \a strong's /// subdictionary. /// /// The result is that no key/value pairs of \a will be lost in nested /// dictionaries. Rather, only non-dictionary values will be overwritten /// /// If \p coerceToWeakerOpinionType is \c true then coerce a strong value to /// the weaker value's type, if there is a weaker value. This is mainly /// intended to promote to enum types. /// /// \ingroup group_vtdict_functions VT_API void VtDictionaryOverRecursive(const VtDictionary &strong, VtDictionary *weak, bool coerceToWeakerOpinionType = false); struct VtDictionaryHash { inline size_t operator()(VtDictionary const &dict) const { return hash_value(dict); } }; PXR_NAMESPACE_CLOSE_SCOPE #endif /* PXR_BASE_VT_DICTIONARY_H */