diff options
Diffstat (limited to '')
-rw-r--r-- | include/iprt/cpp/Makefile.kup | 0 | ||||
-rw-r--r-- | include/iprt/cpp/autores.h | 216 | ||||
-rw-r--r-- | include/iprt/cpp/exception.h | 118 | ||||
-rw-r--r-- | include/iprt/cpp/hardavlrange.h | 1285 | ||||
-rw-r--r-- | include/iprt/cpp/hardavlslaballocator.h | 218 | ||||
-rw-r--r-- | include/iprt/cpp/list.h | 1143 | ||||
-rw-r--r-- | include/iprt/cpp/lock.h | 179 | ||||
-rw-r--r-- | include/iprt/cpp/meta.h | 125 | ||||
-rw-r--r-- | include/iprt/cpp/ministring.h | 1638 | ||||
-rw-r--r-- | include/iprt/cpp/mtlist.h | 185 | ||||
-rw-r--r-- | include/iprt/cpp/path.h | 249 | ||||
-rw-r--r-- | include/iprt/cpp/restanyobject.h | 139 | ||||
-rw-r--r-- | include/iprt/cpp/restarray.h | 463 | ||||
-rw-r--r-- | include/iprt/cpp/restbase.h | 1106 | ||||
-rw-r--r-- | include/iprt/cpp/restclient.h | 824 | ||||
-rw-r--r-- | include/iprt/cpp/restoutput.h | 280 | ||||
-rw-r--r-- | include/iprt/cpp/reststringmap.h | 499 | ||||
-rw-r--r-- | include/iprt/cpp/utils.h | 149 | ||||
-rw-r--r-- | include/iprt/cpp/xml.h | 1252 |
19 files changed, 10068 insertions, 0 deletions
diff --git a/include/iprt/cpp/Makefile.kup b/include/iprt/cpp/Makefile.kup new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/include/iprt/cpp/Makefile.kup diff --git a/include/iprt/cpp/autores.h b/include/iprt/cpp/autores.h new file mode 100644 index 00000000..ddfc8eea --- /dev/null +++ b/include/iprt/cpp/autores.h @@ -0,0 +1,216 @@ +/** @file + * IPRT - C++ Resource Management. + */ + +/* + * Copyright (C) 2008-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#ifndef IPRT_INCLUDED_cpp_autores_h +#define IPRT_INCLUDED_cpp_autores_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include <iprt/types.h> +#include <iprt/assert.h> +#include <iprt/cpp/utils.h> + + + +/** @defgroup grp_rt_cpp_autores C++ Resource Management + * @ingroup grp_rt_cpp + * @{ + */ + +/** + * A callable class template which returns the correct value against which an + * IPRT type must be compared to see if it is invalid. + * + * @warning This template *must* be specialised for the types it is to work with. + */ +template <class T> +inline T RTAutoResNil(void) +{ + AssertFatalMsgFailed(("Unspecialized template!\n")); + return (T)0; +} + +/** Specialisation of RTAutoResNil for RTFILE */ +template <> +inline RTFILE RTAutoResNil(void) +{ + return NIL_RTFILE; +} + +/** + * A function template which calls the correct destructor for an IPRT type. + * + * @warning This template *must* be specialised for the types it is to work with. + */ +template <class T> +inline void RTAutoResDestruct(T a_h) +{ + AssertFatalMsgFailed(("Unspecialized template!\n")); + NOREF(a_h); +} + +/** + * An auto pointer-type class for resources which take a C-style destructor + * (RTMemFree() or equivalent). + * + * The idea of this class is to manage resources which the current code is + * responsible for freeing. By wrapping the resource in an RTCAutoRes, you + * ensure that the resource will be freed when you leave the scope in which + * the RTCAutoRes is defined, unless you explicitly release the resource. + * + * A typical use case is when a function is allocating a number of resources. + * If any single allocation fails then all other resources must be freed. If + * all allocations succeed, then the resources should be returned to the + * caller. By placing all allocated resources in RTCAutoRes containers, you + * ensure that they will be freed on failure, and only have to take care of + * releasing them when you return them. + * + * @param T The type of the resource. + * @param Destruct The function to be used to free the resource. + * This parameter must be supplied if there is no + * specialisation of RTAutoDestruct available for @a T. + * @param NilRes The function returning the NIL value for T. Required. + * This parameter must be supplied if there is no + * specialisation of RTAutoResNil available for @a T. + * + * @note The class can not be initialised directly using assignment, due + * to the lack of a copy constructor. This is intentional. + */ +template <class T, void Destruct(T) = RTAutoResDestruct<T>, T NilRes(void) = RTAutoResNil<T> > +class RTCAutoRes + : public RTCNonCopyable +{ +protected: + /** The resource handle. */ + T m_hRes; + +public: + /** + * Constructor + * + * @param a_hRes The handle to resource to manage. Defaults to NIL. + */ + RTCAutoRes(T a_hRes = NilRes()) + : m_hRes(a_hRes) + { + } + + /** + * Destructor. + * + * This destroys any resource currently managed by the object. + */ + ~RTCAutoRes() + { + if (m_hRes != NilRes()) + Destruct(m_hRes); + } + + /** + * Assignment from a value. + * + * This destroys any resource currently managed by the object + * before taking on the new one. + * + * @param a_hRes The handle to the new resource. + */ + RTCAutoRes &operator=(T a_hRes) + { + if (m_hRes != NilRes()) + Destruct(m_hRes); + m_hRes = a_hRes; + return *this; + } + + /** + * Checks if the resource handle is NIL or not. + */ + bool operator!() + { + return m_hRes == NilRes(); + } + + /** + * Give up ownership the current resource, handing it to the caller. + * + * @returns The current resource handle. + * + * @note Nothing happens to the resource when the object goes out of scope. + */ + T release(void) + { + T Tmp = m_hRes; + m_hRes = NilRes(); + return Tmp; + } + + /** + * Deletes the current resources. + * + * @param a_hRes Handle to a new resource to manage. Defaults to NIL. + */ + void reset(T a_hRes = NilRes()) + { + if (a_hRes != m_hRes) + { + if (m_hRes != NilRes()) + Destruct(m_hRes); + m_hRes = a_hRes; + } + } + + /** + * Get the raw resource handle. + * + * Typically used passing the handle to some IPRT function while + * the object remains in scope. + * + * @returns The raw resource handle. + */ + T get(void) + { + return m_hRes; + } +}; + +/** @} */ + + +/* include after template definition */ +#include <iprt/mem.h> + +#endif /* !IPRT_INCLUDED_cpp_autores_h */ + diff --git a/include/iprt/cpp/exception.h b/include/iprt/cpp/exception.h new file mode 100644 index 00000000..283290d9 --- /dev/null +++ b/include/iprt/cpp/exception.h @@ -0,0 +1,118 @@ +/** @file + * IPRT - C++ Base Exceptions. + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#ifndef IPRT_INCLUDED_cpp_exception_h +#define IPRT_INCLUDED_cpp_exception_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include <iprt/cpp/ministring.h> +#include <exception> + +#if RT_MSC_PREREQ(RT_MSC_VER_VC140) +# pragma warning(push) +# pragma warning(disable:4275) /* non dll-interface class 'std::exception' used as base for dll-interface class 'RTCError' */ +#endif + + +/** @defgroup grp_rt_cpp_exceptions C++ Exceptions + * @ingroup grp_rt_cpp + * @{ + */ + +/** + * Base exception class for IPRT, derived from std::exception. + * The XML exceptions are based on this. + */ +class RT_DECL_CLASS RTCError + : public std::exception +{ +public: + + RTCError(const char *pszMessage) + : m_strMsg(pszMessage) + { + } + + RTCError(const RTCString &a_rstrMessage) + : m_strMsg(a_rstrMessage) + { + } + + RTCError(const RTCError &a_rSrc) + : std::exception(a_rSrc), + m_strMsg(a_rSrc.what()) + { + } + + virtual ~RTCError() throw() + { + } + + void operator=(const RTCError &a_rSrc) + { + m_strMsg = a_rSrc.what(); + } + + void setWhat(const char *a_pszMessage) + { + m_strMsg = a_pszMessage; + } + + virtual const char *what() const throw() + { + return m_strMsg.c_str(); + } + +private: + /** + * Hidden default constructor making sure that the extended one above is + * always used. + */ + RTCError(); + +protected: + /** The exception message. */ + RTCString m_strMsg; +}; + +/** @} */ + +#if RT_MSC_PREREQ(RT_MSC_VER_VC140) +# pragma warning(pop) +#endif +#endif /* !IPRT_INCLUDED_cpp_exception_h */ + diff --git a/include/iprt/cpp/hardavlrange.h b/include/iprt/cpp/hardavlrange.h new file mode 100644 index 00000000..3c07a667 --- /dev/null +++ b/include/iprt/cpp/hardavlrange.h @@ -0,0 +1,1285 @@ +/** @file + * IPRT - Hardened AVL tree, unique key ranges. + */ + +/* + * Copyright (C) 2022-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#ifndef IPRT_INCLUDED_cpp_hardavlrange_h +#define IPRT_INCLUDED_cpp_hardavlrange_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include <iprt/cpp/hardavlslaballocator.h> + +/** @defgroup grp_rt_cpp_hardavl Hardened AVL Trees + * @ingroup grp_rt_cpp + * @{ + */ + +/** + * Check that the tree heights make sense for the current node. + * + * This is a RT_STRICT test as it's expensive and we should have sufficient + * other checks to ensure safe AVL tree operation. + * + * @note the a_cStackEntries parameter is a hack to avoid running into gcc's + * "the address of 'AVLStack' will never be NULL" errors. + */ +#ifdef RT_STRICT +# define RTHARDAVL_STRICT_CHECK_HEIGHTS(a_pNode, a_pAvlStack, a_cStackEntries) do { \ + NodeType * const pLeftNodeX = a_pAllocator->ptrFromInt(readIdx(&(a_pNode)->idxLeft)); \ + AssertReturnStmt(a_pAllocator->isPtrRetOkay(pLeftNodeX), m_cErrors++, a_pAllocator->ptrErrToStatus((a_pNode))); \ + NodeType * const pRightNodeX = a_pAllocator->ptrFromInt(readIdx(&(a_pNode)->idxRight)); \ + AssertReturnStmt(a_pAllocator->isPtrRetOkay(pRightNodeX), m_cErrors++, a_pAllocator->ptrErrToStatus((a_pNode))); \ + uint8_t const cLeftHeightX = pLeftNodeX ? pLeftNodeX->cHeight : 0; \ + uint8_t const cRightHeightX = pRightNodeX ? pRightNodeX->cHeight : 0; \ + if (RT_LIKELY((a_pNode)->cHeight == RT_MAX(cLeftHeightX, cRightHeightX) + 1)) { /*likely*/ } \ + else \ + { \ + RTAssertMsg2("line %u: %u l=%u r=%u\n", __LINE__, (a_pNode)->cHeight, cLeftHeightX, cRightHeightX); \ + if ((a_cStackEntries)) dumpStack(a_pAllocator, (a_pAvlStack)); \ + AssertMsgReturnStmt((a_pNode)->cHeight == RT_MAX(cLeftHeightX, cRightHeightX) + 1, \ + ("%u l=%u r=%u\n", (a_pNode)->cHeight, cLeftHeightX, cRightHeightX), \ + m_cErrors++, VERR_HARDAVL_BAD_HEIGHT); \ + } \ + AssertMsgReturnStmt(RT_ABS(cLeftHeightX - cRightHeightX) <= 1, ("l=%u r=%u\n", cLeftHeightX, cRightHeightX), \ + m_cErrors++, VERR_HARDAVL_UNBALANCED); \ + Assert(!pLeftNodeX || pLeftNodeX->Key < (a_pNode)->Key); \ + Assert(!pRightNodeX || pRightNodeX->Key > (a_pNode)->Key); \ + } while (0) +#else +# define RTHARDAVL_STRICT_CHECK_HEIGHTS(a_pNode, a_pAvlStack, a_cStackEntries) do { } while (0) +#endif + + +/** + * Hardened AVL tree for nodes with key ranges. + * + * This is very crude and therefore expects the NodeType to feature: + * - Key and KeyLast members of KeyType. + * - idxLeft and idxRight members with type uint32_t. + * - cHeight members of type uint8_t. + * + * The code is very C-ish because of it's sources and initial use (ring-0 + * without C++ exceptions enabled). + */ +template<typename NodeType, typename KeyType> +struct RTCHardAvlRangeTree +{ + /** The root index. */ + uint32_t m_idxRoot; + /** The error count. */ + uint32_t m_cErrors; + /** @name Statistics + * @{ */ + uint64_t m_cInserts; + uint64_t m_cRemovals; + uint64_t m_cRebalancingOperations; + /** @} */ + + /** The max stack depth. */ + enum { kMaxStack = 28 }; + /** The max height value we allow. */ + enum { kMaxHeight = kMaxStack + 1 }; + + /** A stack used internally to avoid recursive calls. + * This is used with operations invoking i_rebalance(). */ + typedef struct HardAvlStack + { + /** Number of entries on the stack. */ + unsigned cEntries; + /** The stack. */ + uint32_t *apidxEntries[kMaxStack]; + } HardAvlStack; + + /** @name Key comparisons + * @{ */ + static inline int areKeyRangesIntersecting(KeyType a_Key1First, KeyType a_Key2First, + KeyType a_Key1Last, KeyType a_Key2Last) RT_NOEXCEPT + { + return a_Key1First <= a_Key2Last && a_Key1Last >= a_Key2First; + } + + static inline int isKeyInRange(KeyType a_Key, KeyType a_KeyFirst, KeyType a_KeyLast) RT_NOEXCEPT + { + return a_Key <= a_KeyLast && a_Key >= a_KeyFirst; + } + + static inline int isKeyGreater(KeyType a_Key1, KeyType a_Key2) RT_NOEXCEPT + { + return a_Key1 > a_Key2; + } + /** @} */ + + /** + * Read an index value trying to prevent the compiler from re-reading it. + */ + DECL_FORCE_INLINE(uint32_t) readIdx(uint32_t volatile *pidx) RT_NOEXCEPT + { + uint32_t idx = *pidx; + ASMCompilerBarrier(); + return idx; + } + + RTCHardAvlRangeTree() RT_NOEXCEPT + : m_idxRoot(0) + , m_cErrors(0) + { } + + RTCHardAvlRangeTree(RTCHardAvlTreeSlabAllocator<NodeType> *a_pAllocator) RT_NOEXCEPT + { + initWithAllocator(a_pAllocator); + } + + void initWithAllocator(RTCHardAvlTreeSlabAllocator<NodeType> *a_pAllocator) RT_NOEXCEPT + { + m_idxRoot = a_pAllocator->kNilIndex; + m_cErrors = 0; + } + + /** + * Inserts a node into the AVL-tree. + * + * @returns IPRT status code. + * @retval VERR_ALREADY_EXISTS if a node with overlapping key range exists. + * + * @param a_pAllocator Pointer to the allocator. + * @param a_pNode Pointer to the node which is to be added. + * + * @code + * Find the location of the node (using binary tree algorithm.): + * LOOP until KAVL_NULL leaf pointer + * BEGIN + * Add node pointer pointer to the AVL-stack. + * IF new-node-key < node key THEN + * left + * ELSE + * right + * END + * Fill in leaf node and insert it. + * Rebalance the tree. + * @endcode + */ + int insert(RTCHardAvlTreeSlabAllocator<NodeType> *a_pAllocator, NodeType *a_pNode) RT_NOEXCEPT + { + KeyType const Key = a_pNode->Key; + KeyType const KeyLast = a_pNode->KeyLast; + AssertMsgReturn(Key <= KeyLast, ("Key=%#RX64 KeyLast=%#RX64\n", (uint64_t)Key, (uint64_t)KeyLast), + VERR_HARDAVL_INSERT_INVALID_KEY_RANGE); + + uint32_t *pidxCurNode = &m_idxRoot; + HardAvlStack AVLStack; + AVLStack.cEntries = 0; + for (;;) + { + NodeType *pCurNode = a_pAllocator->ptrFromInt(readIdx(pidxCurNode)); + AssertMsgReturnStmt(a_pAllocator->isPtrRetOkay(pCurNode), ("*pidxCurNode=%#x pCurNode=%p\n", *pidxCurNode, pCurNode), + m_cErrors++, a_pAllocator->ptrErrToStatus(pCurNode)); + if (!pCurNode) + break; + + unsigned const cEntries = AVLStack.cEntries; + AssertMsgReturnStmt(cEntries < RT_ELEMENTS(AVLStack.apidxEntries), + ("%p[%#x/%p] %p[%#x] %p[%#x] %p[%#x] %p[%#x] %p[%#x]\n", pidxCurNode, *pidxCurNode, pCurNode, + AVLStack.apidxEntries[RT_ELEMENTS(AVLStack.apidxEntries) - 1], *AVLStack.apidxEntries[RT_ELEMENTS(AVLStack.apidxEntries) - 1], + AVLStack.apidxEntries[RT_ELEMENTS(AVLStack.apidxEntries) - 2], *AVLStack.apidxEntries[RT_ELEMENTS(AVLStack.apidxEntries) - 2], + AVLStack.apidxEntries[RT_ELEMENTS(AVLStack.apidxEntries) - 3], *AVLStack.apidxEntries[RT_ELEMENTS(AVLStack.apidxEntries) - 3], + AVLStack.apidxEntries[RT_ELEMENTS(AVLStack.apidxEntries) - 4], *AVLStack.apidxEntries[RT_ELEMENTS(AVLStack.apidxEntries) - 4], + AVLStack.apidxEntries[RT_ELEMENTS(AVLStack.apidxEntries) - 5], *AVLStack.apidxEntries[RT_ELEMENTS(AVLStack.apidxEntries) - 5]), + m_cErrors++, VERR_HARDAVL_STACK_OVERFLOW); + AVLStack.apidxEntries[cEntries] = pidxCurNode; + AVLStack.cEntries = cEntries + 1; + + RTHARDAVL_STRICT_CHECK_HEIGHTS(pCurNode, &AVLStack, AVLStack.cEntries); + + /* Range check: */ + if (areKeyRangesIntersecting(pCurNode->Key, Key, pCurNode->KeyLast, KeyLast)) + return VERR_ALREADY_EXISTS; + + /* Descend: */ + if (isKeyGreater(pCurNode->Key, Key)) + pidxCurNode = &pCurNode->idxLeft; + else + pidxCurNode = &pCurNode->idxRight; + } + + a_pNode->idxLeft = a_pAllocator->kNilIndex; + a_pNode->idxRight = a_pAllocator->kNilIndex; + a_pNode->cHeight = 1; + + uint32_t const idxNode = a_pAllocator->ptrToInt(a_pNode); + AssertMsgReturn(a_pAllocator->isIdxRetOkay(idxNode), ("pNode=%p idxNode=%#x\n", a_pNode, idxNode), + a_pAllocator->idxErrToStatus(idxNode)); + *pidxCurNode = idxNode; + + m_cInserts++; + return i_rebalance(a_pAllocator, &AVLStack); + } + + /** + * Removes a node from the AVL-tree by a key value. + * + * @returns IPRT status code. + * @retval VERR_NOT_FOUND if not found. + * @param a_pAllocator Pointer to the allocator. + * @param a_Key A key value in the range of the node to be removed. + * @param a_ppRemoved Where to return the pointer to the removed node. + * + * @code + * Find the node which is to be removed: + * LOOP until not found + * BEGIN + * Add node pointer pointer to the AVL-stack. + * IF the keys matches THEN break! + * IF remove key < node key THEN + * left + * ELSE + * right + * END + * IF found THEN + * BEGIN + * IF left node not empty THEN + * BEGIN + * Find the right most node in the left tree while adding the pointer to the pointer to it's parent to the stack: + * Start at left node. + * LOOP until right node is empty + * BEGIN + * Add to stack. + * go right. + * END + * Link out the found node. + * Replace the node which is to be removed with the found node. + * Correct the stack entry for the pointer to the left tree. + * END + * ELSE + * BEGIN + * Move up right node. + * Remove last stack entry. + * END + * Balance tree using stack. + * END + * return pointer to the removed node (if found). + * @endcode + */ + int remove(RTCHardAvlTreeSlabAllocator<NodeType> *a_pAllocator, KeyType a_Key, NodeType **a_ppRemoved) RT_NOEXCEPT + { + *a_ppRemoved = NULL; + + /* + * Walk the tree till we locate the node that is to be deleted. + */ + uint32_t *pidxDeleteNode = &m_idxRoot; + NodeType *pDeleteNode; + HardAvlStack AVLStack; + AVLStack.cEntries = 0; + for (;;) + { + pDeleteNode = a_pAllocator->ptrFromInt(readIdx(pidxDeleteNode)); + AssertMsgReturnStmt(a_pAllocator->isPtrRetOkay(pDeleteNode), + ("*pidxCurNode=%#x pDeleteNode=%p\n", *pidxDeleteNode, pDeleteNode), + m_cErrors++, a_pAllocator->ptrErrToStatus(pDeleteNode)); + if (pDeleteNode) + { /*likely*/ } + else + return VERR_NOT_FOUND; + + unsigned const cEntries = AVLStack.cEntries; + AssertMsgReturnStmt(cEntries < RT_ELEMENTS(AVLStack.apidxEntries), + ("%p[%#x/%p] %p[%#x] %p[%#x] %p[%#x] %p[%#x] %p[%#x]\n", + pidxDeleteNode, *pidxDeleteNode, pDeleteNode, + AVLStack.apidxEntries[RT_ELEMENTS(AVLStack.apidxEntries) - 1], *AVLStack.apidxEntries[RT_ELEMENTS(AVLStack.apidxEntries) - 1], + AVLStack.apidxEntries[RT_ELEMENTS(AVLStack.apidxEntries) - 2], *AVLStack.apidxEntries[RT_ELEMENTS(AVLStack.apidxEntries) - 2], + AVLStack.apidxEntries[RT_ELEMENTS(AVLStack.apidxEntries) - 3], *AVLStack.apidxEntries[RT_ELEMENTS(AVLStack.apidxEntries) - 3], + AVLStack.apidxEntries[RT_ELEMENTS(AVLStack.apidxEntries) - 4], *AVLStack.apidxEntries[RT_ELEMENTS(AVLStack.apidxEntries) - 4], + AVLStack.apidxEntries[RT_ELEMENTS(AVLStack.apidxEntries) - 5], *AVLStack.apidxEntries[RT_ELEMENTS(AVLStack.apidxEntries) - 5]), + m_cErrors++, VERR_HARDAVL_STACK_OVERFLOW); + AVLStack.apidxEntries[cEntries] = pidxDeleteNode; + AVLStack.cEntries = cEntries + 1; + + RTHARDAVL_STRICT_CHECK_HEIGHTS(pDeleteNode, &AVLStack, AVLStack.cEntries); + + /* Range check: */ + if (isKeyInRange(a_Key, pDeleteNode->Key, pDeleteNode->KeyLast)) + break; + + /* Descend: */ + if (isKeyGreater(pDeleteNode->Key, a_Key)) + pidxDeleteNode = &pDeleteNode->idxLeft; + else + pidxDeleteNode = &pDeleteNode->idxRight; + } + + /* + * Do the deletion. + */ + uint32_t const idxDeleteLeftNode = readIdx(&pDeleteNode->idxLeft); + if (idxDeleteLeftNode != a_pAllocator->kNilIndex) + { + /* + * Replace the deleted node with the rightmost node in the left subtree. + */ + NodeType * const pDeleteLeftNode = a_pAllocator->ptrFromInt(idxDeleteLeftNode); + AssertMsgReturnStmt(a_pAllocator->isPtrRetOkay(pDeleteLeftNode), + ("idxDeleteLeftNode=%#x pDeleteLeftNode=%p\n", idxDeleteLeftNode, pDeleteLeftNode), + m_cErrors++, a_pAllocator->ptrErrToStatus(pDeleteLeftNode)); + + uint32_t const idxDeleteRightNode = readIdx(&pDeleteNode->idxRight); + AssertReturnStmt(a_pAllocator->isIntValid(idxDeleteRightNode), m_cErrors++, VERR_HARDAVL_INDEX_OUT_OF_BOUNDS); + + const unsigned iStackEntry = AVLStack.cEntries; + + uint32_t *pidxLeftBiggest = &pDeleteNode->idxLeft; + uint32_t idxLeftBiggestNode = idxDeleteLeftNode; + NodeType *pLeftBiggestNode = pDeleteLeftNode; + RTHARDAVL_STRICT_CHECK_HEIGHTS(pLeftBiggestNode, &AVLStack, AVLStack.cEntries); + + uint32_t idxRightTmp; + while ((idxRightTmp = readIdx(&pLeftBiggestNode->idxRight)) != a_pAllocator->kNilIndex) + { + unsigned const cEntries = AVLStack.cEntries; + AssertMsgReturnStmt(cEntries < RT_ELEMENTS(AVLStack.apidxEntries), + ("%p[%#x/%p] %p[%#x] %p[%#x] %p[%#x] %p[%#x] %p[%#x]\n", + pidxLeftBiggest, *pidxLeftBiggest, pLeftBiggestNode, + AVLStack.apidxEntries[RT_ELEMENTS(AVLStack.apidxEntries) - 1], *AVLStack.apidxEntries[RT_ELEMENTS(AVLStack.apidxEntries) - 1], + AVLStack.apidxEntries[RT_ELEMENTS(AVLStack.apidxEntries) - 2], *AVLStack.apidxEntries[RT_ELEMENTS(AVLStack.apidxEntries) - 2], + AVLStack.apidxEntries[RT_ELEMENTS(AVLStack.apidxEntries) - 3], *AVLStack.apidxEntries[RT_ELEMENTS(AVLStack.apidxEntries) - 3], + AVLStack.apidxEntries[RT_ELEMENTS(AVLStack.apidxEntries) - 4], *AVLStack.apidxEntries[RT_ELEMENTS(AVLStack.apidxEntries) - 4], + AVLStack.apidxEntries[RT_ELEMENTS(AVLStack.apidxEntries) - 5], *AVLStack.apidxEntries[RT_ELEMENTS(AVLStack.apidxEntries) - 5]), + m_cErrors++, VERR_HARDAVL_STACK_OVERFLOW); + AVLStack.apidxEntries[cEntries] = pidxLeftBiggest; + AVLStack.cEntries = cEntries + 1; + + pidxLeftBiggest = &pLeftBiggestNode->idxRight; + idxLeftBiggestNode = idxRightTmp; + pLeftBiggestNode = a_pAllocator->ptrFromInt(idxRightTmp); + AssertMsgReturnStmt(a_pAllocator->isPtrRetOkay(pLeftBiggestNode), + ("idxLeftBiggestNode=%#x pLeftBiggestNode=%p\n", idxLeftBiggestNode, pLeftBiggestNode), + m_cErrors++, a_pAllocator->ptrErrToStatus(pLeftBiggestNode)); + RTHARDAVL_STRICT_CHECK_HEIGHTS(pLeftBiggestNode, &AVLStack, AVLStack.cEntries); + } + + uint32_t const idxLeftBiggestLeftNode = readIdx(&pLeftBiggestNode->idxLeft); + AssertReturnStmt(a_pAllocator->isIntValid(idxLeftBiggestLeftNode), m_cErrors++, VERR_HARDAVL_INDEX_OUT_OF_BOUNDS); + + /* link out pLeftBiggestNode */ + *pidxLeftBiggest = idxLeftBiggestLeftNode; + + /* link it in place of the deleted node. */ + if (idxDeleteLeftNode != idxLeftBiggestNode) + pLeftBiggestNode->idxLeft = idxDeleteLeftNode; + pLeftBiggestNode->idxRight = idxDeleteRightNode; + pLeftBiggestNode->cHeight = AVLStack.cEntries > iStackEntry ? pDeleteNode->cHeight : 0; + + *pidxDeleteNode = idxLeftBiggestNode; + + if (AVLStack.cEntries > iStackEntry) + AVLStack.apidxEntries[iStackEntry] = &pLeftBiggestNode->idxLeft; + } + else + { + /* No left node, just pull up the right one. */ + uint32_t const idxDeleteRightNode = readIdx(&pDeleteNode->idxRight); + AssertReturnStmt(a_pAllocator->isIntValid(idxDeleteRightNode), m_cErrors++, VERR_HARDAVL_INDEX_OUT_OF_BOUNDS); + *pidxDeleteNode = idxDeleteRightNode; + AVLStack.cEntries--; + } + *a_ppRemoved = pDeleteNode; + + m_cRemovals++; + return i_rebalance(a_pAllocator, &AVLStack); + } + + /** + * Looks up a node from the tree. + * + * @returns IPRT status code. + * @retval VERR_NOT_FOUND if not found. + * + * @param a_pAllocator Pointer to the allocator. + * @param a_Key A key value in the range of the desired node. + * @param a_ppFound Where to return the pointer to the node. + */ + int lookup(RTCHardAvlTreeSlabAllocator<NodeType> *a_pAllocator, KeyType a_Key, NodeType **a_ppFound) RT_NOEXCEPT + { + *a_ppFound = NULL; + + NodeType *pNode = a_pAllocator->ptrFromInt(readIdx(&m_idxRoot)); + AssertMsgReturnStmt(a_pAllocator->isPtrRetOkay(pNode), ("m_idxRoot=%#x pNode=%p\n", m_idxRoot, pNode), + m_cErrors++, a_pAllocator->ptrErrToStatus(pNode)); +#ifdef RT_STRICT + HardAvlStack AVLStack; + AVLStack.apidxEntries[0] = &m_idxRoot; + AVLStack.cEntries = 1; +#endif + unsigned cDepth = 0; + while (pNode) + { + RTHARDAVL_STRICT_CHECK_HEIGHTS(pNode, &AVLStack, AVLStack.cEntries); + AssertReturn(cDepth <= kMaxHeight, VERR_HARDAVL_LOOKUP_TOO_DEEP); + cDepth++; + + if (isKeyInRange(a_Key, pNode->Key, pNode->KeyLast)) + { + *a_ppFound = pNode; + return VINF_SUCCESS; + } + if (isKeyGreater(pNode->Key, a_Key)) + { +#ifdef RT_STRICT + AVLStack.apidxEntries[AVLStack.cEntries++] = &pNode->idxLeft; +#endif + uint32_t const idxLeft = readIdx(&pNode->idxLeft); + pNode = a_pAllocator->ptrFromInt(idxLeft); + AssertMsgReturnStmt(a_pAllocator->isPtrRetOkay(pNode), ("idxLeft=%#x pNode=%p\n", idxLeft, pNode), + m_cErrors++, a_pAllocator->ptrErrToStatus(pNode)); + } + else + { +#ifdef RT_STRICT + AVLStack.apidxEntries[AVLStack.cEntries++] = &pNode->idxRight; +#endif + uint32_t const idxRight = readIdx(&pNode->idxRight); + pNode = a_pAllocator->ptrFromInt(idxRight); + AssertMsgReturnStmt(a_pAllocator->isPtrRetOkay(pNode), ("idxRight=%#x pNode=%p\n", idxRight, pNode), + m_cErrors++, a_pAllocator->ptrErrToStatus(pNode)); + } + } + + return VERR_NOT_FOUND; + } + + /** + * Looks up node matching @a a_Key or if no exact match the closest smaller than it. + * + * @returns IPRT status code. + * @retval VERR_NOT_FOUND if not found. + * + * @param a_pAllocator Pointer to the allocator. + * @param a_Key A key value in the range of the desired node. + * @param a_ppFound Where to return the pointer to the node. + */ + int lookupMatchingOrBelow(RTCHardAvlTreeSlabAllocator<NodeType> *a_pAllocator, KeyType a_Key, + NodeType **a_ppFound) RT_NOEXCEPT + { + *a_ppFound = NULL; + + NodeType *pNode = a_pAllocator->ptrFromInt(readIdx(&m_idxRoot)); + AssertMsgReturnStmt(a_pAllocator->isPtrRetOkay(pNode), ("m_idxRoot=%#x pNode=%p\n", m_idxRoot, pNode), + m_cErrors++, a_pAllocator->ptrErrToStatus(pNode)); +#ifdef RT_STRICT + HardAvlStack AVLStack; + AVLStack.apidxEntries[0] = &m_idxRoot; + AVLStack.cEntries = 1; +#endif + unsigned cDepth = 0; + NodeType *pNodeLast = NULL; + while (pNode) + { + RTHARDAVL_STRICT_CHECK_HEIGHTS(pNode, &AVLStack, AVLStack.cEntries); + AssertReturn(cDepth <= kMaxHeight, VERR_HARDAVL_LOOKUP_TOO_DEEP); + cDepth++; + + if (isKeyInRange(a_Key, pNode->Key, pNode->KeyLast)) + { + *a_ppFound = pNode; + return VINF_SUCCESS; + } + if (isKeyGreater(pNode->Key, a_Key)) + { +#ifdef RT_STRICT + AVLStack.apidxEntries[AVLStack.cEntries++] = &pNode->idxLeft; +#endif + uint32_t const idxLeft = readIdx(&pNode->idxLeft); + NodeType *pLeftNode = a_pAllocator->ptrFromInt(idxLeft); + AssertMsgReturnStmt(a_pAllocator->isPtrRetOkay(pLeftNode), ("idxLeft=%#x pLeftNode=%p\n", idxLeft, pLeftNode), + m_cErrors++, a_pAllocator->ptrErrToStatus(pLeftNode)); + if (pLeftNode) + pNode = pLeftNode; + else if (!pNodeLast) + break; + else + { + *a_ppFound = pNodeLast; + return VINF_SUCCESS; + } + } + else + { +#ifdef RT_STRICT + AVLStack.apidxEntries[AVLStack.cEntries++] = &pNode->idxRight; +#endif + uint32_t const idxRight = readIdx(&pNode->idxRight); + NodeType *pRightNode = a_pAllocator->ptrFromInt(idxRight); + AssertMsgReturnStmt(a_pAllocator->isPtrRetOkay(pRightNode), ("idxRight=%#x pRightNode=%p\n", idxRight, pRightNode), + m_cErrors++, a_pAllocator->ptrErrToStatus(pRightNode)); + if (pRightNode) + { + pNodeLast = pNode; + pNode = pRightNode; + } + else + { + *a_ppFound = pNode; + return VINF_SUCCESS; + } + } + } + + return VERR_NOT_FOUND; + } + + /** + * Looks up node matching @a a_Key or if no exact match the closest larger than it. + * + * @returns IPRT status code. + * @retval VERR_NOT_FOUND if not found. + * + * @param a_pAllocator Pointer to the allocator. + * @param a_Key A key value in the range of the desired node. + * @param a_ppFound Where to return the pointer to the node. + */ + int lookupMatchingOrAbove(RTCHardAvlTreeSlabAllocator<NodeType> *a_pAllocator, KeyType a_Key, + NodeType **a_ppFound) RT_NOEXCEPT + { + *a_ppFound = NULL; + + NodeType *pNode = a_pAllocator->ptrFromInt(readIdx(&m_idxRoot)); + AssertMsgReturnStmt(a_pAllocator->isPtrRetOkay(pNode), ("m_idxRoot=%#x pNode=%p\n", m_idxRoot, pNode), + m_cErrors++, a_pAllocator->ptrErrToStatus(pNode)); +#ifdef RT_STRICT + HardAvlStack AVLStack; + AVLStack.apidxEntries[0] = &m_idxRoot; + AVLStack.cEntries = 1; +#endif + unsigned cDepth = 0; + NodeType *pNodeLast = NULL; + while (pNode) + { + RTHARDAVL_STRICT_CHECK_HEIGHTS(pNode, &AVLStack, AVLStack.cEntries); + AssertReturn(cDepth <= kMaxHeight, VERR_HARDAVL_LOOKUP_TOO_DEEP); + cDepth++; + + if (isKeyInRange(a_Key, pNode->Key, pNode->KeyLast)) + { + *a_ppFound = pNode; + return VINF_SUCCESS; + } + if (isKeyGreater(pNode->Key, a_Key)) + { +#ifdef RT_STRICT + AVLStack.apidxEntries[AVLStack.cEntries++] = &pNode->idxLeft; +#endif + uint32_t const idxLeft = readIdx(&pNode->idxLeft); + NodeType *pLeftNode = a_pAllocator->ptrFromInt(idxLeft); + AssertMsgReturnStmt(a_pAllocator->isPtrRetOkay(pLeftNode), ("idxLeft=%#x pLeftNode=%p\n", idxLeft, pLeftNode), + m_cErrors++, a_pAllocator->ptrErrToStatus(pLeftNode)); + if (pLeftNode) + { + pNodeLast = pNode; + pNode = pLeftNode; + } + else + { + *a_ppFound = pNode; + return VINF_SUCCESS; + } + } + else + { +#ifdef RT_STRICT + AVLStack.apidxEntries[AVLStack.cEntries++] = &pNode->idxRight; +#endif + uint32_t const idxRight = readIdx(&pNode->idxRight); + NodeType *pRightNode = a_pAllocator->ptrFromInt(idxRight); + AssertMsgReturnStmt(a_pAllocator->isPtrRetOkay(pRightNode), ("idxRight=%#x pRightNode=%p\n", idxRight, pRightNode), + m_cErrors++, a_pAllocator->ptrErrToStatus(pRightNode)); + if (pRightNode) + pNode = pRightNode; + else if (!pNodeLast) + break; + else + { + *a_ppFound = pNodeLast; + return VINF_SUCCESS; + } + } + } + + return VERR_NOT_FOUND; + } + + /** + * A callback for doWithAllFromLeft and doWithAllFromRight. + * + * @returns IPRT status code. Any non-zero status causes immediate return from + * the enumeration function. + * @param pNode The current node. + * @param pvUser The user argument. + */ + typedef DECLCALLBACKTYPE(int, FNCALLBACK,(NodeType *pNode, void *pvUser)); + /** Pointer to a callback for doWithAllFromLeft and doWithAllFromRight. */ + typedef FNCALLBACK *PFNCALLBACK; + + /** + * Iterates thru all nodes in the tree from left (smaller) to right. + * + * @returns IPRT status code. + * + * @param a_pAllocator Pointer to the allocator. + * @param a_pfnCallBack Pointer to callback function. + * @param a_pvUser Callback user argument. + * + * @note This is very similar code to doWithAllFromRight() and destroy(). + */ + int doWithAllFromLeft(RTCHardAvlTreeSlabAllocator<NodeType> *a_pAllocator, + PFNCALLBACK a_pfnCallBack, void *a_pvUser) RT_NOEXCEPT + { + NodeType *pNode = a_pAllocator->ptrFromInt(readIdx(&m_idxRoot)); + AssertMsgReturnStmt(a_pAllocator->isPtrRetOkay(pNode), ("m_idxRoot=%#x pNode=%p\n", m_idxRoot, pNode), + m_cErrors++, a_pAllocator->ptrErrToStatus(pNode)); + if (!pNode) + return VINF_SUCCESS; + + /* + * We simulate recursive calling here. For safety reasons, we do not + * pop before going down the right tree like the original code did. + */ + uint32_t cNodesLeft = a_pAllocator->m_cNodes; + NodeType *apEntries[kMaxStack]; + uint8_t abState[kMaxStack]; + unsigned cEntries = 1; + abState[0] = 0; + apEntries[0] = pNode; + while (cEntries > 0) + { + pNode = apEntries[cEntries - 1]; + switch (abState[cEntries - 1]) + { + /* Go left. */ + case 0: + { + abState[cEntries - 1] = 1; + + NodeType * const pLeftNode = a_pAllocator->ptrFromInt(readIdx(&pNode->idxLeft)); + AssertMsgReturnStmt(a_pAllocator->isPtrRetOkay(pLeftNode), + ("idxLeft=%#x pLeftNode=%p\n", pNode->idxLeft, pLeftNode), + m_cErrors++, a_pAllocator->ptrErrToStatus(pLeftNode)); + if (pLeftNode) + { +#if RT_GNUC_PREREQ_EX(4,7,1) && defined(RTASSERT_HAVE_STATIC_ASSERT) /* 32-bit 4.4.7 has trouble, dunno when it started working */ + AssertCompile(kMaxStack > 6); /* exactly. Seems having static_assert is required. */ +#endif + AssertMsgReturnStmt(cEntries < RT_ELEMENTS(apEntries), + ("%p[%#x] %p %p %p %p %p %p\n", pLeftNode, pNode->idxLeft, apEntries[kMaxStack - 1], + apEntries[kMaxStack - 2], apEntries[kMaxStack - 3], apEntries[kMaxStack - 4], + apEntries[kMaxStack - 5], apEntries[kMaxStack - 6]), + m_cErrors++, VERR_HARDAVL_STACK_OVERFLOW); + apEntries[cEntries] = pLeftNode; + abState[cEntries] = 0; + cEntries++; + + AssertReturn(cNodesLeft > 0, VERR_HARDAVL_TRAVERSED_TOO_MANY_NODES); + cNodesLeft--; + break; + } + RT_FALL_THROUGH(); + } + + /* center then right. */ + case 1: + { + abState[cEntries - 1] = 2; + + RTHARDAVL_STRICT_CHECK_HEIGHTS(pNode, NULL, 0); + + int rc = a_pfnCallBack(pNode, a_pvUser); + if (rc != VINF_SUCCESS) + return rc; + + NodeType * const pRightNode = a_pAllocator->ptrFromInt(readIdx(&pNode->idxRight)); + AssertMsgReturnStmt(a_pAllocator->isPtrRetOkay(pRightNode), + ("idxRight=%#x pRightNode=%p\n", pNode->idxRight, pRightNode), + m_cErrors++, a_pAllocator->ptrErrToStatus(pRightNode)); + if (pRightNode) + { +#if RT_GNUC_PREREQ_EX(4,7,1) && defined(RTASSERT_HAVE_STATIC_ASSERT) /* 32-bit 4.4.7 has trouble, dunno when it started working */ + AssertCompile(kMaxStack > 6); /* exactly. Seems having static_assert is required. */ +#endif + AssertMsgReturnStmt(cEntries < RT_ELEMENTS(apEntries), + ("%p[%#x] %p %p %p %p %p %p\n", pRightNode, pNode->idxRight, apEntries[kMaxStack - 1], + apEntries[kMaxStack - 2], apEntries[kMaxStack - 3], apEntries[kMaxStack - 4], + apEntries[kMaxStack - 5], apEntries[kMaxStack - 6]), + m_cErrors++, VERR_HARDAVL_STACK_OVERFLOW); + apEntries[cEntries] = pRightNode; + abState[cEntries] = 0; + cEntries++; + + AssertReturn(cNodesLeft > 0, VERR_HARDAVL_TRAVERSED_TOO_MANY_NODES); + cNodesLeft--; + break; + } + RT_FALL_THROUGH(); + } + + default: + /* pop it. */ + cEntries -= 1; + break; + } + } + return VINF_SUCCESS; + } + + /** + * Iterates thru all nodes in the tree from right (larger) to left (smaller). + * + * @returns IPRT status code. + * + * @param a_pAllocator Pointer to the allocator. + * @param a_pfnCallBack Pointer to callback function. + * @param a_pvUser Callback user argument. + * + * @note This is very similar code to doWithAllFromLeft() and destroy(). + */ + int doWithAllFromRight(RTCHardAvlTreeSlabAllocator<NodeType> *a_pAllocator, + PFNCALLBACK a_pfnCallBack, void *a_pvUser) RT_NOEXCEPT + { + NodeType *pNode = a_pAllocator->ptrFromInt(readIdx(&m_idxRoot)); + AssertMsgReturnStmt(a_pAllocator->isPtrRetOkay(pNode), ("m_idxRoot=%#x pNode=%p\n", m_idxRoot, pNode), + m_cErrors++, a_pAllocator->ptrErrToStatus(pNode)); + if (!pNode) + return VINF_SUCCESS; + + /* + * We simulate recursive calling here. For safety reasons, we do not + * pop before going down the right tree like the original code did. + */ + uint32_t cNodesLeft = a_pAllocator->m_cNodes; + NodeType *apEntries[kMaxStack]; + uint8_t abState[kMaxStack]; + unsigned cEntries = 1; + abState[0] = 0; + apEntries[0] = pNode; + while (cEntries > 0) + { + pNode = apEntries[cEntries - 1]; + switch (abState[cEntries - 1]) + { + /* Go right. */ + case 0: + { + abState[cEntries - 1] = 1; + + NodeType * const pRightNode = a_pAllocator->ptrFromInt(readIdx(&pNode->idxRight)); + AssertMsgReturnStmt(a_pAllocator->isPtrRetOkay(pRightNode), + ("idxRight=%#x pRightNode=%p\n", pNode->idxRight, pRightNode), + m_cErrors++, a_pAllocator->ptrErrToStatus(pRightNode)); + if (pRightNode) + { +#if RT_GNUC_PREREQ_EX(4,7,1) && defined(RTASSERT_HAVE_STATIC_ASSERT) /* 32-bit 4.4.7 has trouble, dunno when it started working */ + AssertCompile(kMaxStack > 6); /* exactly. Seems having static_assert is required. */ +#endif + AssertMsgReturnStmt(cEntries < RT_ELEMENTS(apEntries), + ("%p[%#x] %p %p %p %p %p %p\n", pRightNode, pNode->idxRight, apEntries[kMaxStack - 1], + apEntries[kMaxStack - 2], apEntries[kMaxStack - 3], apEntries[kMaxStack - 4], + apEntries[kMaxStack - 5], apEntries[kMaxStack - 6]), + m_cErrors++, VERR_HARDAVL_STACK_OVERFLOW); + apEntries[cEntries] = pRightNode; + abState[cEntries] = 0; + cEntries++; + + AssertReturn(cNodesLeft > 0, VERR_HARDAVL_TRAVERSED_TOO_MANY_NODES); + cNodesLeft--; + break; + } + RT_FALL_THROUGH(); + } + + /* center then left. */ + case 1: + { + abState[cEntries - 1] = 2; + + RTHARDAVL_STRICT_CHECK_HEIGHTS(pNode, NULL, 0); + + int rc = a_pfnCallBack(pNode, a_pvUser); + if (rc != VINF_SUCCESS) + return rc; + + NodeType * const pLeftNode = a_pAllocator->ptrFromInt(readIdx(&pNode->idxLeft)); + AssertMsgReturnStmt(a_pAllocator->isPtrRetOkay(pLeftNode), + ("idxLeft=%#x pLeftNode=%p\n", pNode->idxLeft, pLeftNode), + m_cErrors++, a_pAllocator->ptrErrToStatus(pLeftNode)); + if (pLeftNode) + { +#if RT_GNUC_PREREQ_EX(4,7,1) && defined(RTASSERT_HAVE_STATIC_ASSERT) /* 32-bit 4.4.7 has trouble, dunno when it started working */ + AssertCompile(kMaxStack > 6); /* exactly. Seems having static_assert is required. */ +#endif + AssertMsgReturnStmt(cEntries < RT_ELEMENTS(apEntries), + ("%p[%#x] %p %p %p %p %p %p\n", pLeftNode, pNode->idxLeft, apEntries[kMaxStack - 1], + apEntries[kMaxStack - 2], apEntries[kMaxStack - 3], apEntries[kMaxStack - 4], + apEntries[kMaxStack - 5], apEntries[kMaxStack - 6]), + m_cErrors++, VERR_HARDAVL_STACK_OVERFLOW); + apEntries[cEntries] = pLeftNode; + abState[cEntries] = 0; + cEntries++; + + AssertReturn(cNodesLeft > 0, VERR_HARDAVL_TRAVERSED_TOO_MANY_NODES); + cNodesLeft--; + break; + } + RT_FALL_THROUGH(); + } + + default: + /* pop it. */ + cEntries -= 1; + break; + } + } + return VINF_SUCCESS; + } + + /** + * A callback for destroy to do additional cleanups before the node is freed. + * + * @param pNode The current node. + * @param pvUser The user argument. + */ + typedef DECLCALLBACKTYPE(void, FNDESTROYCALLBACK,(NodeType *pNode, void *pvUser)); + /** Pointer to a callback for destroy. */ + typedef FNDESTROYCALLBACK *PFNDESTROYCALLBACK; + + /** + * Destroys the tree, starting with the root node. + * + * This will invoke the freeNode() method on the allocate for every node after + * first doing the callback to let the caller free additional resources + * referenced by the node. + * + * @returns IPRT status code. + * + * @param a_pAllocator Pointer to the allocator. + * @param a_pfnCallBack Pointer to callback function. Optional. + * @param a_pvUser Callback user argument. + * + * @note This is mostly the same code as the doWithAllFromLeft(). + */ + int destroy(RTCHardAvlTreeSlabAllocator<NodeType> *a_pAllocator, + PFNDESTROYCALLBACK a_pfnCallBack = NULL, void *a_pvUser = NULL) RT_NOEXCEPT + { + NodeType *pNode = a_pAllocator->ptrFromInt(readIdx(&m_idxRoot)); + AssertMsgReturnStmt(a_pAllocator->isPtrRetOkay(pNode), ("m_idxRoot=%#x pNode=%p\n", m_idxRoot, pNode), + m_cErrors++, a_pAllocator->ptrErrToStatus(pNode)); + if (!pNode) + return VINF_SUCCESS; + + /* + * We simulate recursive calling here. For safety reasons, we do not + * pop before going down the right tree like the original code did. + */ + uint32_t cNodesLeft = a_pAllocator->m_cNodes; + NodeType *apEntries[kMaxStack]; + uint8_t abState[kMaxStack]; + unsigned cEntries = 1; + abState[0] = 0; + apEntries[0] = pNode; + while (cEntries > 0) + { + pNode = apEntries[cEntries - 1]; + switch (abState[cEntries - 1]) + { + /* Go left. */ + case 0: + { + abState[cEntries - 1] = 1; + + NodeType * const pLeftNode = a_pAllocator->ptrFromInt(readIdx(&pNode->idxLeft)); + AssertMsgReturnStmt(a_pAllocator->isPtrRetOkay(pLeftNode), + ("idxLeft=%#x pLeftNode=%p\n", pNode->idxLeft, pLeftNode), + m_cErrors++, a_pAllocator->ptrErrToStatus(pLeftNode)); + if (pLeftNode) + { +#if RT_GNUC_PREREQ_EX(4,7,1) && defined(RTASSERT_HAVE_STATIC_ASSERT) /* 32-bit 4.4.7 has trouble, dunno when it started working */ + AssertCompile(kMaxStack > 6); /* exactly. Seems having static_assert is required. */ +#endif + AssertMsgReturnStmt(cEntries < RT_ELEMENTS(apEntries), + ("%p[%#x] %p %p %p %p %p %p\n", pLeftNode, pNode->idxLeft, apEntries[kMaxStack - 1], + apEntries[kMaxStack - 2], apEntries[kMaxStack - 3], apEntries[kMaxStack - 4], + apEntries[kMaxStack - 5], apEntries[kMaxStack - 6]), + m_cErrors++, VERR_HARDAVL_STACK_OVERFLOW); + apEntries[cEntries] = pLeftNode; + abState[cEntries] = 0; + cEntries++; + + AssertReturn(cNodesLeft > 0, VERR_HARDAVL_TRAVERSED_TOO_MANY_NODES); + cNodesLeft--; + break; + } + RT_FALL_THROUGH(); + } + + /* right. */ + case 1: + { + abState[cEntries - 1] = 2; + + NodeType * const pRightNode = a_pAllocator->ptrFromInt(readIdx(&pNode->idxRight)); + AssertMsgReturnStmt(a_pAllocator->isPtrRetOkay(pRightNode), + ("idxRight=%#x pRightNode=%p\n", pNode->idxRight, pRightNode), + m_cErrors++, a_pAllocator->ptrErrToStatus(pRightNode)); + if (pRightNode) + { +#if RT_GNUC_PREREQ_EX(4,7,1) && defined(RTASSERT_HAVE_STATIC_ASSERT) /* 32-bit 4.4.7 has trouble, dunno when it started working */ + AssertCompile(kMaxStack > 6); /* exactly. Seems having static_assert is required. */ +#endif + AssertMsgReturnStmt(cEntries < RT_ELEMENTS(apEntries), + ("%p[%#x] %p %p %p %p %p %p\n", pRightNode, pNode->idxRight, apEntries[kMaxStack - 1], + apEntries[kMaxStack - 2], apEntries[kMaxStack - 3], apEntries[kMaxStack - 4], + apEntries[kMaxStack - 5], apEntries[kMaxStack - 6]), + m_cErrors++, VERR_HARDAVL_STACK_OVERFLOW); + apEntries[cEntries] = pRightNode; + abState[cEntries] = 0; + cEntries++; + + AssertReturn(cNodesLeft > 0, VERR_HARDAVL_TRAVERSED_TOO_MANY_NODES); + cNodesLeft--; + break; + } + RT_FALL_THROUGH(); + } + + default: + { + /* pop it and destroy it. */ + if (a_pfnCallBack) + a_pfnCallBack(pNode, a_pvUser); + + int rc = a_pAllocator->freeNode(pNode); + AssertRCReturnStmt(rc, m_cErrors++, rc); + + cEntries -= 1; + break; + } + } + } + + Assert(m_idxRoot == a_pAllocator->kNilIndex); + return VINF_SUCCESS; + } + + + /** + * Gets the tree height value (reads cHeigh from the root node). + * + * @retval UINT8_MAX if bogus tree. + */ + uint8_t getHeight(RTCHardAvlTreeSlabAllocator<NodeType> *a_pAllocator) RT_NOEXCEPT + { + NodeType *pNode = a_pAllocator->ptrFromInt(readIdx(&m_idxRoot)); + AssertMsgReturnStmt(a_pAllocator->isPtrRetOkay(pNode), ("m_idxRoot=%#x pNode=%p\n", m_idxRoot, pNode), + m_cErrors++, UINT8_MAX); + if (pNode) + return pNode->cHeight; + return 0; + } + +#ifdef RT_STRICT + + static void dumpStack(RTCHardAvlTreeSlabAllocator<NodeType> *a_pAllocator, HardAvlStack const *pStack) RT_NOEXCEPT + { + uint32_t const * const *paidx = pStack->apidxEntries; + RTAssertMsg2("stack: %u:\n", pStack->cEntries); + for (unsigned i = 0; i < pStack->cEntries; i++) + { + uint32_t idx = *paidx[i]; + uint32_t idxNext = i + 1 < pStack->cEntries ? *paidx[i + 1] : UINT32_MAX; + NodeType const *pNode = a_pAllocator->ptrFromInt(idx); + RTAssertMsg2(" #%02u: %p[%#06x] pNode=%p h=%02d l=%#06x%c r=%#06x%c\n", i, paidx[i], idx, pNode, pNode->cHeight, + pNode->idxLeft, pNode->idxLeft == idxNext ? '*' : ' ', + pNode->idxRight, pNode->idxRight == idxNext ? '*' : ' '); + } + } + + static void printTree(RTCHardAvlTreeSlabAllocator<NodeType> *a_pAllocator, uint32_t a_idxRoot, + unsigned a_uLevel = 0, unsigned a_uMaxLevel = 8, const char *a_pszDir = "") RT_NOEXCEPT + { + if (a_idxRoot == a_pAllocator->kNilIndex) + RTAssertMsg2("%*snil\n", a_uLevel * 6, a_pszDir); + else if (a_uLevel < a_uMaxLevel) + { + NodeType *pNode = a_pAllocator->ptrFromInt(a_idxRoot); + printTree(a_pAllocator, readIdx(&pNode->idxRight), a_uLevel + 1, a_uMaxLevel, "/ "); + RTAssertMsg2("%*s%#x/%u\n", a_uLevel * 6, a_pszDir, a_idxRoot, pNode->cHeight); + printTree(a_pAllocator, readIdx(&pNode->idxLeft), a_uLevel + 1, a_uMaxLevel, "\\ "); + } + else + RTAssertMsg2("%*stoo deep\n", a_uLevel * 6, a_pszDir); + } + +#endif + +private: + /** + * Rewinds a stack of pointers to pointers to nodes, rebalancing the tree. + * + * @returns IPRT status code. + * + * @param a_pAllocator Pointer to the allocator. + * @param a_pStack Pointer to stack to rewind. + * @param a_fLog Log is done (DEBUG builds only). + * + * @code + * LOOP thru all stack entries + * BEGIN + * Get pointer to pointer to node (and pointer to node) from the stack. + * IF 2 higher left subtree than in right subtree THEN + * BEGIN + * IF higher (or equal) left-sub-subtree than right-sub-subtree THEN + * * n+2|n+3 + * / \ / \ + * n+2 n ==> n+1 n+1|n+2 + * / \ / \ + * n+1 n|n+1 n|n+1 n + * + * Or with keys: + * + * 4 2 + * / \ / \ + * 2 5 ==> 1 4 + * / \ / \ + * 1 3 3 5 + * + * ELSE + * * n+2 + * / \ / \ + * n+2 n n+1 n+1 + * / \ ==> / \ / \ + * n n+1 n L R n + * / \ + * L R + * + * Or with keys: + * 6 4 + * / \ / \ + * 2 7 ==> 2 6 + * / \ / \ / \ + * 1 4 1 3 5 7 + * / \ + * 3 5 + * END + * ELSE IF 2 higher in right subtree than in left subtree THEN + * BEGIN + * Same as above but left <==> right. (invert the picture) + * ELSE + * IF correct height THEN break + * ELSE correct height. + * END + * @endcode + * @internal + */ + int i_rebalance(RTCHardAvlTreeSlabAllocator<NodeType> *a_pAllocator, HardAvlStack *a_pStack, bool a_fLog = false) RT_NOEXCEPT + { + RT_NOREF(a_fLog); + + while (a_pStack->cEntries > 0) + { + /* pop */ + uint32_t * const pidxNode = a_pStack->apidxEntries[--a_pStack->cEntries]; + uint32_t const idxNode = readIdx(pidxNode); + NodeType * const pNode = a_pAllocator->ptrFromInt(idxNode); + AssertMsgReturnStmt(a_pAllocator->isPtrRetOkay(pNode), + ("pidxNode=%p[%#x] pNode=%p\n", pidxNode, *pidxNode, pNode), + m_cErrors++, a_pAllocator->ptrErrToStatus(pNode)); + + /* Read node properties: */ + uint32_t const idxLeftNode = readIdx(&pNode->idxLeft); + NodeType * const pLeftNode = a_pAllocator->ptrFromInt(idxLeftNode); + AssertMsgReturnStmt(a_pAllocator->isPtrRetOkay(pLeftNode), + ("idxLeftNode=%#x pLeftNode=%p\n", idxLeftNode, pLeftNode), + m_cErrors++, a_pAllocator->ptrErrToStatus(pLeftNode)); + + uint32_t const idxRightNode = readIdx(&pNode->idxRight); + NodeType * const pRightNode = a_pAllocator->ptrFromInt(idxRightNode); + AssertMsgReturnStmt(a_pAllocator->isPtrRetOkay(pRightNode), + ("idxRight=%#x pRightNode=%p\n", idxRightNode, pRightNode), + m_cErrors++, a_pAllocator->ptrErrToStatus(pRightNode)); + + uint8_t const cLeftHeight = pLeftNode ? pLeftNode->cHeight : 0; + AssertReturnStmt(cLeftHeight <= kMaxHeight, m_cErrors++, VERR_HARDAVL_BAD_LEFT_HEIGHT); + + uint8_t const cRightHeight = pRightNode ? pRightNode->cHeight : 0; + AssertReturnStmt(cRightHeight <= kMaxHeight, m_cErrors++, VERR_HARDAVL_BAD_RIGHT_HEIGHT); + + /* Decide what needs doing: */ + if (cRightHeight + 1 < cLeftHeight) + { + Assert(cRightHeight + 2 == cLeftHeight); + AssertReturnStmt(pLeftNode, m_cErrors++, VERR_HARDAVL_UNEXPECTED_NULL_LEFT); + + uint32_t const idxLeftLeftNode = readIdx(&pLeftNode->idxLeft); + NodeType * const pLeftLeftNode = a_pAllocator->ptrFromInt(idxLeftLeftNode); + AssertMsgReturnStmt(a_pAllocator->isPtrRetOkay(pLeftLeftNode), + ("idxLeftLeftNode=%#x pLeftLeftNode=%p\n", idxLeftLeftNode, pLeftLeftNode), + m_cErrors++, a_pAllocator->ptrErrToStatus(pLeftLeftNode)); + + uint32_t const idxLeftRightNode = readIdx(&pLeftNode->idxRight); + NodeType * const pLeftRightNode = a_pAllocator->ptrFromInt(idxLeftRightNode); + AssertMsgReturnStmt(a_pAllocator->isPtrRetOkay(pLeftRightNode), + ("idxLeftRightNode=%#x pLeftRightNode=%p\n", idxLeftRightNode, pLeftRightNode), + m_cErrors++, a_pAllocator->ptrErrToStatus(pLeftRightNode)); + + uint8_t const cLeftRightHeight = pLeftRightNode ? pLeftRightNode->cHeight : 0; + if ((pLeftLeftNode ? pLeftLeftNode->cHeight : 0) >= cLeftRightHeight) + { + AssertReturnStmt(cLeftRightHeight + 2 <= kMaxHeight, m_cErrors++, VERR_HARDAVL_BAD_NEW_HEIGHT); + pNode->idxLeft = idxLeftRightNode; + pNode->cHeight = (uint8_t)(cLeftRightHeight + 1); + pLeftNode->cHeight = (uint8_t)(cLeftRightHeight + 2); + pLeftNode->idxRight = idxNode; + *pidxNode = idxLeftNode; +#ifdef DEBUG + if (a_fLog) RTAssertMsg2("rebalance: %#2u: op #1\n", a_pStack->cEntries); +#endif + } + else + { + AssertReturnStmt(cLeftRightHeight <= kMaxHeight, m_cErrors++, VERR_HARDAVL_BAD_RIGHT_HEIGHT); + AssertReturnStmt(pLeftRightNode, m_cErrors++, VERR_HARDAVL_UNEXPECTED_NULL_RIGHT); + + uint32_t const idxLeftRightLeftNode = readIdx(&pLeftRightNode->idxLeft); + AssertReturnStmt(a_pAllocator->isIntValid(idxLeftRightLeftNode), m_cErrors++, VERR_HARDAVL_INDEX_OUT_OF_BOUNDS); + uint32_t const idxLeftRightRightNode = readIdx(&pLeftRightNode->idxRight); + AssertReturnStmt(a_pAllocator->isIntValid(idxLeftRightRightNode), m_cErrors++, VERR_HARDAVL_INDEX_OUT_OF_BOUNDS); + pLeftNode->idxRight = idxLeftRightLeftNode; + pNode->idxLeft = idxLeftRightRightNode; + + pLeftRightNode->idxLeft = idxLeftNode; + pLeftRightNode->idxRight = idxNode; + pLeftNode->cHeight = cLeftRightHeight; + pNode->cHeight = cLeftRightHeight; + pLeftRightNode->cHeight = cLeftHeight; + *pidxNode = idxLeftRightNode; +#ifdef DEBUG + if (a_fLog) RTAssertMsg2("rebalance: %#2u: op #2\n", a_pStack->cEntries); +#endif + } + m_cRebalancingOperations++; + } + else if (cLeftHeight + 1 < cRightHeight) + { + Assert(cLeftHeight + 2 == cRightHeight); + AssertReturnStmt(pRightNode, m_cErrors++, VERR_HARDAVL_UNEXPECTED_NULL_RIGHT); + + uint32_t const idxRightLeftNode = readIdx(&pRightNode->idxLeft); + NodeType * const pRightLeftNode = a_pAllocator->ptrFromInt(idxRightLeftNode); + AssertMsgReturnStmt(a_pAllocator->isPtrRetOkay(pRightLeftNode), + ("idxRightLeftNode=%#x pRightLeftNode=%p\n", idxRightLeftNode, pRightLeftNode), + m_cErrors++, a_pAllocator->ptrErrToStatus(pRightLeftNode)); + + uint32_t const idxRightRightNode = readIdx(&pRightNode->idxRight); + NodeType * const pRightRightNode = a_pAllocator->ptrFromInt(idxRightRightNode); + AssertMsgReturnStmt(a_pAllocator->isPtrRetOkay(pRightRightNode), + ("idxRightRightNode=%#x pRightRightNode=%p\n", idxRightRightNode, pRightRightNode), + m_cErrors++, a_pAllocator->ptrErrToStatus(pRightRightNode)); + + uint8_t const cRightLeftHeight = pRightLeftNode ? pRightLeftNode->cHeight : 0; + if ((pRightRightNode ? pRightRightNode->cHeight : 0) >= cRightLeftHeight) + { + AssertReturnStmt(cRightLeftHeight + 2 <= kMaxHeight, m_cErrors++, VERR_HARDAVL_BAD_NEW_HEIGHT); + + pNode->idxRight = idxRightLeftNode; + pRightNode->idxLeft = idxNode; + pNode->cHeight = (uint8_t)(cRightLeftHeight + 1); + pRightNode->cHeight = (uint8_t)(cRightLeftHeight + 2); + *pidxNode = idxRightNode; +#ifdef DEBUG + if (a_fLog) RTAssertMsg2("rebalance: %#2u: op #3 h=%d, *pidxNode=%#x\n", a_pStack->cEntries, pRightNode->cHeight, *pidxNode); +#endif + RTHARDAVL_STRICT_CHECK_HEIGHTS(pRightNode, NULL, 0); + RTHARDAVL_STRICT_CHECK_HEIGHTS(pNode, NULL, 0); + } + else + { + AssertReturnStmt(cRightLeftHeight <= kMaxHeight, m_cErrors++, VERR_HARDAVL_BAD_LEFT_HEIGHT); + AssertReturnStmt(pRightLeftNode, m_cErrors++, VERR_HARDAVL_UNEXPECTED_NULL_LEFT); + + uint32_t const idxRightLeftRightNode = readIdx(&pRightLeftNode->idxRight); + AssertReturnStmt(a_pAllocator->isIntValid(idxRightLeftRightNode), m_cErrors++, VERR_HARDAVL_INDEX_OUT_OF_BOUNDS); + uint32_t const idxRightLeftLeftNode = readIdx(&pRightLeftNode->idxLeft); + AssertReturnStmt(a_pAllocator->isIntValid(idxRightLeftLeftNode), m_cErrors++, VERR_HARDAVL_INDEX_OUT_OF_BOUNDS); + pRightNode->idxLeft = idxRightLeftRightNode; + pNode->idxRight = idxRightLeftLeftNode; + + pRightLeftNode->idxRight = idxRightNode; + pRightLeftNode->idxLeft = idxNode; + pRightNode->cHeight = cRightLeftHeight; + pNode->cHeight = cRightLeftHeight; + pRightLeftNode->cHeight = cRightHeight; + *pidxNode = idxRightLeftNode; +#ifdef DEBUG + if (a_fLog) RTAssertMsg2("rebalance: %#2u: op #4 h=%d, *pidxNode=%#x\n", a_pStack->cEntries, pRightLeftNode->cHeight, *pidxNode); +#endif + } + m_cRebalancingOperations++; + } + else + { + uint8_t const cHeight = (uint8_t)(RT_MAX(cLeftHeight, cRightHeight) + 1); + AssertReturnStmt(cHeight <= kMaxHeight, m_cErrors++, VERR_HARDAVL_BAD_NEW_HEIGHT); + if (cHeight == pNode->cHeight) + { +#ifdef DEBUG + if (a_fLog) RTAssertMsg2("rebalance: %#2u: op #5, h=%d - done\n", a_pStack->cEntries, cHeight); +#endif + RTHARDAVL_STRICT_CHECK_HEIGHTS(pNode, NULL, 0); + if (pLeftNode) + RTHARDAVL_STRICT_CHECK_HEIGHTS(pLeftNode, NULL, 0); + if (pRightNode) + RTHARDAVL_STRICT_CHECK_HEIGHTS(pRightNode, NULL, 0); + break; + } +#ifdef DEBUG + if (a_fLog) RTAssertMsg2("rebalance: %#2u: op #5, h=%d - \n", a_pStack->cEntries, cHeight); +#endif + pNode->cHeight = cHeight; + } + } + return VINF_SUCCESS; + } +}; + +/** @} */ + +#endif /* !IPRT_INCLUDED_cpp_hardavlrange_h */ + diff --git a/include/iprt/cpp/hardavlslaballocator.h b/include/iprt/cpp/hardavlslaballocator.h new file mode 100644 index 00000000..6d42ebc4 --- /dev/null +++ b/include/iprt/cpp/hardavlslaballocator.h @@ -0,0 +1,218 @@ +/** @file + * IPRT - Hardened AVL tree slab allocator. + */ + +/* + * Copyright (C) 2022-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#ifndef IPRT_INCLUDED_cpp_hardavlslaballocator_h +#define IPRT_INCLUDED_cpp_hardavlslaballocator_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include <iprt/asm.h> +#include <iprt/assert.h> +#include <iprt/err.h> +#include <iprt/string.h> + +/** @addtogroup grp_rt_cpp_hardavl + * @{ + */ + + +/** + * Slab allocator for the hardened AVL tree. + */ +template<typename NodeType> +struct RTCHardAvlTreeSlabAllocator +{ + /** Pointer to an array of nodes. */ + NodeType *m_paNodes; + /** Node allocation bitmap: 1 = free, 0 = allocated. */ + uint64_t *m_pbmAlloc; + /** Max number of nodes in m_paNodes and valid bits in m_pbmAlloc. */ + uint32_t m_cNodes; + /** Pointer error counter. */ + uint32_t m_cErrors; + /** Allocation hint. */ + uint32_t m_idxAllocHint; + uint32_t m_uPadding; + + enum + { + kNilIndex = 0, + kErr_IndexOutOfBound = -1, + kErr_PointerOutOfBound = -2, + kErr_MisalignedPointer = -3, + kErr_NodeIsFree = -4, + kErr_Last = kErr_NodeIsFree + }; + + RTCHardAvlTreeSlabAllocator() RT_NOEXCEPT + : m_paNodes(NULL) + , m_pbmAlloc(NULL) + , m_cNodes(0) + , m_cErrors(0) + , m_idxAllocHint(0) + , m_uPadding(0) + {} + + inline void initSlabAllocator(uint32_t a_cNodes, NodeType *a_paNodes, uint64_t *a_pbmAlloc) RT_NOEXCEPT + { + m_cNodes = a_cNodes; + m_paNodes = a_paNodes; + m_pbmAlloc = a_pbmAlloc; + + /* Initialize the allocation bit. */ + RT_BZERO(a_pbmAlloc, (a_cNodes + 63) / 64 * 8); + ASMBitSetRange(a_pbmAlloc, 0, a_cNodes); + } + + inline NodeType *ptrFromInt(uint32_t a_idxNode1) RT_NOEXCEPT + { + if (a_idxNode1 == (uint32_t)kNilIndex) + return NULL; + AssertMsgReturnStmt(a_idxNode1 <= m_cNodes, ("a_idxNode1=%#x m_cNodes=%#x\n", a_idxNode1, m_cNodes), + m_cErrors++, (NodeType *)(intptr_t)kErr_IndexOutOfBound); + AssertMsgReturnStmt(ASMBitTest(m_pbmAlloc, a_idxNode1 - 1) == false, ("a_idxNode1=%#x\n", a_idxNode1), + m_cErrors++, (NodeType *)(intptr_t)kErr_NodeIsFree); + return &m_paNodes[a_idxNode1 - 1]; + } + + static inline bool isPtrRetOkay(NodeType *a_pNode) RT_NOEXCEPT + { + return (uintptr_t)a_pNode < (uintptr_t)kErr_Last; + } + + static inline int ptrErrToStatus(NodeType *a_pNode) RT_NOEXCEPT + { + return (int)(intptr_t)a_pNode - (VERR_HARDAVL_INDEX_OUT_OF_BOUNDS - kErr_IndexOutOfBound); + } + + inline uint32_t ptrToInt(NodeType *a_pNode) RT_NOEXCEPT + { + if (a_pNode == NULL) + return 0; + uintptr_t const offNode = (uintptr_t)a_pNode - (uintptr_t)m_paNodes; + uintptr_t const idxNode0 = offNode / sizeof(m_paNodes[0]); + AssertMsgReturnStmt((offNode % sizeof(m_paNodes[0])) == 0, + ("pNode=%p / offNode=%#zx vs m_paNodes=%p L %#x, each %#x bytes\n", + a_pNode, offNode, m_paNodes, m_cNodes, sizeof(m_paNodes[0])), + m_cErrors++, (uint32_t)kErr_MisalignedPointer); + AssertMsgReturnStmt(idxNode0 < m_cNodes, + ("pNode=%p vs m_paNodes=%p L %#x\n", a_pNode, m_paNodes, m_cNodes), + m_cErrors++, (uint32_t)kErr_PointerOutOfBound); + AssertMsgReturnStmt(ASMBitTest(m_pbmAlloc, idxNode0) == false, ("a_pNode=%p idxNode0=%#x\n", a_pNode, idxNode0), + m_cErrors++, (uint32_t)kErr_NodeIsFree); + return idxNode0 + 1; + } + + static inline bool isIdxRetOkay(uint32_t a_idxNode) RT_NOEXCEPT + { + return a_idxNode < (uint32_t)kErr_Last; + } + + static inline int idxErrToStatus(uint32_t a_idxNode) RT_NOEXCEPT + { + return (int)a_idxNode - (VERR_HARDAVL_INDEX_OUT_OF_BOUNDS - kErr_IndexOutOfBound); + } + + inline bool isIntValid(uint32_t a_idxNode1) RT_NOEXCEPT + { + return a_idxNode1 <= m_cNodes; + } + + inline int freeNode(NodeType *a_pNode) RT_NOEXCEPT + { + uint32_t idxNode1 = ptrToInt(a_pNode); + if (idxNode1 == (uint32_t)kNilIndex) + return 0; + if (idxNode1 < (uint32_t)kErr_Last) + { + AssertMsgReturnStmt(ASMAtomicBitTestAndSet(m_pbmAlloc, idxNode1 - 1) == false, + ("a_pNode=%p idxNode1=%#x\n", a_pNode, idxNode1), + m_cErrors++, kErr_NodeIsFree); + return 0; + } + return (int)idxNode1; + } + + inline NodeType *allocateNode(void) RT_NOEXCEPT + { + /* + * Use the hint first, then scan the whole bitmap. + * Note! We don't expect concurrent allocation calls, so no need to repeat. + */ + uint32_t const idxHint = m_idxAllocHint; + uint32_t idxNode0; + if ( idxHint >= m_cNodes + || (int32_t)(idxNode0 = (uint32_t)ASMBitNextSet(m_pbmAlloc, m_cNodes, idxHint)) < 0) + idxNode0 = (uint32_t)ASMBitFirstSet(m_pbmAlloc, m_cNodes); + if ((int32_t)idxNode0 >= 0) + { + if (ASMAtomicBitTestAndClear(m_pbmAlloc, idxNode0) == true) + { + m_idxAllocHint = idxNode0; + return &m_paNodes[idxNode0]; + } + AssertMsgFailed(("idxNode0=%#x\n", idxNode0)); + m_cErrors++; + } + return NULL; + } +}; + + +/** + * Placeholder structure for ring-3 slab allocator. + */ +typedef struct RTCHardAvlTreeSlabAllocatorR3_T +{ + /** Pointer to an array of nodes. */ + RTR3PTR m_paNodes; + /** Node allocation bitmap: 1 = free, 0 = allocated. */ + RTR3PTR m_pbmAlloc; + /** Max number of nodes in m_paNodes and valid bits in m_pbmAlloc. */ + uint32_t m_cNodes; + /** Pointer error counter. */ + uint32_t m_cErrors; + /** Allocation hint. */ + uint32_t m_idxAllocHint; + uint32_t m_uPadding; +} RTCHardAvlTreeSlabAllocatorR3_T; +AssertCompileSize(RTCHardAvlTreeSlabAllocatorR3_T, + sizeof(RTCHardAvlTreeSlabAllocator<RTUINT128U>) - (sizeof(void *) - sizeof(RTR3PTR)) * 2); + +/** @} */ + +#endif /* !IPRT_INCLUDED_cpp_hardavlslaballocator_h */ + diff --git a/include/iprt/cpp/list.h b/include/iprt/cpp/list.h new file mode 100644 index 00000000..dc0fe5e6 --- /dev/null +++ b/include/iprt/cpp/list.h @@ -0,0 +1,1143 @@ +/** @file + * IPRT - Generic List Class. + */ + +/* + * Copyright (C) 2011-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#ifndef IPRT_INCLUDED_cpp_list_h +#define IPRT_INCLUDED_cpp_list_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include <iprt/cpp/meta.h> +#include <iprt/mem.h> +#include <iprt/string.h> /* for memcpy */ +#include <iprt/assert.h> + +#include <new> /* For std::bad_alloc */ + +/** @defgroup grp_rt_cpp_list C++ List support + * @ingroup grp_rt_cpp + * + * @brief Generic C++ list class support. + * + * This list classes manage any amount of data in a fast and easy to use way. + * They have no dependencies on STL, only on generic memory management methods + * of IRPT. This allows list handling in situations where the use of STL + * container classes is forbidden. + * + * Not all of the functionality of STL container classes is implemented. There + * are no iterators or any other high level access/modifier methods (e.g. + * std::algorithms). + * + * The implementation is array based which allows fast access to the items. + * Appending items is usually also fast, cause the internal array is + * preallocated. To minimize the memory overhead, native types (that is + * everything smaller then the size of void*) are directly saved in the array. + * If bigger types are used (e.g. RTCString) the internal array is an array of + * pointers to the objects. + * + * The size of the internal array will usually not shrink, but grow + * automatically. Only certain methods, like RTCList::clear or the "=" operator + * will reset any previously allocated memory. You can call + * RTCList::setCapacity for manual adjustment. If the size of an new list will + * be known, calling the constructor with the necessary capacity will speed up + * the insertion of the new items. + * + * For the full public interface these list classes offer see RTCListBase. + * + * There are some requirements for the types used which follow: + * -# They need a default and a copy constructor. + * -# Some methods (e.g. RTCList::contains) need an equal operator. + * -# If the type is some complex class (that is, having a constructor which + * allocates members on the heap) it has to be greater than sizeof(void*) to + * be used correctly. If this is not the case you can manually overwrite the + * list behavior. Just add T* as a second parameter to the list template if + * your class is called T. Another possibility is to specialize the list for + * your target class. See below for more information. + * + * The native types like int, bool, ptr, ..., are meeting this criteria, so + * they are save to use. + * + * Please note that the return type of some of the getter methods are slightly + * different depending on the list type. Native types return the item by value, + * items with a size greater than sizeof(void*) by reference. As native types + * saved directly in the internal array, returning a reference to them (and + * saving them in a reference as well) would make them invalid (or pointing to + * a wrong item) when the list is changed in the meanwhile. Returning a + * reference for bigger types isn't problematic and makes sure we get out the + * best speed of the list. The one exception to this rule is the index + * operator[]. This operator always return a reference to make it possible to + * use it as a lvalue. Its your responsibility to make sure the list isn't + * changed when using the value as reference returned by this operator. + * + * The list class is reentrant. For a thread-safe variant see RTCMTList. + * + * Implementation details: + * It is possible to specialize any type. This might be necessary to get the + * best speed out of the list. Examples are the 64-bit types, which use the + * native (no pointers) implementation even on a 32-bit host. Consult the + * source code for more details. + * + * Current specialized implementations: + * - int64_t: RTCList<int64_t> + * - uint64_t: RTCList<uint64_t> + * + * @{ + */ + +/** + * The guard definition. + */ +template <bool G> +class RTCListGuard; + +/** + * The default guard which does nothing. + */ +template <> +class RTCListGuard<false> +{ +public: + inline void enterRead() const {} + inline void leaveRead() const {} + inline void enterWrite() {} + inline void leaveWrite() {} + + /* Define our own new and delete. */ +#ifdef RT_NEED_NEW_AND_DELETE + RTMEM_IMPLEMENT_NEW_AND_DELETE(); +#else + RTMEMEF_NEW_AND_DELETE_OPERATORS(); +#endif +}; + +/** + * General helper template for managing native values in RTCListBase. + */ +template <typename T1, typename T2> +class RTCListHelper +{ +public: + static inline void set(T2 *p, size_t i, const T1 &v) { p[i] = v; } + static inline T1 & at(T2 *p, size_t i) { return p[i]; } + static inline const T1 &atConst(T2 const *p, size_t i) { return p[i]; } + static inline size_t find(T2 *p, const T1 &v, size_t cElements) + { + size_t i = cElements; + while (i-- > 0) + if (p[i] == v) + return i; + return cElements; + } + static inline void copyTo(T2 *p, T2 *const p1 , size_t iTo, size_t cSize) + { + if (cSize > 0) + memcpy(&p[iTo], &p1[0], sizeof(T1) * cSize); + } + static inline void erase(T2 * /* p */, size_t /* i */) { /* Nothing to do here. */ } + static inline void eraseRange(T2 * /* p */, size_t /* cFrom */, size_t /* cSize */) { /* Nothing to do here. */ } +}; + +/** + * Specialized helper template for managing pointer values in RTCListBase. + */ +template <typename T1> +class RTCListHelper<T1, T1*> +{ +public: + static inline void set(T1 **p, size_t i, const T1 &v) { p[i] = new T1(v); } + static inline T1 & at(T1 **p, size_t i) { return *p[i]; } + static inline const T1 &atConst(T1 * const *p, size_t i) { return *p[i]; } + static inline size_t find(T1 **p, const T1 &v, size_t cElements) + { + size_t i = cElements; + while (i-- > 0) + if (*p[i] == v) + return i; + return cElements; + } + static inline void copyTo(T1 **p, T1 **const p1 , size_t iTo, size_t cSize) + { + for (size_t i = 0; i < cSize; ++i) + p[iTo + i] = new T1(*p1[i]); + } + static inline void erase(T1 **p, size_t i) { delete p[i]; } + static inline void eraseRange(T1 **p, size_t iFrom, size_t cItems) + { + while (cItems-- > 0) + delete p[iFrom++]; + } +}; + +/** + * This is the base class for all other list classes. It implements the + * necessary list functionality in a type independent way and offers the public + * list interface to the user. + */ +template <class T, typename ITYPE, bool MT> +class RTCListBase +{ + /** @name Traits. + * + * Defines the return type of most of the getter methods. If the internal + * used type is a pointer, we return a reference. If not we return by + * value. + * + * @{ + */ + typedef typename RTCIfPtr<ITYPE, T&, T>::result GET_RTYPE; + typedef typename RTCIfPtr<ITYPE, const T&, T>::result GET_CRTYPE; + /** @} */ + +public: + /** + * Creates a new list. + * + * This preallocates @a cCapacity elements within the list. + * + * @param cCapacity The initial capacity the list has. + * @throws std::bad_alloc + */ + RTCListBase(size_t cCapacity = kDefaultCapacity) + : m_pArray(0) + , m_cElements(0) + , m_cCapacity(0) + { + if (cCapacity > 0) + growArray(cCapacity); + } + + /** + * Creates a copy of another list. + * + * The other list will be fully copied and the capacity will be the same as + * the size of the other list. + * + * @param other The list to copy. + * @throws std::bad_alloc + */ + RTCListBase(const RTCListBase<T, ITYPE, MT>& other) + : m_pArray(0) + , m_cElements(0) + , m_cCapacity(0) + { + other.m_guard.enterRead(); + + size_t const cElementsOther = other.m_cElements; + resizeArrayNoErase(cElementsOther); + RTCListHelper<T, ITYPE>::copyTo(m_pArray, other.m_pArray, 0, cElementsOther); + m_cElements = cElementsOther; + + other.m_guard.leaveRead(); + } + + /** + * Destructor. + */ + ~RTCListBase() + { + RTCListHelper<T, ITYPE>::eraseRange(m_pArray, 0, m_cElements); + if (m_pArray) + { + RTMemFree(m_pArray); + m_pArray = NULL; + } + m_cElements = m_cCapacity = 0; + } + + /** + * Sets a new capacity within the list. + * + * If the new capacity is bigger than the old size, it will be simply + * preallocated more space for the new items. If the new capacity is + * smaller than the previous size, items at the end of the list will be + * deleted. + * + * @param cCapacity The new capacity within the list. + * @throws std::bad_alloc + */ + void setCapacity(size_t cCapacity) + { + m_guard.enterWrite(); + resizeArray(cCapacity); + m_guard.leaveWrite(); + } + + /** + * Return the current capacity of the list. + * + * @return The actual capacity. + */ + size_t capacity() const + { + m_guard.enterRead(); + size_t cRet = m_cCapacity; + m_guard.leaveRead(); + return cRet; + } + + /** + * Check if an list contains any items. + * + * @return True if there is more than zero items, false otherwise. + */ + bool isEmpty() const + { + m_guard.enterRead(); + bool fEmpty = m_cElements == 0; + m_guard.leaveRead(); + return fEmpty; + } + + /** + * Return the current count of elements within the list. + * + * @return The current element count. + */ + size_t size() const + { + m_guard.enterRead(); + size_t cRet = m_cElements; + m_guard.leaveRead(); + return cRet; + } + + /** + * Inserts an item to the list at position @a i. + * + * @param i The position of the new item. The must be within or at the + * exact end of the list. Indexes specified beyond the end of + * the list will be changed to an append() operation and strict + * builds will raise an assert. + * @param val The new item. + * @return a reference to this list. + * @throws std::bad_alloc + */ + RTCListBase<T, ITYPE, MT> &insert(size_t i, const T &val) + { + m_guard.enterWrite(); + + AssertMsgStmt(i <= m_cElements, ("i=%zu m_cElements=%zu\n", i, m_cElements), i = m_cElements); + + if (m_cElements == m_cCapacity) + growArray(m_cCapacity + kDefaultCapacity); + + memmove(&m_pArray[i + 1], &m_pArray[i], (m_cElements - i) * sizeof(ITYPE)); + RTCListHelper<T, ITYPE>::set(m_pArray, i, val); + ++m_cElements; + + m_guard.leaveWrite(); + return *this; + } + + /** + * Inserts a list to the list at position @a i. + * + * @param i The position of the new item. The must be within or at the + * exact end of the list. Indexes specified beyond the end of + * the list will be changed to an append() operation and strict + * builds will raise an assert. + * @param other The other list. This MUST not be the same as the destination + * list, will assert and return without doing anything if this + * happens. + * @return a reference to this list. + * @throws std::bad_alloc + */ + RTCListBase<T, ITYPE, MT> &insert(size_t i, const RTCListBase<T, ITYPE, MT> &other) + { + AssertReturn(this != &other, *this); + + other.m_guard.enterRead(); + m_guard.enterWrite(); + + AssertMsgStmt(i <= m_cElements, ("i=%zu m_cElements=%zu\n", i, m_cElements), i = m_cElements); + + size_t cElementsOther = other.m_cElements; + if (RT_LIKELY(cElementsOther > 0)) + { + if (m_cCapacity - m_cElements < cElementsOther) + growArray(m_cCapacity + (cElementsOther - (m_cCapacity - m_cElements))); + if (i < m_cElements) + memmove(&m_pArray[i + cElementsOther], &m_pArray[i], (m_cElements - i) * sizeof(ITYPE)); + + RTCListHelper<T, ITYPE>::copyTo(&m_pArray[i], other.m_pArray, 0, cElementsOther); + m_cElements += cElementsOther; + } + + m_guard.leaveWrite(); + other.m_guard.leaveRead(); + return *this; + } + + /** + * Prepend an item to the list. + * + * @param val The new item. + * @return a reference to this list. + * @throws std::bad_alloc + */ + RTCListBase<T, ITYPE, MT> &prepend(const T &val) + { + return insert(0, val); + } + + /** + * Prepend a list of type T to the list. + * + * @param other The list to prepend. + * @return a reference to this list. + * @throws std::bad_alloc + */ + RTCListBase<T, ITYPE, MT> &prepend(const RTCListBase<T, ITYPE, MT> &other) + { + return insert(0, other); + } + + /** + * Append a default item to the list. + * + * @return a mutable reference to the item + * @throws std::bad_alloc + */ + GET_RTYPE append() + { + m_guard.enterWrite(); + if (m_cElements == m_cCapacity) + growArray(m_cCapacity + kDefaultCapacity); + RTCListHelper<T, ITYPE>::set(m_pArray, m_cElements, T()); + GET_RTYPE rRet = RTCListHelper<T, ITYPE>::at(m_pArray, m_cElements); + ++m_cElements; + m_guard.leaveWrite(); + + return rRet; + } + + /** + * Append an item to the list. + * + * @param val The new item. + * @return a reference to this list. + * @throws std::bad_alloc + */ + RTCListBase<T, ITYPE, MT> &append(const T &val) + { + m_guard.enterWrite(); + if (m_cElements == m_cCapacity) + growArray(m_cCapacity + kDefaultCapacity); + RTCListHelper<T, ITYPE>::set(m_pArray, m_cElements, val); + ++m_cElements; + m_guard.leaveWrite(); + + return *this; + } + + /** + * Append a list of type T to the list. + * + * @param other The list to append. Must not be the same as the destination + * list, will assert and return without doing anything. + * @return a reference to this list. + * @throws std::bad_alloc + */ + RTCListBase<T, ITYPE, MT> &append(const RTCListBase<T, ITYPE, MT> &other) + { + AssertReturn(this != &other, *this); + + other.m_guard.enterRead(); + m_guard.enterWrite(); + + insert(m_cElements, other); + + m_guard.leaveWrite(); + other.m_guard.leaveRead(); + return *this; + } + + /** + * Copy the items of the other list into this list. + * + * All previous items of this list are deleted. + * + * @param other The list to copy. + * @return a reference to this list. + */ + RTCListBase<T, ITYPE, MT> &operator=(const RTCListBase<T, ITYPE, MT>& other) + { + /* Prevent self assignment */ + if (RT_LIKELY(this != &other)) + { + other.m_guard.enterRead(); + m_guard.enterWrite(); + + /* Delete all items. */ + RTCListHelper<T, ITYPE>::eraseRange(m_pArray, 0, m_cElements); + + /* Need we to realloc memory. */ + if (other.m_cElements != m_cCapacity) + resizeArrayNoErase(other.m_cElements); + m_cElements = other.m_cElements; + + /* Copy new items. */ + RTCListHelper<T, ITYPE>::copyTo(m_pArray, other.m_pArray, 0, other.m_cElements); + + m_guard.leaveWrite(); + other.m_guard.leaveRead(); + } + return *this; + } + + /** + * Compares if this list's items match the other list. + * + * @returns \c true if both lists contain the same items, \c false if not. + * @param other The list to compare this list with. + */ + bool operator==(const RTCListBase<T, ITYPE, MT>& other) + { + /* Prevent self comparrison */ + if (RT_LIKELY(this == &other)) + return true; + + other.m_guard.enterRead(); + m_guard.enterRead(); + + bool fEqual = true; + if (other.m_cElements == m_cElements) + { + for (size_t i = 0; i < m_cElements; i++) + { + if (RTCListHelper<T, ITYPE>::at(m_pArray, i) != RTCListHelper<T, ITYPE>::at(other.m_pArray, i)) + { + fEqual = false; + break; + } + } + } + else + fEqual = false; + + m_guard.leaveRead(); + other.m_guard.leaveRead(); + + return fEqual; + } + + /** + * Compares if this list's items do not match the other list. + * + * @returns \c true if the lists do not match, \c false if otherwise. + * @param other The list to compare this list with. + */ + bool operator!=(const RTCListBase<T, ITYPE, MT>& other) + { + return !(*this == other); + } + + /** + * Replace an item in the list. + * + * @param i The position of the item to replace. If this is out of range, + * the request will be ignored, strict builds will assert. + * @param val The new value. + * @return a reference to this list. + */ + RTCListBase<T, ITYPE, MT> &replace(size_t i, const T &val) + { + m_guard.enterWrite(); + + if (i < m_cElements) + { + RTCListHelper<T, ITYPE>::erase(m_pArray, i); + RTCListHelper<T, ITYPE>::set(m_pArray, i, val); + } + else + AssertMsgFailed(("i=%zu m_cElements=%zu\n", i, m_cElements)); + + m_guard.leaveWrite(); + return *this; + } + + /** + * Applies a filter of type T to this list. + * + * @param other The list which contains the elements to filter out from this list. + * @return a reference to this list. + */ + RTCListBase<T, ITYPE, MT> &filter(const RTCListBase<T, ITYPE, MT> &other) + { + AssertReturn(this != &other, *this); + + other.m_guard.enterRead(); + m_guard.enterWrite(); + + for (size_t i = 0; i < m_cElements; i++) + { + for (size_t f = 0; f < other.m_cElements; f++) + { + if (RTCListHelper<T, ITYPE>::at(m_pArray, i) == RTCListHelper<T, ITYPE>::at(other.m_pArray, f)) + removeAtLocked(i); + } + } + + m_guard.leaveWrite(); + other.m_guard.leaveRead(); + return *this; + } + + /** + * Return the first item as constant object. + * + * @return A reference or pointer to the first item. + * + * @note No boundary checks are done. Make sure there is at least one + * element. + */ + GET_CRTYPE first() const + { + m_guard.enterRead(); + Assert(m_cElements > 0); + GET_CRTYPE res = RTCListHelper<T, ITYPE>::at(m_pArray, 0); + m_guard.leaveRead(); + return res; + } + + /** + * Return the first item. + * + * @return A reference or pointer to the first item. + * + * @note No boundary checks are done. Make sure there is at least one + * element. + */ + GET_RTYPE first() + { + m_guard.enterRead(); + Assert(m_cElements > 0); + GET_RTYPE res = RTCListHelper<T, ITYPE>::at(m_pArray, 0); + m_guard.leaveRead(); + return res; + } + + /** + * Return the last item as constant object. + * + * @return A reference or pointer to the last item. + * + * @note No boundary checks are done. Make sure there is at least one + * element. + */ + GET_CRTYPE last() const + { + m_guard.enterRead(); + Assert(m_cElements > 0); + GET_CRTYPE res = RTCListHelper<T, ITYPE>::at(m_pArray, m_cElements - 1); + m_guard.leaveRead(); + return res; + } + + /** + * Return the last item. + * + * @return A reference or pointer to the last item. + * + * @note No boundary checks are done. Make sure there is at least one + * element. + */ + GET_RTYPE last() + { + m_guard.enterRead(); + Assert(m_cElements > 0); + GET_RTYPE res = RTCListHelper<T, ITYPE>::at(m_pArray, m_cElements - 1); + m_guard.leaveRead(); + return res; + } + + /** + * Return the item at position @a i as constant object. + * + * @param i The position of the item to return. This better not be out of + * bounds, however should it be the last element of the array + * will be return and strict builds will raise an assertion. + * Should the array be empty, a crash is very likely. + * @return The item at position @a i. + */ + GET_CRTYPE at(size_t i) const + { + m_guard.enterRead(); + AssertMsgStmt(i < m_cElements, ("i=%zu m_cElements=%zu\n", i, m_cElements), i = m_cElements - 1); + GET_CRTYPE res = RTCListHelper<T, ITYPE>::at(m_pArray, i); + m_guard.leaveRead(); + return res; + } + + /** + * Return the item at position @a i. + * + * @param i The position of the item to return. This better not be out of + * bounds, however should it be the last element of the array + * will be return and strict builds will raise an assertion. + * Should the array be empty, a crash is very likely. + * @return The item at position @a i. + */ + GET_RTYPE at(size_t i) + { + m_guard.enterRead(); + AssertMsgStmt(i < m_cElements, ("i=%zu m_cElements=%zu\n", i, m_cElements), i = m_cElements - 1); + GET_RTYPE res = RTCListHelper<T, ITYPE>::at(m_pArray, i); + m_guard.leaveRead(); + return res; + } + + /** + * Return the item at position @a i as mutable reference. + * + * @param i The position of the item to return. This better not be out of + * bounds, however should it be the last element of the array + * will be return and strict builds will raise an assertion. + * Should the array be empty, a crash is very likely. + * @return The item at position @a i. + */ + T &operator[](size_t i) + { + m_guard.enterRead(); + AssertMsgStmt(i < m_cElements, ("i=%zu m_cElements=%zu\n", i, m_cElements), i = m_cElements - 1); + T &res = RTCListHelper<T, ITYPE>::at(m_pArray, i); + m_guard.leaveRead(); + return res; + } + + /** + * Return the item at position @a i as immutable reference. + * + * @param i The position of the item to return. This better not be out of + * bounds, however should it be the last element of the array + * will be return and strict builds will raise an assertion. + * Should the array be empty, a crash is very likely. + * @return The item at position @a i. + */ + const T &operator[](size_t i) const + { + m_guard.enterRead(); + AssertMsgStmt(i < m_cElements, ("i=%zu m_cElements=%zu\n", i, m_cElements), i = m_cElements - 1); + const T &rRet = RTCListHelper<T, ITYPE>::atConst(m_pArray, i); + m_guard.leaveRead(); + return rRet; + } + + /** + * Return a copy of the item at position @a i or default value if out of range. + * + * @param i The position of the item to return. + * @return Copy of the item at position @a i or default value. + */ + T value(size_t i) const + { + m_guard.enterRead(); + if (RT_LIKELY(i < m_cElements)) + { + T res = RTCListHelper<T, ITYPE>::at(m_pArray, i); + m_guard.leaveRead(); + return res; + } + m_guard.leaveRead(); + return T(); + } + + /** + * Return a copy of the item at position @a i, or @a defaultVal if out of range. + * + * @param i The position of the item to return. + * @param defaultVal The value to return in case @a i is invalid. + * @return Copy of the item at position @a i or @a defaultVal. + */ + T value(size_t i, const T &defaultVal) const + { + m_guard.enterRead(); + if (RT_LIKELY(i < m_cElements)) + { + T res = RTCListHelper<T, ITYPE>::at(m_pArray, i); + m_guard.leaveRead(); + return res; + } + m_guard.leaveRead(); + return defaultVal; + } + + /** + * Check if @a val is contained in the array. + * + * @param val The value to check for. + * @return true if it is found, false otherwise. + */ + bool contains(const T &val) const + { + m_guard.enterRead(); + bool fRc = RTCListHelper<T, ITYPE>::find(m_pArray, val, m_cElements) < m_cElements; + m_guard.leaveRead(); + return fRc; + } + + /** + * Remove the first item. + * + * @note You should make sure the list isn't empty. Strict builds will assert. + * The other builds will quietly ignore the request. + */ + void removeFirst() + { + removeAt(0); + } + + /** + * Remove the last item. + * + * @note You should make sure the list isn't empty. Strict builds will assert. + * The other builds will quietly ignore the request. + */ + void removeLast() + { + m_guard.enterWrite(); + removeAtLocked(m_cElements - 1); + m_guard.leaveWrite(); + } + + /** + * Remove the item at position @a i. + * + * @param i The position of the item to remove. Out of bounds values will + * be ignored and an assertion will be raised in strict builds. + */ + void removeAt(size_t i) + { + m_guard.enterWrite(); + removeAtLocked(i); + m_guard.leaveWrite(); + } + + /** + * Remove a range of items from the list. + * + * @param iStart The start position of the items to remove. + * @param iEnd The end position of the items to remove (excluded). + */ + void removeRange(size_t iStart, size_t iEnd) + { + AssertReturnVoid(iStart <= iEnd); + m_guard.enterWrite(); + + AssertMsgStmt(iEnd <= m_cElements, ("iEnd=%zu m_cElements=%zu\n", iEnd, m_cElements), iEnd = m_cElements); + AssertMsgStmt(iStart < m_cElements, ("iStart=%zu m_cElements=%zu\n", iStart, m_cElements), iStart = m_cElements); + size_t const cElements = iEnd - iStart; + if (cElements > 0) + { + Assert(iStart < m_cElements); + RTCListHelper<T, ITYPE>::eraseRange(m_pArray, iStart, cElements); + if (m_cElements > iEnd) + memmove(&m_pArray[iStart], &m_pArray[iEnd], (m_cElements - iEnd) * sizeof(ITYPE)); + m_cElements -= cElements; + } + + m_guard.leaveWrite(); + } + + /** + * Delete all items in the list. + */ + void clear() + { + m_guard.enterWrite(); + + /* Values cleanup */ + RTCListHelper<T, ITYPE>::eraseRange(m_pArray, 0, m_cElements); + if (m_cElements != kDefaultCapacity) + resizeArrayNoErase(kDefaultCapacity); + m_cElements = 0; + + m_guard.leaveWrite(); + } + + /** + * Return the raw array. + * + * For native types this is a pointer to continuous memory of the items. For + * pointer types this is a continuous memory of pointers to the items. + * + * @warning If you change anything in the underlaying list, this memory + * will very likely become invalid. So take care when using this + * method and better try to avoid using it. + * + * @returns the raw memory. + */ + ITYPE *raw() const + { + m_guard.enterRead(); + ITYPE *pRet = m_pArray; + m_guard.leaveRead(); + return pRet; + } + + RTCListBase<T, ITYPE, MT> &operator<<(const T &val) + { + return append(val); + } + + /* Define our own new and delete. */ +#ifdef RT_NEED_NEW_AND_DELETE + RTMEM_IMPLEMENT_NEW_AND_DELETE(); +#else + RTMEMEF_NEW_AND_DELETE_OPERATORS(); +#endif + + /** + * The default capacity of the list. This is also used as grow factor. + */ + static const size_t kDefaultCapacity; + +protected: + + /** + * Generic resizes the array, surplus elements are erased. + * + * @param cElementsNew The new array size. + * @throws std::bad_alloc. + */ + void resizeArray(size_t cElementsNew) + { + /* Same size? */ + if (cElementsNew == m_cCapacity) + return; + + /* If we get smaller we have to delete some of the objects at the end + of the list. */ + if ( cElementsNew < m_cElements + && m_pArray) + RTCListHelper<T, ITYPE>::eraseRange(m_pArray, cElementsNew, m_cElements - cElementsNew); + + resizeArrayNoErase(cElementsNew); + } + + /** + * Resizes the array without doing the erase() thing on surplus elements. + * + * @param cElementsNew The new array size. + * @throws std::bad_alloc. + */ + void resizeArrayNoErase(size_t cElementsNew) + { + /* Same size? */ + if (cElementsNew == m_cCapacity) + return; + + /* Resize the array. */ + if (cElementsNew > 0) + { + void *pvNew = RTMemRealloc(m_pArray, sizeof(ITYPE) * cElementsNew); + if (!pvNew) + { +#ifdef RT_EXCEPTIONS_ENABLED + throw std::bad_alloc(); +#endif + return; + } + m_pArray = static_cast<ITYPE*>(pvNew); + } + /* If we get zero we delete the array it self. */ + else if (m_pArray) + { + RTMemFree(m_pArray); + m_pArray = NULL; + } + + m_cCapacity = cElementsNew; + if (m_cElements > cElementsNew) + m_cElements = cElementsNew; + } + + /** + * Special realloc method which require that the array will grow. + * + * @param cElementsNew The new array size. + * @throws std::bad_alloc. + * @note No boundary checks are done! + */ + void growArray(size_t cElementsNew) + { + Assert(cElementsNew > m_cCapacity); + void *pvNew = RTMemRealloc(m_pArray, sizeof(ITYPE) * cElementsNew); + if (pvNew) + { + m_cCapacity = cElementsNew; + m_pArray = static_cast<ITYPE*>(pvNew); + } + else + { +#ifdef RT_EXCEPTIONS_ENABLED + throw std::bad_alloc(); +#endif + } + } + + /** + * Remove the item at position @a i. + * + * @param i The position of the item to remove. Out of bounds values will + * be ignored and an assertion will be raised in strict builds. + * @remarks + */ + void removeAtLocked(size_t i) + { + AssertMsgReturnVoid(i < m_cElements, ("i=%zu m_cElements=%zu\n", i, m_cElements)); + + RTCListHelper<T, ITYPE>::erase(m_pArray, i); + if (i < m_cElements - 1) + memmove(&m_pArray[i], &m_pArray[i + 1], (m_cElements - i - 1) * sizeof(ITYPE)); + --m_cElements; + } + + + /** The internal list array. */ + ITYPE *m_pArray; + /** The current count of items in use. */ + size_t m_cElements; + /** The current capacity of the internal array. */ + size_t m_cCapacity; + /** The guard used to serialize the access to the items. */ + RTCListGuard<MT> m_guard; +}; + +template <class T, typename ITYPE, bool MT> +const size_t RTCListBase<T, ITYPE, MT>::kDefaultCapacity = 10; + +/** + * Template class which automatically determines the type of list to use. + * + * @see RTCListBase + */ +template <class T, typename ITYPE = typename RTCIf<(sizeof(T) > sizeof(void*)), T*, T>::result> +class RTCList : public RTCListBase<T, ITYPE, false> +{ + /* Traits */ + typedef RTCListBase<T, ITYPE, false> BASE; + +public: + /** + * Creates a new list. + * + * This preallocates @a cCapacity elements within the list. + * + * @param cCapacity The initial capacity the list has. + * @throws std::bad_alloc + */ + RTCList(size_t cCapacity = BASE::kDefaultCapacity) + : BASE(cCapacity) {} + + RTCList(const BASE &other) + : BASE(other) {} + + /* Define our own new and delete. */ +#ifdef RT_NEED_NEW_AND_DELETE + RTMEM_IMPLEMENT_NEW_AND_DELETE(); +#else + RTMEMEF_NEW_AND_DELETE_OPERATORS(); +#endif +}; + +/** + * Specialized class for using the native type list for unsigned 64-bit + * values even on a 32-bit host. + * + * @see RTCListBase + */ +template <> +class RTCList<uint64_t>: public RTCListBase<uint64_t, uint64_t, false> +{ + /* Traits */ + typedef RTCListBase<uint64_t, uint64_t, false> BASE; + +public: + /** + * Creates a new list. + * + * This preallocates @a cCapacity elements within the list. + * + * @param cCapacity The initial capacity the list has. + * @throws std::bad_alloc + */ + RTCList(size_t cCapacity = BASE::kDefaultCapacity) + : BASE(cCapacity) {} + + /* Define our own new and delete. */ +#ifdef RT_NEED_NEW_AND_DELETE + RTMEM_IMPLEMENT_NEW_AND_DELETE(); +#else + RTMEMEF_NEW_AND_DELETE_OPERATORS(); +#endif +}; + +/** + * Specialized class for using the native type list for signed 64-bit + * values even on a 32-bit host. + * + * @see RTCListBase + */ +template <> +class RTCList<int64_t>: public RTCListBase<int64_t, int64_t, false> +{ + /* Traits */ + typedef RTCListBase<int64_t, int64_t, false> BASE; + +public: + /** + * Creates a new list. + * + * This preallocates @a cCapacity elements within the list. + * + * @param cCapacity The initial capacity the list has. + * @throws std::bad_alloc + */ + RTCList(size_t cCapacity = BASE::kDefaultCapacity) + : BASE(cCapacity) {} + + /* Define our own new and delete. */ +#ifdef RT_NEED_NEW_AND_DELETE + RTMEM_IMPLEMENT_NEW_AND_DELETE(); +#else + RTMEMEF_NEW_AND_DELETE_OPERATORS(); +#endif +}; + +/** @} */ + +#endif /* !IPRT_INCLUDED_cpp_list_h */ + diff --git a/include/iprt/cpp/lock.h b/include/iprt/cpp/lock.h new file mode 100644 index 00000000..c03e9b36 --- /dev/null +++ b/include/iprt/cpp/lock.h @@ -0,0 +1,179 @@ +/** @file + * IPRT - Classes for Scope-based Locking. + */ + +/* + * Copyright (C) 2007-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#ifndef IPRT_INCLUDED_cpp_lock_h +#define IPRT_INCLUDED_cpp_lock_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include <iprt/critsect.h> +#ifdef RT_LOCK_STRICT +# include <iprt/lockvalidator.h> +#endif + +RT_C_DECLS_BEGIN + +/** @defgroup grp_rt_cpp_lock C++ Scope-based Locking + * @ingroup grp_rt_cpp + * @{ + */ + +class RTCLock; + +/** + * The mutex lock. + * + * This is used as an object data member if the intention is to lock + * a single object. This can also be used statically, initialized in + * a global variable, for class wide purposes. + * + * This is best used together with RTCLock. + */ +class RTCLockMtx +{ +friend class RTCLock; + +private: + RTCRITSECT mMtx; + +public: + RTCLockMtx() + { +#ifdef RT_LOCK_STRICT_ORDER + RTCritSectInitEx(&mMtx, 0 /*fFlags*/, + RTLockValidatorClassCreateUnique(RT_SRC_POS, NULL), + RTLOCKVAL_SUB_CLASS_NONE, NULL); +#else + RTCritSectInit(&mMtx); +#endif + } + + /** Use to when creating locks that belongs in the same "class". */ + RTCLockMtx(RT_SRC_POS_DECL, uint32_t uSubClass = RTLOCKVAL_SUB_CLASS_NONE) + { +#ifdef RT_LOCK_STRICT_ORDER + RTCritSectInitEx(&mMtx, 0 /*fFlags*/, + RTLockValidatorClassForSrcPos(RT_SRC_POS_ARGS, NULL), + uSubClass, NULL); +#else + NOREF(uSubClass); + RTCritSectInit(&mMtx); + RT_SRC_POS_NOREF(); +#endif + } + + ~RTCLockMtx() + { + RTCritSectDelete(&mMtx); + } + + /* lock() and unlock() are private so that only friend RTCLock can access + them. */ +private: + inline void lock() + { + RTCritSectEnter(&mMtx); + } + + inline void unlock() + { + RTCritSectLeave(&mMtx); + } +}; + + +/** + * The stack object for automatic locking and unlocking. + * + * This is a helper class for automatic locks, to simplify requesting a + * RTCLockMtx and to not forget releasing it. To request a RTCLockMtx, simply + * create an instance of RTCLock on the stack and pass the mutex to it: + * + * @code + extern RTCLockMtx gMtx; // wherever this is + ... + if (...) + { + RTCLock lock(gMtx); + ... // do stuff + // when lock goes out of scope, destructor releases the mutex + } + @endcode + * + * You can also explicitly release the mutex by calling RTCLock::release(). + * This might be helpful if the lock doesn't go out of scope early enough + * for your mutex to be released. + */ +class RTCLock +{ +private: + /** Reference to the lock we're holding. */ + RTCLockMtx &m_rMtx; + /** Whether we're currently holding the lock of if it was already + * explictily released by the release() method. */ + bool m_fLocked; + +public: + RTCLock(RTCLockMtx &a_rMtx) + : m_rMtx(a_rMtx) + { + m_rMtx.lock(); + m_fLocked = true; + } + + ~RTCLock() + { + if (m_fLocked) + m_rMtx.unlock(); + } + + inline void release() + { + if (m_fLocked) + { + m_rMtx.unlock(); + m_fLocked = false; + } + } +}; + + +/** @} */ + +RT_C_DECLS_END + +#endif /* !IPRT_INCLUDED_cpp_lock_h */ + diff --git a/include/iprt/cpp/meta.h b/include/iprt/cpp/meta.h new file mode 100644 index 00000000..b59493b7 --- /dev/null +++ b/include/iprt/cpp/meta.h @@ -0,0 +1,125 @@ +/** @file + * IPRT - C++ Meta programming. + */ + +/* + * Copyright (C) 2011-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#ifndef IPRT_INCLUDED_cpp_meta_h +#define IPRT_INCLUDED_cpp_meta_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include <iprt/types.h> + +/** @defgroup grp_rt_cpp_meta C++ Meta programming utilities + * @ingroup grp_rt_cpp + * @{ + */ + +/** + * Check for a condition on compile time and dependent of the result TrueResult + * or FalseResult will be defined. + * + * @param Condition Condition to check. + * @param TrueResult Result when condition is true. + * @param FalseResult Result when condition is false + */ +template <bool Condition, typename TrueResult, typename FalseResult> +struct RTCIf; + +/** + * Check for a condition on compile time and dependent of the result TrueResult + * or FalseResult will be defined. + * + * True specialization of RTCIf. + * + * @param TrueResult Result when condition is true. + * @param FalseResult Result when condition is false + */ +template <typename TrueResult, typename FalseResult> +struct RTCIf<true, TrueResult, FalseResult> +{ + typedef TrueResult result; +}; + +/** + * Check for a condition on compile time and dependent of the result TrueResult + * or FalseResult will be defined. + * + * False specialization of RTCIf. + * + * @param TrueResult Result when condition is true. + * @param FalseResult Result when condition is false + */ +template <typename TrueResult, typename FalseResult> +struct RTCIf<false, TrueResult, FalseResult> +{ + typedef FalseResult result; +}; + +/** + * Check if @a T is a pointer or not at compile time and dependent of the + * result TrueResult or FalseResult will be defined. + * + * False version of RTCIfPtr. + * + * @param Condition Condition to check. + * @param TrueResult Result when condition is true. + * @param FalseResult Result when condition is false + */ +template <class T, typename TrueResult, typename FalseResult> +struct RTCIfPtr +{ + typedef FalseResult result; +}; + +/** + * Check if @a T is a pointer or not at compile time and dependent of the + * result TrueResult or FalseResult will be defined. + * + * True specialization of RTCIfPtr. + * + * @param Condition Condition to check. + * @param TrueResult Result when condition is true. + * @param FalseResult Result when condition is false + */ +template <class T, typename TrueResult, typename FalseResult> +struct RTCIfPtr<T*, TrueResult, FalseResult> +{ + typedef TrueResult result; +}; + +/** @} */ + +#endif /* !IPRT_INCLUDED_cpp_meta_h */ + diff --git a/include/iprt/cpp/ministring.h b/include/iprt/cpp/ministring.h new file mode 100644 index 00000000..c9b30f44 --- /dev/null +++ b/include/iprt/cpp/ministring.h @@ -0,0 +1,1638 @@ +/** @file + * IPRT - C++ string class. + */ + +/* + * Copyright (C) 2007-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#ifndef IPRT_INCLUDED_cpp_ministring_h +#define IPRT_INCLUDED_cpp_ministring_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include <iprt/mem.h> +#include <iprt/string.h> +#include <iprt/stdarg.h> +#include <iprt/cpp/list.h> + +#include <new> + + +/** @defgroup grp_rt_cpp_string C++ String support + * @ingroup grp_rt_cpp + * @{ + */ + +/** @brief C++ string class. + * + * This is a C++ string class that does not depend on anything else except IPRT + * memory management functions. Semantics are like in std::string, except it + * can do a lot less. + * + * Note that RTCString does not differentiate between NULL strings + * and empty strings. In other words, RTCString("") and RTCString(NULL) + * behave the same. In both cases, RTCString allocates no memory, reports + * a zero length and zero allocated bytes for both, and returns an empty + * C-style string from c_str(). + * + * @note RTCString ASSUMES that all strings it deals with are valid UTF-8. + * The caller is responsible for not breaking this assumption. + */ +#ifdef VBOX + /** @remarks Much of the code in here used to be in com::Utf8Str so that + * com::Utf8Str can now derive from RTCString and only contain code + * that is COM-specific, such as com::Bstr conversions. Compared to + * the old Utf8Str though, RTCString always knows the length of its + * member string and the size of the buffer so it can use memcpy() + * instead of strdup(). + */ +#endif +class RT_DECL_CLASS RTCString +{ +public: +#if defined(RT_NEED_NEW_AND_DELETE) && ( !defined(RTMEM_WRAP_SOME_NEW_AND_DELETE_TO_EF) \ + || defined(RTMEM_NO_WRAP_SOME_NEW_AND_DELETE_TO_EF)) + RTMEM_IMPLEMENT_NEW_AND_DELETE(); +#else + RTMEMEF_NEW_AND_DELETE_OPERATORS(); +#endif + + /** + * Creates an empty string that has no memory allocated. + */ + RTCString() + : m_psz(NULL), + m_cch(0), + m_cbAllocated(0) + { + } + + /** + * Creates a copy of another RTCString. + * + * This allocates s.length() + 1 bytes for the new instance, unless s is empty. + * + * @param a_rSrc The source string. + * + * @throws std::bad_alloc + */ + RTCString(const RTCString &a_rSrc) + { + copyFromN(a_rSrc.m_psz, a_rSrc.m_cch); + } + + /** + * Creates a copy of a C-style string. + * + * This allocates strlen(pcsz) + 1 bytes for the new instance, unless s is empty. + * + * @param pcsz The source string. + * + * @throws std::bad_alloc + */ + RTCString(const char *pcsz) + { + copyFromN(pcsz, pcsz ? strlen(pcsz) : 0); + } + + /** + * Create a partial copy of another RTCString. + * + * @param a_rSrc The source string. + * @param a_offSrc The byte offset into the source string. + * @param a_cchSrc The max number of chars (encoded UTF-8 bytes) + * to copy from the source string. + */ + RTCString(const RTCString &a_rSrc, size_t a_offSrc, size_t a_cchSrc = npos) + { + if (a_offSrc < a_rSrc.m_cch) + copyFromN(&a_rSrc.m_psz[a_offSrc], RT_MIN(a_cchSrc, a_rSrc.m_cch - a_offSrc)); + else + { + m_psz = NULL; + m_cch = 0; + m_cbAllocated = 0; + } + } + + /** + * Create a partial copy of a C-style string. + * + * @param a_pszSrc The source string (UTF-8). + * @param a_cchSrc The max number of chars (encoded UTF-8 bytes) + * to copy from the source string. This must not + * be '0' as the compiler could easily mistake + * that for the va_list constructor. + */ + RTCString(const char *a_pszSrc, size_t a_cchSrc) + { + size_t cchMax = a_pszSrc ? RTStrNLen(a_pszSrc, a_cchSrc) : 0; + copyFromN(a_pszSrc, RT_MIN(a_cchSrc, cchMax)); + } + + /** + * Create a string containing @a a_cTimes repetitions of the character @a + * a_ch. + * + * @param a_cTimes The number of times the character is repeated. + * @param a_ch The character to fill the string with. + */ + RTCString(size_t a_cTimes, char a_ch) + : m_psz(NULL), + m_cch(0), + m_cbAllocated(0) + { + Assert((unsigned)a_ch < 0x80); + if (a_cTimes) + { + reserve(a_cTimes + 1); + memset(m_psz, a_ch, a_cTimes); + m_psz[a_cTimes] = '\0'; + m_cch = a_cTimes; + } + } + + /** + * Create a new string given the format string and its arguments. + * + * @param a_pszFormat Pointer to the format string (UTF-8), + * @see pg_rt_str_format. + * @param a_va Argument vector containing the arguments + * specified by the format string. + * @sa printfV + * @remarks Not part of std::string. + */ + RTCString(const char *a_pszFormat, va_list a_va) RT_IPRT_FORMAT_ATTR(1, 0) + : m_psz(NULL), + m_cch(0), + m_cbAllocated(0) + { + printfV(a_pszFormat, a_va); + } + + /** + * Destructor. + */ + virtual ~RTCString() + { + cleanup(); + } + + /** + * String length in bytes. + * + * Returns the length of the member string in bytes, which is equal to strlen(c_str()). + * In other words, this does not count unicode codepoints; use utf8length() for that. + * The byte length is always cached so calling this is cheap and requires no + * strlen() invocation. + * + * @returns m_cbLength. + */ + size_t length() const + { + return m_cch; + } + + /** + * String length in unicode codepoints. + * + * As opposed to length(), which returns the length in bytes, this counts + * the number of unicode codepoints. This is *not* cached so calling this + * is expensive. + * + * @returns Number of codepoints in the member string. + */ + size_t uniLength() const + { + return m_psz ? RTStrUniLen(m_psz) : 0; + } + + /** + * The allocated buffer size (in bytes). + * + * Returns the number of bytes allocated in the internal string buffer, which is + * at least length() + 1 if length() > 0; for an empty string, this returns 0. + * + * @returns m_cbAllocated. + */ + size_t capacity() const + { + return m_cbAllocated; + } + + /** + * Make sure at that least cb of buffer space is reserved. + * + * Requests that the contained memory buffer have at least cb bytes allocated. + * This may expand or shrink the string's storage, but will never truncate the + * contained string. In other words, cb will be ignored if it's smaller than + * length() + 1. + * + * @param cb New minimum size (in bytes) of member memory buffer. + * + * @throws std::bad_alloc On allocation error. The object is left unchanged. + */ + void reserve(size_t cb) + { + if ( ( cb != m_cbAllocated + && cb > m_cch + 1) + || ( m_psz == NULL + && cb > 0)) + { + int rc = RTStrRealloc(&m_psz, cb); + if (RT_SUCCESS(rc)) + m_cbAllocated = cb; +#ifdef RT_EXCEPTIONS_ENABLED + else + throw std::bad_alloc(); +#endif + } + } + + /** + * A C like version of the reserve method, i.e. return code instead of throw. + * + * @returns VINF_SUCCESS or VERR_NO_STRING_MEMORY. + * @param cb New minimum size (in bytes) of member memory buffer. + */ + int reserveNoThrow(size_t cb) RT_NOEXCEPT + { + if ( ( cb != m_cbAllocated + && cb > m_cch + 1) + || ( m_psz == NULL + && cb > 0)) + { + int rc = RTStrRealloc(&m_psz, cb); + if (RT_SUCCESS(rc)) + m_cbAllocated = cb; + else + return rc; + } + return VINF_SUCCESS; + } + + /** + * Deallocates all memory. + */ + inline void setNull() + { + cleanup(); + } + + /** + * Assigns a copy of pcsz to @a this. + * + * @param pcsz The source string. + * + * @throws std::bad_alloc On allocation failure. The object is left describing + * a NULL string. + * + * @returns Reference to the object. + */ + RTCString &operator=(const char *pcsz) + { + if (m_psz != pcsz) + { + cleanup(); + copyFromN(pcsz, pcsz ? strlen(pcsz) : 0); + } + return *this; + } + + /** + * Assigns a copy of s to @a this. + * + * @param s The source string. + * + * @throws std::bad_alloc On allocation failure. The object is left describing + * a NULL string. + * + * @returns Reference to the object. + */ + RTCString &operator=(const RTCString &s) + { + if (this != &s) + { + cleanup(); + copyFromN(s.m_psz, s.m_cch); + } + return *this; + } + + /** + * Assigns a copy of another RTCString. + * + * @param a_rSrc Reference to the source string. + * @throws std::bad_alloc On allocation error. The object is left unchanged. + */ + RTCString &assign(const RTCString &a_rSrc); + + /** + * Assigns a copy of another RTCString. + * + * @param a_rSrc Reference to the source string. + * @returns VINF_SUCCESS or VERR_NO_STRING_MEMORY. + */ + int assignNoThrow(const RTCString &a_rSrc) RT_NOEXCEPT; + + /** + * Assigns a copy of a C-style string. + * + * @param a_pszSrc Pointer to the C-style source string. + * @throws std::bad_alloc On allocation error. The object is left unchanged. + * @remarks ASSUMES valid + */ + RTCString &assign(const char *a_pszSrc); + + /** + * Assigns a copy of a C-style string. + * + * @param a_pszSrc Pointer to the C-style source string. + * @returns VINF_SUCCESS or VERR_NO_STRING_MEMORY. + * @remarks ASSUMES valid + */ + int assignNoThrow(const char *a_pszSrc) RT_NOEXCEPT; + + /** + * Assigns a partial copy of another RTCString. + * + * @param a_rSrc The source string. + * @param a_offSrc The byte offset into the source string. + * @param a_cchSrc The max number of chars (encoded UTF-8 bytes) + * to copy from the source string. + * @throws std::bad_alloc On allocation error. The object is left unchanged. + */ + RTCString &assign(const RTCString &a_rSrc, size_t a_offSrc, size_t a_cchSrc = npos); + + /** + * Assigns a partial copy of another RTCString. + * + * @param a_rSrc The source string. + * @param a_offSrc The byte offset into the source string. + * @param a_cchSrc The max number of chars (encoded UTF-8 bytes) + * to copy from the source string. + * @returns VINF_SUCCESS or VERR_NO_STRING_MEMORY. + */ + int assignNoThrow(const RTCString &a_rSrc, size_t a_offSrc, size_t a_cchSrc = npos) RT_NOEXCEPT; + + /** + * Assigns a partial copy of a C-style string. + * + * @param a_pszSrc The source string (UTF-8). + * @param a_cchSrc The max number of chars (encoded UTF-8 bytes) + * to copy from the source string. + * @throws std::bad_alloc On allocation error. The object is left unchanged. + */ + RTCString &assign(const char *a_pszSrc, size_t a_cchSrc); + + /** + * Assigns a partial copy of a C-style string. + * + * @param a_pszSrc The source string (UTF-8). + * @param a_cchSrc The max number of chars (encoded UTF-8 bytes) + * to copy from the source string. + * @returns VINF_SUCCESS or VERR_NO_STRING_MEMORY. + */ + int assignNoThrow(const char *a_pszSrc, size_t a_cchSrc) RT_NOEXCEPT; + + /** + * Assigs a string containing @a a_cTimes repetitions of the character @a a_ch. + * + * @param a_cTimes The number of times the character is repeated. + * @param a_ch The character to fill the string with. + * @throws std::bad_alloc On allocation error. The object is left unchanged. + */ + RTCString &assign(size_t a_cTimes, char a_ch); + + /** + * Assigs a string containing @a a_cTimes repetitions of the character @a a_ch. + * + * @param a_cTimes The number of times the character is repeated. + * @param a_ch The character to fill the string with. + * @returns VINF_SUCCESS or VERR_NO_STRING_MEMORY. + */ + int assignNoThrow(size_t a_cTimes, char a_ch) RT_NOEXCEPT; + + /** + * Assigns the output of the string format operation (RTStrPrintf). + * + * @param pszFormat Pointer to the format string, + * @see pg_rt_str_format. + * @param ... Ellipsis containing the arguments specified by + * the format string. + * + * @throws std::bad_alloc On allocation error. Object state is undefined. + * + * @returns Reference to the object. + */ + RTCString &printf(const char *pszFormat, ...) RT_IPRT_FORMAT_ATTR(1, 2); + + /** + * Assigns the output of the string format operation (RTStrPrintf). + * + * @param pszFormat Pointer to the format string, + * @see pg_rt_str_format. + * @param ... Ellipsis containing the arguments specified by + * the format string. + * + * @returns VINF_SUCCESS or VERR_NO_STRING_MEMORY. + */ + int printfNoThrow(const char *pszFormat, ...) RT_NOEXCEPT RT_IPRT_FORMAT_ATTR(1, 2); + + /** + * Assigns the output of the string format operation (RTStrPrintfV). + * + * @param pszFormat Pointer to the format string, + * @see pg_rt_str_format. + * @param va Argument vector containing the arguments + * specified by the format string. + * + * @throws std::bad_alloc On allocation error. Object state is undefined. + * + * @returns Reference to the object. + */ + RTCString &printfV(const char *pszFormat, va_list va) RT_IPRT_FORMAT_ATTR(1, 0); + + /** + * Assigns the output of the string format operation (RTStrPrintfV). + * + * @param pszFormat Pointer to the format string, + * @see pg_rt_str_format. + * @param va Argument vector containing the arguments + * specified by the format string. + * + * @returns VINF_SUCCESS or VERR_NO_STRING_MEMORY. + */ + int printfVNoThrow(const char *pszFormat, va_list va) RT_NOEXCEPT RT_IPRT_FORMAT_ATTR(1, 0); + + /** + * Appends the string @a that to @a rThat. + * + * @param rThat The string to append. + * @throws std::bad_alloc On allocation error. The object is left unchanged. + * @returns Reference to the object. + */ + RTCString &append(const RTCString &rThat); + + /** + * Appends the string @a that to @a rThat. + * + * @param rThat The string to append. + * @returns VINF_SUCCESS or VERR_NO_STRING_MEMORY. + */ + int appendNoThrow(const RTCString &rThat) RT_NOEXCEPT; + + /** + * Appends the string @a pszSrc to @a this. + * + * @param pszSrc The C-style string to append. + * @throws std::bad_alloc On allocation error. The object is left unchanged. + * @returns Reference to the object. + */ + RTCString &append(const char *pszSrc); + + /** + * Appends the string @a pszSrc to @a this. + * + * @param pszSrc The C-style string to append. + * @returns VINF_SUCCESS or VERR_NO_STRING_MEMORY. + */ + int appendNoThrow(const char *pszSrc) RT_NOEXCEPT; + + /** + * Appends the a substring from @a rThat to @a this. + * + * @param rThat The string to append a substring from. + * @param offStart The start of the substring to append (byte offset, + * not codepoint). + * @param cchMax The maximum number of bytes to append. + * @throws std::bad_alloc On allocation error. The object is left unchanged. + * @returns Reference to the object. + */ + RTCString &append(const RTCString &rThat, size_t offStart, size_t cchMax = RTSTR_MAX); + + /** + * Appends the a substring from @a rThat to @a this. + * + * @param rThat The string to append a substring from. + * @param offStart The start of the substring to append (byte offset, + * not codepoint). + * @param cchMax The maximum number of bytes to append. + * @returns VINF_SUCCESS or VERR_NO_STRING_MEMORY. + */ + int appendNoThrow(const RTCString &rThat, size_t offStart, size_t cchMax = RTSTR_MAX) RT_NOEXCEPT; + + /** + * Appends the first @a cchMax chars from string @a pszThat to @a this. + * + * @param pszThat The C-style string to append. + * @param cchMax The maximum number of bytes to append. + * @throws std::bad_alloc On allocation error. The object is left unchanged. + * @returns Reference to the object. + */ + RTCString &append(const char *pszThat, size_t cchMax); + + /** + * Appends the first @a cchMax chars from string @a pszThat to @a this. + * + * @param pszThat The C-style string to append. + * @param cchMax The maximum number of bytes to append. + * @returns VINF_SUCCESS or VERR_NO_STRING_MEMORY. + */ + int appendNoThrow(const char *pszThat, size_t cchMax) RT_NOEXCEPT; + + /** + * Appends the given character to @a this. + * + * @param ch The character to append. + * @throws std::bad_alloc On allocation error. The object is left unchanged. + * @returns Reference to the object. + */ + RTCString &append(char ch); + + /** + * Appends the given character to @a this. + * + * @param ch The character to append. + * @returns VINF_SUCCESS or VERR_NO_STRING_MEMORY. + */ + int appendNoThrow(char ch) RT_NOEXCEPT; + + /** + * Appends the given unicode code point to @a this. + * + * @param uc The unicode code point to append. + * @throws std::bad_alloc On allocation error. The object is left unchanged. + * @returns Reference to the object. + */ + RTCString &appendCodePoint(RTUNICP uc); + + /** + * Appends the given unicode code point to @a this. + * + * @param uc The unicode code point to append. + * @returns VINF_SUCCESS, VERR_INVALID_UTF8_ENCODING or VERR_NO_STRING_MEMORY. + */ + int appendCodePointNoThrow(RTUNICP uc) RT_NOEXCEPT; + + /** + * Appends the output of the string format operation (RTStrPrintf). + * + * @param pszFormat Pointer to the format string, + * @see pg_rt_str_format. + * @param ... Ellipsis containing the arguments specified by + * the format string. + * + * @throws std::bad_alloc On allocation error. Object state is undefined. + * + * @returns Reference to the object. + */ + RTCString &appendPrintf(const char *pszFormat, ...) RT_IPRT_FORMAT_ATTR(1, 2); + + /** + * Appends the output of the string format operation (RTStrPrintf). + * + * @param pszFormat Pointer to the format string, + * @see pg_rt_str_format. + * @param ... Ellipsis containing the arguments specified by + * the format string. + * + * @returns VINF_SUCCESS or VERR_NO_STRING_MEMORY. + */ + int appendPrintfNoThrow(const char *pszFormat, ...) RT_NOEXCEPT RT_IPRT_FORMAT_ATTR(1, 2); + + /** + * Appends the output of the string format operation (RTStrPrintfV). + * + * @param pszFormat Pointer to the format string, + * @see pg_rt_str_format. + * @param va Argument vector containing the arguments + * specified by the format string. + * + * @throws std::bad_alloc On allocation error. Object state is undefined. + * + * @returns Reference to the object. + */ + RTCString &appendPrintfV(const char *pszFormat, va_list va) RT_IPRT_FORMAT_ATTR(1, 0); + + /** + * Appends the output of the string format operation (RTStrPrintfV). + * + * @param pszFormat Pointer to the format string, + * @see pg_rt_str_format. + * @param va Argument vector containing the arguments + * specified by the format string. + * + * @returns VINF_SUCCESS or VERR_NO_STRING_MEMORY. + */ + int appendPrintfVNoThrow(const char *pszFormat, va_list va) RT_NOEXCEPT RT_IPRT_FORMAT_ATTR(1, 0); + + /** + * Shortcut to append(), RTCString variant. + * + * @param rThat The string to append. + * @returns Reference to the object. + */ + RTCString &operator+=(const RTCString &rThat) + { + return append(rThat); + } + + /** + * Shortcut to append(), const char* variant. + * + * @param pszThat The C-style string to append. + * @returns Reference to the object. + */ + RTCString &operator+=(const char *pszThat) + { + return append(pszThat); + } + + /** + * Shortcut to append(), char variant. + * + * @param ch The character to append. + * + * @returns Reference to the object. + */ + RTCString &operator+=(char ch) + { + return append(ch); + } + + /** + * Converts the member string to upper case. + * + * @returns Reference to the object. + */ + RTCString &toUpper() RT_NOEXCEPT + { + if (length()) + { + /* Folding an UTF-8 string may result in a shorter encoding (see + testcase), so recalculate the length afterwards. */ + ::RTStrToUpper(m_psz); + size_t cchNew = strlen(m_psz); + Assert(cchNew <= m_cch); + m_cch = cchNew; + } + return *this; + } + + /** + * Converts the member string to lower case. + * + * @returns Reference to the object. + */ + RTCString &toLower() RT_NOEXCEPT + { + if (length()) + { + /* Folding an UTF-8 string may result in a shorter encoding (see + testcase), so recalculate the length afterwards. */ + ::RTStrToLower(m_psz); + size_t cchNew = strlen(m_psz); + Assert(cchNew <= m_cch); + m_cch = cchNew; + } + return *this; + } + + /** + * Erases a sequence from the string. + * + * @returns Reference to the object. + * @param offStart Where in @a this string to start erasing. + * @param cchLength How much following @a offStart to erase. + */ + RTCString &erase(size_t offStart = 0, size_t cchLength = npos) RT_NOEXCEPT; + + /** + * Replaces a span of @a this string with a replacement string. + * + * @returns Reference to the object. + * @param offStart Where in @a this string to start replacing. + * @param cchLength How much following @a offStart to replace. npos is + * accepted. + * @param rStrReplacement The replacement string. + * + * @throws std::bad_alloc On allocation error. The object is left unchanged. + * + * @note Non-standard behaviour if offStart is beyond the end of the string. + * No change will occure and strict builds hits a debug assertion. + */ + RTCString &replace(size_t offStart, size_t cchLength, const RTCString &rStrReplacement); + + /** + * Replaces a span of @a this string with a replacement string. + * + * @returns VINF_SUCCESS, VERR_OUT_OF_RANGE or VERR_NO_STRING_MEMORY. + * @param offStart Where in @a this string to start replacing. + * @param cchLength How much following @a offStart to replace. npos is + * accepted. + * @param rStrReplacement The replacement string. + */ + int replaceNoThrow(size_t offStart, size_t cchLength, const RTCString &rStrReplacement) RT_NOEXCEPT; + + /** + * Replaces a span of @a this string with a replacement substring. + * + * @returns Reference to the object. + * @param offStart Where in @a this string to start replacing. + * @param cchLength How much following @a offStart to replace. npos is + * accepted. + * @param rStrReplacement The string from which a substring is taken. + * @param offReplacement The offset into @a rStrReplacement where the + * replacement substring starts. + * @param cchReplacement The maximum length of the replacement substring. + * + * @throws std::bad_alloc On allocation error. The object is left unchanged. + * + * @note Non-standard behaviour if offStart or offReplacement is beyond the + * end of the repective strings. No change is made in the former case, + * while we consider it an empty string in the latter. In both + * situation a debug assertion is raised in strict builds. + */ + RTCString &replace(size_t offStart, size_t cchLength, const RTCString &rStrReplacement, + size_t offReplacement, size_t cchReplacement); + + /** + * Replaces a span of @a this string with a replacement substring. + * + * @returns VINF_SUCCESS, VERR_OUT_OF_RANGE or VERR_NO_STRING_MEMORY. + * @param offStart Where in @a this string to start replacing. + * @param cchLength How much following @a offStart to replace. npos is + * accepted. + * @param rStrReplacement The string from which a substring is taken. + * @param offReplacement The offset into @a rStrReplacement where the + * replacement substring starts. + * @param cchReplacement The maximum length of the replacement substring. + */ + int replaceNoThrow(size_t offStart, size_t cchLength, const RTCString &rStrReplacement, + size_t offReplacement, size_t cchReplacement) RT_NOEXCEPT; + + /** + * Replaces a span of @a this string with the replacement string. + * + * @returns Reference to the object. + * @param offStart Where in @a this string to start replacing. + * @param cchLength How much following @a offStart to replace. npos is + * accepted. + * @param pszReplacement The replacement string. + * + * @throws std::bad_alloc On allocation error. The object is left unchanged. + * + * @note Non-standard behaviour if offStart is beyond the end of the string. + * No change will occure and strict builds hits a debug assertion. + */ + RTCString &replace(size_t offStart, size_t cchLength, const char *pszReplacement); + + /** + * Replaces a span of @a this string with the replacement string. + * + * @returns VINF_SUCCESS, VERR_OUT_OF_RANGE or VERR_NO_STRING_MEMORY. + * @param offStart Where in @a this string to start replacing. + * @param cchLength How much following @a offStart to replace. npos is + * accepted. + * @param pszReplacement The replacement string. + */ + int replaceNoThrow(size_t offStart, size_t cchLength, const char *pszReplacement) RT_NOEXCEPT; + + /** + * Replaces a span of @a this string with the replacement string. + * + * @returns Reference to the object. + * @param offStart Where in @a this string to start replacing. + * @param cchLength How much following @a offStart to replace. npos is + * accepted. + * @param pszReplacement The replacement string. + * @param cchReplacement How much of @a pszReplacement to use at most. If a + * zero terminator is found before reaching this value, + * we'll stop there. + * + * @throws std::bad_alloc On allocation error. The object is left unchanged. + * + * @note Non-standard behaviour if offStart is beyond the end of the string. + * No change will occure and strict builds hits a debug assertion. + */ + RTCString &replace(size_t offStart, size_t cchLength, const char *pszReplacement, size_t cchReplacement); + + /** + * Replaces a span of @a this string with the replacement string. + * + * @returns VINF_SUCCESS, VERR_OUT_OF_RANGE or VERR_NO_STRING_MEMORY. + * @param offStart Where in @a this string to start replacing. + * @param cchLength How much following @a offStart to replace. npos is + * accepted. + * @param pszReplacement The replacement string. + * @param cchReplacement How much of @a pszReplacement to use at most. If a + * zero terminator is found before reaching this value, + * we'll stop there. + */ + int replaceNoThrow(size_t offStart, size_t cchLength, const char *pszReplacement, size_t cchReplacement) RT_NOEXCEPT; + + /** + * Truncates the string to a max length of @a cchMax. + * + * If the string is shorter than @a cchMax characters, no change is made. + * + * If the @a cchMax is not at the start of a UTF-8 sequence, it will be adjusted + * down to the start of the UTF-8 sequence. Thus, after a truncation, the + * length() may be smaller than @a cchMax. + * + */ + RTCString &truncate(size_t cchMax) RT_NOEXCEPT; + + /** + * Index operator. + * + * Returns the byte at the given index, or a null byte if the index is not + * smaller than length(). This does _not_ count codepoints but simply points + * into the member C-style string. + * + * @param i The index into the string buffer. + * @returns char at the index or null. + */ + inline char operator[](size_t i) const RT_NOEXCEPT + { + if (i < length()) + return m_psz[i]; + return '\0'; + } + + /** + * Returns the contained string as a const C-style string pointer. + * + * This never returns NULL; if the string is empty, this returns a pointer to + * static null byte. + * + * @returns const pointer to C-style string. + */ + inline const char *c_str() const RT_NOEXCEPT + { + return (m_psz) ? m_psz : ""; + } + + /** + * Returns a non-const raw pointer that allows to modify the string directly. + * As opposed to c_str() and raw(), this DOES return NULL for an empty string + * because we cannot return a non-const pointer to a static "" global. + * + * @warning + * -# Be sure not to modify data beyond the allocated memory! Call + * capacity() to find out how large that buffer is. + * -# After any operation that modifies the length of the string, + * you _must_ call RTCString::jolt(), or subsequent copy operations + * may go nowhere. Better not use mutableRaw() at all. + */ + char *mutableRaw() RT_NOEXCEPT + { + return m_psz; + } + + /** + * Clean up after using mutableRaw. + * + * Intended to be called after something has messed with the internal string + * buffer (e.g. after using mutableRaw() or Utf8Str::asOutParam()). Resets the + * internal lengths correctly. Otherwise subsequent copy operations may go + * nowhere. + */ + void jolt() RT_NOEXCEPT + { + if (m_psz) + { + m_cch = strlen(m_psz); + m_cbAllocated = m_cch + 1; /* (Required for the Utf8Str::asOutParam case) */ + } + else + { + m_cch = 0; + m_cbAllocated = 0; + } + } + + /** + * Returns @c true if the member string has no length. + * + * This is @c true for instances created from both NULL and "" input + * strings. + * + * This states nothing about how much memory might be allocated. + * + * @returns @c true if empty, @c false if not. + */ + bool isEmpty() const RT_NOEXCEPT + { + return length() == 0; + } + + /** + * Returns @c false if the member string has no length. + * + * This is @c false for instances created from both NULL and "" input + * strings. + * + * This states nothing about how much memory might be allocated. + * + * @returns @c false if empty, @c true if not. + */ + bool isNotEmpty() const RT_NOEXCEPT + { + return length() != 0; + } + + /** Case sensitivity selector. */ + enum CaseSensitivity + { + CaseSensitive, + CaseInsensitive + }; + + /** + * Compares the member string to a C-string. + * + * @param pcszThat The string to compare with. + * @param cs Whether comparison should be case-sensitive. + * @returns 0 if equal, negative if this is smaller than @a pcsz, positive + * if larger. + */ + int compare(const char *pcszThat, CaseSensitivity cs = CaseSensitive) const RT_NOEXCEPT + { + /* This klugde is for m_cch=0 and m_psz=NULL. pcsz=NULL and psz="" + are treated the same way so that str.compare(str2.c_str()) works. */ + if (length() == 0) + return pcszThat == NULL || *pcszThat == '\0' ? 0 : -1; + + if (cs == CaseSensitive) + return ::RTStrCmp(m_psz, pcszThat); + return ::RTStrICmp(m_psz, pcszThat); + } + + /** + * Compares the member string to another RTCString. + * + * @param rThat The string to compare with. + * @param cs Whether comparison should be case-sensitive. + * @returns 0 if equal, negative if this is smaller than @a pcsz, positive + * if larger. + */ + int compare(const RTCString &rThat, CaseSensitivity cs = CaseSensitive) const RT_NOEXCEPT + { + if (cs == CaseSensitive) + return ::RTStrCmp(m_psz, rThat.m_psz); + return ::RTStrICmp(m_psz, rThat.m_psz); + } + + /** + * Compares the two strings. + * + * @returns true if equal, false if not. + * @param rThat The string to compare with. + */ + bool equals(const RTCString &rThat) const RT_NOEXCEPT + { + return rThat.length() == length() + && ( length() == 0 + || memcmp(rThat.m_psz, m_psz, length()) == 0); + } + + /** + * Compares the two strings. + * + * @returns true if equal, false if not. + * @param pszThat The string to compare with. + */ + bool equals(const char *pszThat) const RT_NOEXCEPT + { + /* This klugde is for m_cch=0 and m_psz=NULL. pcsz=NULL and psz="" + are treated the same way so that str.equals(str2.c_str()) works. */ + if (length() == 0) + return pszThat == NULL || *pszThat == '\0'; + return RTStrCmp(pszThat, m_psz) == 0; + } + + /** + * Compares the two strings ignoring differences in case. + * + * @returns true if equal, false if not. + * @param that The string to compare with. + */ + bool equalsIgnoreCase(const RTCString &that) const RT_NOEXCEPT + { + /* Unfolded upper and lower case characters may require different + amount of encoding space, so the length optimization doesn't work. */ + return RTStrICmp(that.m_psz, m_psz) == 0; + } + + /** + * Compares the two strings ignoring differences in case. + * + * @returns true if equal, false if not. + * @param pszThat The string to compare with. + */ + bool equalsIgnoreCase(const char *pszThat) const RT_NOEXCEPT + { + /* This klugde is for m_cch=0 and m_psz=NULL. pcsz=NULL and psz="" + are treated the same way so that str.equalsIgnoreCase(str2.c_str()) works. */ + if (length() == 0) + return pszThat == NULL || *pszThat == '\0'; + return RTStrICmp(pszThat, m_psz) == 0; + } + + /** @name Comparison operators. + * @{ */ + bool operator==(const RTCString &that) const { return equals(that); } + bool operator!=(const RTCString &that) const { return !equals(that); } + bool operator<( const RTCString &that) const { return compare(that) < 0; } + bool operator>( const RTCString &that) const { return compare(that) > 0; } + + bool operator==(const char *pszThat) const { return equals(pszThat); } + bool operator!=(const char *pszThat) const { return !equals(pszThat); } + bool operator<( const char *pszThat) const { return compare(pszThat) < 0; } + bool operator>( const char *pszThat) const { return compare(pszThat) > 0; } + /** @} */ + + /** Max string offset value. + * + * When returned by a method, this indicates failure. When taken as input, + * typically a default, it means all the way to the string terminator. + */ + static const size_t npos; + + /** + * Find the given substring. + * + * Looks for @a pszNeedle in @a this starting at @a offStart and returns its + * position as a byte (not codepoint) offset, counting from the beginning of + * @a this as 0. + * + * @param pszNeedle The substring to find. + * @param offStart The (byte) offset into the string buffer to start + * searching. + * + * @returns 0 based position of pszNeedle. npos if not found. + */ + size_t find(const char *pszNeedle, size_t offStart = 0) const RT_NOEXCEPT; + size_t find_first_of(const char *pszNeedle, size_t offStart = 0) const RT_NOEXCEPT + { return find(pszNeedle, offStart); } + + /** + * Find the given substring. + * + * Looks for @a pStrNeedle in @a this starting at @a offStart and returns its + * position as a byte (not codepoint) offset, counting from the beginning of + * @a this as 0. + * + * @param pStrNeedle The substring to find. + * @param offStart The (byte) offset into the string buffer to start + * searching. + * + * @returns 0 based position of pStrNeedle. npos if not found or pStrNeedle is + * NULL or an empty string. + */ + size_t find(const RTCString *pStrNeedle, size_t offStart = 0) const RT_NOEXCEPT; + + /** + * Find the given substring. + * + * Looks for @a rStrNeedle in @a this starting at @a offStart and returns its + * position as a byte (not codepoint) offset, counting from the beginning of + * @a this as 0. + * + * @param rStrNeedle The substring to find. + * @param offStart The (byte) offset into the string buffer to start + * searching. + * + * @returns 0 based position of pStrNeedle. npos if not found or pStrNeedle is + * NULL or an empty string. + */ + size_t find(const RTCString &rStrNeedle, size_t offStart = 0) const RT_NOEXCEPT; + size_t find_first_of(const RTCString &rStrNeedle, size_t offStart = 0) const RT_NOEXCEPT + { return find(rStrNeedle, offStart); } + + /** + * Find the given character (byte). + * + * @returns 0 based position of chNeedle. npos if not found or pStrNeedle is + * NULL or an empty string. + * @param chNeedle The character (byte) to find. + * @param offStart The (byte) offset into the string buffer to start + * searching. Default is start of the string. + * + * @note This searches for a C character value, not a codepoint. Use the + * string version to locate codepoints above U+7F. + */ + size_t find(char chNeedle, size_t offStart = 0) const RT_NOEXCEPT; + size_t find_first_of(char chNeedle, size_t offStart = 0) const RT_NOEXCEPT + { return find(chNeedle, offStart); } + + /** + * Replaces all occurences of cFind with cReplace in the member string. + * In order not to produce invalid UTF-8, the characters must be ASCII + * values less than 128; this is not verified. + * + * @param chFind Character to replace. Must be ASCII < 128. + * @param chReplace Character to replace cFind with. Must be ASCII < 128. + */ + void findReplace(char chFind, char chReplace) RT_NOEXCEPT; + + /** + * Count the occurences of the specified character in the string. + * + * @param ch What to search for. Must be ASCII < 128. + * @remarks QString::count + */ + size_t count(char ch) const RT_NOEXCEPT; + + /** + * Count the occurences of the specified sub-string in the string. + * + * @param psz What to search for. + * @param cs Case sensitivity selector. + * @remarks QString::count + */ + size_t count(const char *psz, CaseSensitivity cs = CaseSensitive) const RT_NOEXCEPT; + + /** + * Count the occurences of the specified sub-string in the string. + * + * @param pStr What to search for. + * @param cs Case sensitivity selector. + * @remarks QString::count + */ + size_t count(const RTCString *pStr, CaseSensitivity cs = CaseSensitive) const RT_NOEXCEPT; + + /** + * Strips leading and trailing spaces. + * + * @returns this + */ + RTCString &strip() RT_NOEXCEPT; + + /** + * Strips leading spaces. + * + * @returns this + */ + RTCString &stripLeft() RT_NOEXCEPT; + + /** + * Strips trailing spaces. + * + * @returns this + */ + RTCString &stripRight() RT_NOEXCEPT; + + /** + * Returns a substring of @a this as a new Utf8Str. + * + * Works exactly like its equivalent in std::string. With the default + * parameters "0" and "npos", this always copies the entire string. The + * "pos" and "n" arguments represent bytes; it is the caller's responsibility + * to ensure that the offsets do not copy invalid UTF-8 sequences. When + * used in conjunction with find() and length(), this will work. + * + * @param pos Index of first byte offset to copy from @a this, + * counting from 0. + * @param n Number of bytes to copy, starting with the one at "pos". + * The copying will stop if the null terminator is encountered before + * n bytes have been copied. + */ + RTCString substr(size_t pos = 0, size_t n = npos) const + { + return RTCString(*this, pos, n); + } + + /** + * Returns a substring of @a this as a new Utf8Str. As opposed to substr(), this + * variant takes codepoint offsets instead of byte offsets. + * + * @param pos Index of first unicode codepoint to copy from + * @a this, counting from 0. + * @param n Number of unicode codepoints to copy, starting with + * the one at "pos". The copying will stop if the null + * terminator is encountered before n codepoints have + * been copied. + */ + RTCString substrCP(size_t pos = 0, size_t n = npos) const; + + /** + * Returns true if @a this ends with @a that. + * + * @param that Suffix to test for. + * @param cs Case sensitivity selector. + * @returns true if match, false if mismatch. + */ + bool endsWith(const RTCString &that, CaseSensitivity cs = CaseSensitive) const RT_NOEXCEPT; + + /** + * Returns true if @a this begins with @a that. + * @param that Prefix to test for. + * @param cs Case sensitivity selector. + * @returns true if match, false if mismatch. + */ + bool startsWith(const RTCString &that, CaseSensitivity cs = CaseSensitive) const RT_NOEXCEPT; + + /** + * Checks if the string starts with the given word, ignoring leading blanks. + * + * @param pszWord The word to test for. + * @param enmCase Case sensitivity selector. + * @returns true if match, false if mismatch. + */ + bool startsWithWord(const char *pszWord, CaseSensitivity enmCase = CaseSensitive) const RT_NOEXCEPT; + + /** + * Checks if the string starts with the given word, ignoring leading blanks. + * + * @param rThat Prefix to test for. + * @param enmCase Case sensitivity selector. + * @returns true if match, false if mismatch. + */ + bool startsWithWord(const RTCString &rThat, CaseSensitivity enmCase = CaseSensitive) const RT_NOEXCEPT; + + /** + * Returns true if @a this contains @a that (strstr). + * + * @param that Substring to look for. + * @param cs Case sensitivity selector. + * @returns true if found, false if not found. + */ + bool contains(const RTCString &that, CaseSensitivity cs = CaseSensitive) const RT_NOEXCEPT; + + /** + * Returns true if @a this contains @a pszNeedle (strstr). + * + * @param pszNeedle Substring to look for. + * @param cs Case sensitivity selector. + * @returns true if found, false if not found. + */ + bool contains(const char *pszNeedle, CaseSensitivity cs = CaseSensitive) const RT_NOEXCEPT; + + /** + * Attempts to convert the member string into a 32-bit integer. + * + * @returns 32-bit unsigned number on success. + * @returns 0 on failure. + */ + int32_t toInt32() const RT_NOEXCEPT + { + return RTStrToInt32(c_str()); + } + + /** + * Attempts to convert the member string into an unsigned 32-bit integer. + * + * @returns 32-bit unsigned number on success. + * @returns 0 on failure. + */ + uint32_t toUInt32() const RT_NOEXCEPT + { + return RTStrToUInt32(c_str()); + } + + /** + * Attempts to convert the member string into an 64-bit integer. + * + * @returns 64-bit unsigned number on success. + * @returns 0 on failure. + */ + int64_t toInt64() const RT_NOEXCEPT + { + return RTStrToInt64(c_str()); + } + + /** + * Attempts to convert the member string into an unsigned 64-bit integer. + * + * @returns 64-bit unsigned number on success. + * @returns 0 on failure. + */ + uint64_t toUInt64() const RT_NOEXCEPT + { + return RTStrToUInt64(c_str()); + } + + /** + * Attempts to convert the member string into an unsigned 64-bit integer. + * + * @param i Where to return the value on success. + * @returns IPRT error code, see RTStrToInt64. + */ + int toInt(uint64_t &i) const RT_NOEXCEPT; + + /** + * Attempts to convert the member string into an unsigned 32-bit integer. + * + * @param i Where to return the value on success. + * @returns IPRT error code, see RTStrToInt32. + */ + int toInt(uint32_t &i) const RT_NOEXCEPT; + + /** Splitting behavior regarding empty sections in the string. */ + enum SplitMode + { + KeepEmptyParts, /**< Empty parts are added as empty strings to the result list. */ + RemoveEmptyParts /**< Empty parts are skipped. */ + }; + + /** + * Splits a string separated by strSep into its parts. + * + * @param a_rstrSep The separator to search for. + * @param a_enmMode How should empty parts be handled. + * @returns separated strings as string list. + * @throws std::bad_alloc On allocation error. + */ + RTCList<RTCString, RTCString *> split(const RTCString &a_rstrSep, + SplitMode a_enmMode = RemoveEmptyParts) const; + + /** + * Joins a list of strings together using the provided separator and + * an optional prefix for each item in the list. + * + * @param a_rList The list to join. + * @param a_rstrPrefix The prefix used for appending to each item. + * @param a_rstrSep The separator used for joining. + * @returns joined string. + * @throws std::bad_alloc On allocation error. + */ + static RTCString joinEx(const RTCList<RTCString, RTCString *> &a_rList, + const RTCString &a_rstrPrefix /* = "" */, + const RTCString &a_rstrSep /* = "" */); + + /** + * Joins a list of strings together using the provided separator. + * + * @param a_rList The list to join. + * @param a_rstrSep The separator used for joining. + * @returns joined string. + * @throws std::bad_alloc On allocation error. + */ + static RTCString join(const RTCList<RTCString, RTCString *> &a_rList, + const RTCString &a_rstrSep = ""); + + /** + * Swaps two strings in a fast way. + * + * Exception safe. + * + * @param a_rThat The string to swap with. + */ + inline void swap(RTCString &a_rThat) RT_NOEXCEPT + { + char *pszTmp = m_psz; + size_t cchTmp = m_cch; + size_t cbAllocatedTmp = m_cbAllocated; + + m_psz = a_rThat.m_psz; + m_cch = a_rThat.m_cch; + m_cbAllocated = a_rThat.m_cbAllocated; + + a_rThat.m_psz = pszTmp; + a_rThat.m_cch = cchTmp; + a_rThat.m_cbAllocated = cbAllocatedTmp; + } + +protected: + + /** + * Hide operator bool() to force people to use isEmpty() explicitly. + */ + operator bool() const; + + /** + * Destructor implementation, also used to clean up in operator=() before + * assigning a new string. + */ + void cleanup() RT_NOEXCEPT + { + if (m_psz) + { + RTStrFree(m_psz); + m_psz = NULL; + m_cch = 0; + m_cbAllocated = 0; + } + } + + /** + * Protected internal helper to copy a string. + * + * This ignores the previous object state, so either call this from a + * constructor or call cleanup() first. copyFromN() unconditionally sets + * the members to a copy of the given other strings and makes no + * assumptions about previous contents. Can therefore be used both in copy + * constructors, when member variables have no defined value, and in + * assignments after having called cleanup(). + * + * @param pcszSrc The source string. + * @param cchSrc The number of chars (bytes) to copy from the + * source strings. RTSTR_MAX is NOT accepted. + * + * @throws std::bad_alloc On allocation failure. The object is left + * describing a NULL string. + */ + void copyFromN(const char *pcszSrc, size_t cchSrc) + { + if (cchSrc) + { + m_psz = RTStrAlloc(cchSrc + 1); + if (RT_LIKELY(m_psz)) + { + m_cch = cchSrc; + m_cbAllocated = cchSrc + 1; + memcpy(m_psz, pcszSrc, cchSrc); + m_psz[cchSrc] = '\0'; + } + else + { + m_cch = 0; + m_cbAllocated = 0; +#ifdef RT_EXCEPTIONS_ENABLED + throw std::bad_alloc(); +#endif + } + } + else + { + m_cch = 0; + m_cbAllocated = 0; + m_psz = NULL; + } + } + + /** + * Appends exactly @a cchSrc chars from @a pszSrc to @a this. + * + * This is an internal worker for the append() methods. + * + * @returns Reference to the object. + * @param pszSrc The source string. + * @param cchSrc The source string length (exact). + * @throws std::bad_alloc On allocation error. The object is left unchanged. + * + */ + RTCString &appendWorker(const char *pszSrc, size_t cchSrc); + + /** + * Appends exactly @a cchSrc chars from @a pszSrc to @a this. + * + * This is an internal worker for the appendNoThrow() methods. + * + * @returns VINF_SUCCESS or VERR_NO_STRING_MEMORY. + * @param pszSrc The source string. + * @param cchSrc The source string length (exact). + */ + int appendWorkerNoThrow(const char *pszSrc, size_t cchSrc) RT_NOEXCEPT; + + /** + * Replaces exatly @a cchLength chars at @a offStart with @a cchSrc from @a + * pszSrc. + * + * @returns Reference to the object. + * @param offStart Where in @a this string to start replacing. + * @param cchLength How much following @a offStart to replace. npos is + * accepted. + * @param pszSrc The replacement string. + * @param cchSrc The exactly length of the replacement string. + * + * @throws std::bad_alloc On allocation error. The object is left unchanged. + */ + RTCString &replaceWorker(size_t offStart, size_t cchLength, const char *pszSrc, size_t cchSrc); + + /** + * Replaces exatly @a cchLength chars at @a offStart with @a cchSrc from @a + * pszSrc. + * + * @returns VINF_SUCCESS, VERR_OUT_OF_RANGE or VERR_NO_STRING_MEMORY. + * @param offStart Where in @a this string to start replacing. + * @param cchLength How much following @a offStart to replace. npos is + * accepted. + * @param pszSrc The replacement string. + * @param cchSrc The exactly length of the replacement string. + */ + int replaceWorkerNoThrow(size_t offStart, size_t cchLength, const char *pszSrc, size_t cchSrc) RT_NOEXCEPT; + + static DECLCALLBACK(size_t) printfOutputCallback(void *pvArg, const char *pachChars, size_t cbChars); + static DECLCALLBACK(size_t) printfOutputCallbackNoThrow(void *pvArg, const char *pachChars, size_t cbChars) RT_NOEXCEPT; + + char *m_psz; /**< The string buffer. */ + size_t m_cch; /**< strlen(m_psz) - i.e. no terminator included. */ + size_t m_cbAllocated; /**< Size of buffer that m_psz points to; at least m_cbLength + 1. */ +}; + +/** @} */ + + +/** @addtogroup grp_rt_cpp_string + * @{ + */ + +/** + * Concatenate two strings. + * + * @param a_rstr1 String one. + * @param a_rstr2 String two. + * @returns the concatenate string. + * + * @relates RTCString + */ +RTDECL(const RTCString) operator+(const RTCString &a_rstr1, const RTCString &a_rstr2); + +/** + * Concatenate two strings. + * + * @param a_rstr1 String one. + * @param a_psz2 String two. + * @returns the concatenate string. + * + * @relates RTCString + */ +RTDECL(const RTCString) operator+(const RTCString &a_rstr1, const char *a_psz2); + +/** + * Concatenate two strings. + * + * @param a_psz1 String one. + * @param a_rstr2 String two. + * @returns the concatenate string. + * + * @relates RTCString + */ +RTDECL(const RTCString) operator+(const char *a_psz1, const RTCString &a_rstr2); + +/** + * Class with RTCString::printf as constructor for your convenience. + * + * Constructing a RTCString string object from a format string and a variable + * number of arguments can easily be confused with the other RTCString + * constructors, thus this child class. + * + * The usage of this class is like the following: + * @code + RTCStringFmt strName("program name = %s", argv[0]); + @endcode + */ +class RTCStringFmt : public RTCString +{ +public: + + /** + * Constructs a new string given the format string and the list of the + * arguments for the format string. + * + * @param a_pszFormat Pointer to the format string (UTF-8), + * @see pg_rt_str_format. + * @param ... Ellipsis containing the arguments specified by + * the format string. + */ + explicit RTCStringFmt(const char *a_pszFormat, ...) RT_IPRT_FORMAT_ATTR(1, 2) + { + va_list va; + va_start(va, a_pszFormat); + printfV(a_pszFormat, va); + va_end(va); + } + + RTMEMEF_NEW_AND_DELETE_OPERATORS(); + +protected: + RTCStringFmt() {} +}; + +/** @} */ + +#endif /* !IPRT_INCLUDED_cpp_ministring_h */ + diff --git a/include/iprt/cpp/mtlist.h b/include/iprt/cpp/mtlist.h new file mode 100644 index 00000000..7c4d6f1a --- /dev/null +++ b/include/iprt/cpp/mtlist.h @@ -0,0 +1,185 @@ +/** @file + * IPRT - Generic thread-safe list Class. + */ + +/* + * Copyright (C) 2011-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#ifndef IPRT_INCLUDED_cpp_mtlist_h +#define IPRT_INCLUDED_cpp_mtlist_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include <iprt/cpp/list.h> +#include <iprt/semaphore.h> + +/** @addtogroup grp_rt_cpp_list + * @{ + */ + +/** + * A guard class for thread-safe read/write access. + */ +template <> +class RTCListGuard<true> +{ +public: + RTCListGuard() : m_hRWSem(NIL_RTSEMRW) + { +#if defined(RT_LOCK_STRICT_ORDER) && defined(IN_RING3) + RTLOCKVALCLASS hClass; + int rc = RTLockValidatorClassCreate(&hClass, true /*fAutodidact*/, RT_SRC_POS, "RTCListGuard"); + AssertStmt(RT_SUCCESS(rc), hClass = NIL_RTLOCKVALCLASS); + rc = RTSemRWCreateEx(&m_hRWSem, 0 /*fFlags*/, hClass, RTLOCKVAL_SUB_CLASS_NONE, NULL /*pszNameFmt*/); + AssertRC(rc); +#else + int rc = RTSemRWCreateEx(&m_hRWSem, 0 /*fFlags*/, NIL_RTLOCKVALCLASS, 0, NULL); + AssertRC(rc); +#endif + } + + ~RTCListGuard() + { + RTSemRWDestroy(m_hRWSem); + m_hRWSem = NIL_RTSEMRW; + } + + inline void enterRead() const { int rc = RTSemRWRequestRead(m_hRWSem, RT_INDEFINITE_WAIT); AssertRC(rc); } + inline void leaveRead() const { int rc = RTSemRWReleaseRead(m_hRWSem); AssertRC(rc); } + inline void enterWrite() { int rc = RTSemRWRequestWrite(m_hRWSem, RT_INDEFINITE_WAIT); AssertRC(rc); } + inline void leaveWrite() { int rc = RTSemRWReleaseWrite(m_hRWSem); AssertRC(rc); } + + /* Define our own new and delete. */ + RTMEMEF_NEW_AND_DELETE_OPERATORS(); + +private: + mutable RTSEMRW m_hRWSem; +}; + +/** + * @brief Generic thread-safe list class. + * + * RTCMTList is a thread-safe implementation of the list class. It uses a + * read/write semaphore to serialize the access to the items. Several readers + * can simultaneous access different or the same item. If one thread is writing + * to an item, the other accessors are blocked until the write has finished. + * + * Although the access is guarded, the user has to make sure the list content + * is consistent when iterating over the list or doing any other kind of access + * which makes assumptions about the list content. For a finer control of access + * restrictions, use your own locking mechanism and the standard list + * implementation. + * + * @see RTCListBase + */ +template <class T, typename ITYPE = typename RTCIf<(sizeof(T) > sizeof(void*)), T*, T>::result> +class RTCMTList : public RTCListBase<T, ITYPE, true> +{ + /* Traits */ + typedef RTCListBase<T, ITYPE, true> BASE; + +public: + /** + * Creates a new list. + * + * This preallocates @a cCapacity elements within the list. + * + * @param cCapacity The initial capacity the list has. + * @throws std::bad_alloc + */ + RTCMTList(size_t cCapacity = BASE::kDefaultCapacity) + : BASE(cCapacity) {} + + /* Define our own new and delete. */ + RTMEMEF_NEW_AND_DELETE_OPERATORS(); +}; + +/** + * Specialized thread-safe list class for using the native type list for + * unsigned 64-bit values even on a 32-bit host. + * + * @see RTCListBase + */ +template <> +class RTCMTList<uint64_t>: public RTCListBase<uint64_t, uint64_t, true> +{ + /* Traits */ + typedef RTCListBase<uint64_t, uint64_t, true> BASE; + +public: + /** + * Creates a new list. + * + * This preallocates @a cCapacity elements within the list. + * + * @param cCapacity The initial capacity the list has. + * @throws std::bad_alloc + */ + RTCMTList(size_t cCapacity = BASE::kDefaultCapacity) + : BASE(cCapacity) {} + + /* Define our own new and delete. */ + RTMEMEF_NEW_AND_DELETE_OPERATORS(); +}; + +/** + * Specialized thread-safe list class for using the native type list for + * signed 64-bit values even on a 32-bit host. + * + * @see RTCListBase + */ +template <> +class RTCMTList<int64_t>: public RTCListBase<int64_t, int64_t, true> +{ + /* Traits */ + typedef RTCListBase<int64_t, int64_t, true> BASE; + +public: + /** + * Creates a new list. + * + * This preallocates @a cCapacity elements within the list. + * + * @param cCapacity The initial capacity the list has. + * @throws std::bad_alloc + */ + RTCMTList(size_t cCapacity = BASE::kDefaultCapacity) + : BASE(cCapacity) {} + + /* Define our own new and delete. */ + RTMEMEF_NEW_AND_DELETE_OPERATORS(); +}; + +/** @} */ + +#endif /* !IPRT_INCLUDED_cpp_mtlist_h */ + diff --git a/include/iprt/cpp/path.h b/include/iprt/cpp/path.h new file mode 100644 index 00000000..445b9b59 --- /dev/null +++ b/include/iprt/cpp/path.h @@ -0,0 +1,249 @@ +/** @file + * IPRT - C++ path utilities. + */ + +/* + * Copyright (C) 2017-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#ifndef IPRT_INCLUDED_cpp_path_h +#define IPRT_INCLUDED_cpp_path_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include <iprt/assert.h> +#include <iprt/errcore.h> +#include <iprt/path.h> +#include <iprt/cpp/ministring.h> + + +/** @defgroup grp_rt_cpp_path C++ Path Utilities + * @ingroup grp_rt_cpp + * @{ + */ + +/** + * RTPathAbs() wrapper for working directly on a RTCString instance. + * + * @returns IPRT status code. + * @param rStrAbs Reference to the destination string. + * @param pszRelative The relative source string. + */ +DECLINLINE(int) RTPathAbsCxx(RTCString &rStrAbs, const char *pszRelative) +{ + Assert(rStrAbs.c_str() != pszRelative); + int rc = rStrAbs.reserveNoThrow(RTPATH_MAX); + if (RT_SUCCESS(rc)) + { + unsigned cTries = 8; + for (;;) + { + char *pszDst = rStrAbs.mutableRaw(); + size_t cbCap = rStrAbs.capacity(); + rc = RTPathAbsEx(NULL, pszRelative, RTPATH_STR_F_STYLE_HOST, pszDst, &cbCap); + if (RT_SUCCESS(rc)) + break; + *pszDst = '\0'; + if (rc != VERR_BUFFER_OVERFLOW) + break; + if (--cTries == 0) + break; + rc = rStrAbs.reserveNoThrow(RT_MIN(RT_ALIGN_Z(cbCap, 64), RTPATH_MAX)); + if (RT_FAILURE(rc)) + break; + } + rStrAbs.jolt(); + } + return rc; +} + + +/** + * RTPathAbs() wrapper for working directly on a RTCString instance. + * + * @returns IPRT status code. + * @param rStrAbs Reference to the destination string. + * @param rStrRelative Reference to the relative source string. + */ +DECLINLINE(int) RTPathAbsCxx(RTCString &rStrAbs, RTCString const &rStrRelative) +{ + return RTPathAbsCxx(rStrAbs, rStrRelative.c_str()); +} + + + +/** + * RTPathAbsEx() wrapper for working directly on a RTCString instance. + * + * @returns IPRT status code. + * @param rStrAbs Reference to the destination string. + * @param pszBase The base path, optional. + * @param pszRelative The relative source string. + * @param fFlags RTPATH_STR_F_STYLE_XXX and RTPATHABS_F_XXX flags. + */ +DECLINLINE(int) RTPathAbsExCxx(RTCString &rStrAbs, const char *pszBase, const char *pszRelative, uint32_t fFlags = RTPATH_STR_F_STYLE_HOST) +{ + Assert(rStrAbs.c_str() != pszRelative); + int rc = rStrAbs.reserveNoThrow(RTPATH_MAX); + if (RT_SUCCESS(rc)) + { + unsigned cTries = 8; + for (;;) + { + char *pszDst = rStrAbs.mutableRaw(); + size_t cbCap = rStrAbs.capacity(); + rc = RTPathAbsEx(pszBase, pszRelative, fFlags, pszDst, &cbCap); + if (RT_SUCCESS(rc)) + break; + *pszDst = '\0'; + if (rc != VERR_BUFFER_OVERFLOW) + break; + if (--cTries == 0) + break; + rc = rStrAbs.reserveNoThrow(RT_MIN(RT_ALIGN_Z(cbCap, 64), RTPATH_MAX)); + if (RT_FAILURE(rc)) + break; + } + rStrAbs.jolt(); + } + return rc; +} + + +DECLINLINE(int) RTPathAbsExCxx(RTCString &rStrAbs, RTCString const &rStrBase, RTCString const &rStrRelative, uint32_t fFlags = RTPATH_STR_F_STYLE_HOST) +{ + return RTPathAbsExCxx(rStrAbs, rStrBase.c_str(), rStrRelative.c_str(), fFlags); +} + + +DECLINLINE(int) RTPathAbsExCxx(RTCString &rStrAbs, const char *pszBase, RTCString const &rStrRelative, uint32_t fFlags = RTPATH_STR_F_STYLE_HOST) +{ + return RTPathAbsExCxx(rStrAbs, pszBase, rStrRelative.c_str(), fFlags); +} + + +DECLINLINE(int) RTPathAbsExCxx(RTCString &rStrAbs, RTCString const &rStrBase, const char *pszRelative, uint32_t fFlags = RTPATH_STR_F_STYLE_HOST) +{ + return RTPathAbsExCxx(rStrAbs, rStrBase.c_str(), pszRelative, fFlags); +} + + + +/** + * RTPathAppPrivateNoArch() wrapper for working directly on a RTCString instance. + * + * @returns IPRT status code. + * @param rStrDst Reference to the destination string. + */ +DECLINLINE(int) RTPathAppPrivateNoArchCxx(RTCString &rStrDst) +{ + int rc = rStrDst.reserveNoThrow(RTPATH_MAX); + if (RT_SUCCESS(rc)) + { + char *pszDst = rStrDst.mutableRaw(); + rc = RTPathAppPrivateNoArch(pszDst, rStrDst.capacity()); + if (RT_FAILURE(rc)) + *pszDst = '\0'; + rStrDst.jolt(); + } + return rc; + +} + + +/** + * RTPathAppend() wrapper for working directly on a RTCString instance. + * + * @returns IPRT status code. + * @param rStrDst Reference to the destination string. + * @param pszAppend One or more components to append to the path already + * present in @a rStrDst. + */ +DECLINLINE(int) RTPathAppendCxx(RTCString &rStrDst, const char *pszAppend) +{ + Assert(rStrDst.c_str() != pszAppend); + size_t cbEstimate = rStrDst.length() + 1 + strlen(pszAppend) + 1; + int rc; + if (rStrDst.capacity() >= cbEstimate) + rc = VINF_SUCCESS; + else + rc = rStrDst.reserveNoThrow(RT_ALIGN_Z(cbEstimate, 8)); + if (RT_SUCCESS(rc)) + { + rc = RTPathAppend(rStrDst.mutableRaw(), rStrDst.capacity(), pszAppend); + if (rc == VERR_BUFFER_OVERFLOW) + { + rc = rStrDst.reserveNoThrow(RTPATH_MAX); + if (RT_SUCCESS(rc)) + rc = RTPathAppend(rStrDst.mutableRaw(), rStrDst.capacity(), pszAppend); + } + rStrDst.jolt(); + } + return rc; +} + + +/** + * RTPathAppend() wrapper for working directly on a RTCString instance. + * + * @returns IPRT status code. + * @param rStrDst Reference to the destination string. + * @param rStrAppend One or more components to append to the path already + * present in @a rStrDst. + */ +DECLINLINE(int) RTPathAppendCxx(RTCString &rStrDst, RTCString const &rStrAppend) +{ + Assert(&rStrDst != &rStrAppend); + size_t cbEstimate = rStrDst.length() + 1 + rStrAppend.length() + 1; + int rc; + if (rStrDst.capacity() >= cbEstimate) + rc = VINF_SUCCESS; + else + rc = rStrDst.reserveNoThrow(RT_ALIGN_Z(cbEstimate, 8)); + if (RT_SUCCESS(rc)) + { + rc = RTPathAppend(rStrDst.mutableRaw(), rStrDst.capacity(), rStrAppend.c_str()); + if (rc == VERR_BUFFER_OVERFLOW) + { + rc = rStrDst.reserveNoThrow(RTPATH_MAX); + if (RT_SUCCESS(rc)) + rc = RTPathAppend(rStrDst.mutableRaw(), rStrDst.capacity(), rStrAppend.c_str()); + } + rStrDst.jolt(); + } + return rc; +} + + +/** @} */ + +#endif /* !IPRT_INCLUDED_cpp_path_h */ + diff --git a/include/iprt/cpp/restanyobject.h b/include/iprt/cpp/restanyobject.h new file mode 100644 index 00000000..54a977dd --- /dev/null +++ b/include/iprt/cpp/restanyobject.h @@ -0,0 +1,139 @@ +/** @file + * IPRT - C++ Representational State Transfer (REST) Any Object Class. + */ + +/* + * Copyright (C) 2008-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#ifndef IPRT_INCLUDED_cpp_restanyobject_h +#define IPRT_INCLUDED_cpp_restanyobject_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include <iprt/cpp/restbase.h> +#include <iprt/cpp/restarray.h> +#include <iprt/cpp/reststringmap.h> + + +/** @defgroup grp_rt_cpp_restanyobj C++ Representational State Transfer (REST) Any Object Class. + * @ingroup grp_rt_cpp + * @{ + */ + +/** + * Wrapper object that can represent any kind of basic REST object. + * + * This class is the result of a couple of design choices made in our REST + * data model. If could have been avoided if we used pointers all over + * the place and didn't rely entirely on the object specific implementations + * of deserializeFromJson and fromString to do the deserializing or everything. + * + * The assumption, though, was that most of the data we're dealing with has a + * known structure and maps to fixed types. So, the data model was optimized + * for that rather than flexiblity here. + */ +class RT_DECL_CLASS RTCRestAnyObject : public RTCRestObjectBase +{ +public: + /** Default constructor. */ + RTCRestAnyObject() RT_NOEXCEPT; + /** Destructor. */ + virtual ~RTCRestAnyObject(); + + /** Copy constructor. */ + RTCRestAnyObject(RTCRestAnyObject const &a_rThat); + /** Copy assignment operator. */ + RTCRestAnyObject &operator=(RTCRestAnyObject const &a_rThat); + + /** Safe copy assignment method. */ + int assignCopy(RTCRestAnyObject const &a_rThat) RT_NOEXCEPT; + /** Safe copy assignment method, boolean variant. */ + int assignCopy(RTCRestBool const &a_rThat) RT_NOEXCEPT; + /** Safe copy assignment method, int64_t variant. */ + int assignCopy(RTCRestInt64 const &a_rThat) RT_NOEXCEPT; + /** Safe copy assignment method, int32_t variant. */ + int assignCopy(RTCRestInt32 const &a_rThat) RT_NOEXCEPT; + /** Safe copy assignment method, int16_t variant. */ + int assignCopy(RTCRestInt16 const &a_rThat) RT_NOEXCEPT; + /** Safe copy assignment method, double variant. */ + int assignCopy(RTCRestDouble const &a_rThat) RT_NOEXCEPT; + /** Safe copy assignment method, string variant. */ + int assignCopy(RTCRestString const &a_rThat) RT_NOEXCEPT; + /** Safe copy assignment method, array variant. */ + int assignCopy(RTCRestArray<RTCRestAnyObject> const &a_rThat) RT_NOEXCEPT; + /** Safe copy assignment method, string map variant. */ + int assignCopy(RTCRestStringMap<RTCRestAnyObject> const &a_rThat) RT_NOEXCEPT; + + /** Safe value assignment method, boolean variant. */ + int assignValue(bool a_fValue) RT_NOEXCEPT; + /** Safe value assignment method, int64_t variant. */ + int assignValue(int64_t a_iValue) RT_NOEXCEPT; + /** Safe value assignment method, int32_t variant. */ + int assignValue(int32_t a_iValue) RT_NOEXCEPT; + /** Safe value assignment method, int16_t variant. */ + int assignValue(int16_t a_iValue) RT_NOEXCEPT; + /** Safe value assignment method, double variant. */ + int assignValue(double a_iValue) RT_NOEXCEPT; + /** Safe value assignment method, string variant. */ + int assignValue(RTCString const &a_rValue) RT_NOEXCEPT; + /** Safe value assignment method, C-string variant. */ + int assignValue(const char *a_pszValue) RT_NOEXCEPT; + + /** Make a clone of this object. */ + inline RTCRestAnyObject *clone() const RT_NOEXCEPT { return (RTCRestAnyObject *)baseClone(); } + + /* Overridden methods: */ + virtual RTCRestObjectBase *baseClone() const RT_NOEXCEPT RT_OVERRIDE; + virtual int setNull(void) RT_NOEXCEPT RT_OVERRIDE; + virtual int resetToDefault() RT_NOEXCEPT RT_OVERRIDE; + virtual RTCRestOutputBase &serializeAsJson(RTCRestOutputBase &a_rDst) const RT_NOEXCEPT RT_OVERRIDE; + virtual int deserializeFromJson(RTCRestJsonCursor const &a_rCursor) RT_NOEXCEPT RT_OVERRIDE; + virtual int toString(RTCString *a_pDst, uint32_t a_fFlags = kCollectionFormat_Unspecified) const RT_NOEXCEPT RT_OVERRIDE; + virtual int fromString(RTCString const &a_rValue, const char *a_pszName, PRTERRINFO a_pErrInfo = NULL, + uint32_t a_fFlags = kCollectionFormat_Unspecified) RT_NOEXCEPT RT_OVERRIDE; + virtual kTypeClass typeClass(void) const RT_NOEXCEPT RT_OVERRIDE; + virtual const char *typeName(void) const RT_NOEXCEPT RT_OVERRIDE; + + /** Factory method. */ + static DECLCALLBACK(RTCRestObjectBase *) createInstance(void) RT_NOEXCEPT; + /** Deserialization w/ instantiation. */ + static FNDESERIALIZEINSTANCEFROMJSON deserializeInstanceFromJson; + +protected: + /** The data. */ + RTCRestObjectBase *m_pData; +}; + +/** @} */ + +#endif /* !IPRT_INCLUDED_cpp_restanyobject_h */ + diff --git a/include/iprt/cpp/restarray.h b/include/iprt/cpp/restarray.h new file mode 100644 index 00000000..d20d6802 --- /dev/null +++ b/include/iprt/cpp/restarray.h @@ -0,0 +1,463 @@ +/** @file + * IPRT - C++ Representational State Transfer (REST) Array Template Class. + */ + +/* + * Copyright (C) 2008-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#ifndef IPRT_INCLUDED_cpp_restarray_h +#define IPRT_INCLUDED_cpp_restarray_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include <iprt/cpp/restbase.h> + + +/** @defgroup grp_rt_cpp_restarray C++ Representational State Transfer (REST) Array Template Class. + * @ingroup grp_rt_cpp + * @{ + */ + +/** + * Abstract base class for the RTCRestArray template. + */ +class RT_DECL_CLASS RTCRestArrayBase : public RTCRestObjectBase +{ +public: + /** Default destructor. */ + RTCRestArrayBase() RT_NOEXCEPT; + /** Destructor. */ + virtual ~RTCRestArrayBase(); + + /* Overridden methods: */ + virtual RTCRestObjectBase *baseClone() const RT_NOEXCEPT RT_OVERRIDE; + virtual int resetToDefault() RT_NOEXCEPT RT_OVERRIDE; + virtual RTCRestOutputBase &serializeAsJson(RTCRestOutputBase &a_rDst) const RT_NOEXCEPT RT_OVERRIDE; + virtual int deserializeFromJson(RTCRestJsonCursor const &a_rCursor) RT_NOEXCEPT RT_OVERRIDE; + virtual int toString(RTCString *a_pDst, uint32_t a_fFlags = kCollectionFormat_Unspecified) const RT_NOEXCEPT RT_OVERRIDE; + virtual int fromString(RTCString const &a_rValue, const char *a_pszName, PRTERRINFO a_pErrInfo = NULL, + uint32_t a_fFlags = kCollectionFormat_Unspecified) RT_NOEXCEPT RT_OVERRIDE; + virtual kTypeClass typeClass(void) const RT_NOEXCEPT RT_OVERRIDE; + virtual const char *typeName(void) const RT_NOEXCEPT RT_OVERRIDE; + + /** + * Clear the content of the map. + */ + void clear() RT_NOEXCEPT; + + /** + * Check if an list contains any items. + * + * @return True if there is more than zero items, false otherwise. + */ + inline bool isEmpty() const RT_NOEXCEPT + { + return m_cElements == 0; + } + + /** + * Gets the number of entries in the map. + */ + inline size_t size() const RT_NOEXCEPT + { + return m_cElements; + } + + /** + * Returns the base object pointer at a given index. + * + * @returns The base object at @a a_idx, NULL if out of range. + * @param a_idx The array index. + */ + inline RTCRestObjectBase *atBase(size_t a_idx) RT_NOEXCEPT + { + if (a_idx < m_cElements) + return m_papElements[a_idx]; + return NULL; + } + + /** + * Returns the const base object pointer at a given index. + * + * @returns The base object at @a a_idx, NULL if out of range. + * @param a_idx The array index. + */ + inline RTCRestObjectBase const *atBase(size_t a_idx) const RT_NOEXCEPT + { + if (a_idx < m_cElements) + return m_papElements[a_idx]; + return NULL; + } + + /** + * Removes the element at @a a_idx. + * @returns true if @a a_idx is valid, false if out of range. + * @param a_idx The index of the element to remove. + * The value ~(size_t)0 is an alias for the final element. + */ + bool removeAt(size_t a_idx) RT_NOEXCEPT; + + /** + * Makes sure the array can hold at the given number of entries. + * + * @returns VINF_SUCCESS or VERR_NO_MEMORY. + * @param a_cEnsureCapacity The number of elements to ensure capacity to hold. + */ + int ensureCapacity(size_t a_cEnsureCapacity) RT_NOEXCEPT; + + +protected: + /** The array. */ + RTCRestObjectBase **m_papElements; + /** Number of elements in the array. */ + size_t m_cElements; + /** The number of elements m_papElements can hold. + * The difference between m_cCapacity and m_cElements are all NULLs. */ + size_t m_cCapacity; + + /** + * Helper for creating a clone. + * + * @returns Pointer to new array on success, NULL if out of memory. + */ + virtual RTCRestArrayBase *createClone(void) const RT_NOEXCEPT = 0; + + /** + * Wrapper around the value constructor. + * + * @returns Pointer to new value object on success, NULL if out of memory. + */ + virtual RTCRestObjectBase *createValue(void) RT_NOEXCEPT = 0; + + /** + * For accessing the static deserializeInstanceFromJson() method of the value. + */ + virtual int deserializeValueInstanceFromJson(RTCRestJsonCursor const &a_rCursor, RTCRestObjectBase **a_ppInstance) RT_NOEXCEPT = 0; + + /** + * Worker for the copy assignment method and copyArrayWorkerMayThrow(). + * + * This will use createEntryCopy to do the copying. + * + * @returns VINF_SUCCESS on success, VERR_NO_MEMORY or VERR_NO_STR_MEMORY on failure. + * @param a_rThat The array to copy. Caller makes 100% sure the it has + * the same type as the destination. + */ + int copyArrayWorkerNoThrow(RTCRestArrayBase const &a_rThat) RT_NOEXCEPT; + + /** + * Wrapper around copyArrayWorkerNoThrow for the copy constructor and the + * assignment operator. + */ + void copyArrayWorkerMayThrow(RTCRestArrayBase const &a_rThat); + + /** + * Worker for performing inserts. + * + * @returns VINF_SUCCESS or VWRN_ALREADY_EXISTS on success. + * VERR_ALREADY_EXISTS, VERR_NO_MEMORY or VERR_NO_STR_MEMORY on failure. + * @param a_idx Where to insert it. The value ~(size_t)0 is an alias for m_cElements. + * @param a_pValue The value to insert. Ownership is transferred to the map on success. + * @param a_fReplace Whether to replace existing entry rather than insert. + */ + int insertWorker(size_t a_idx, RTCRestObjectBase *a_pValue, bool a_fReplace) RT_NOEXCEPT; + + /** + * Worker for performing inserts. + * + * @returns VINF_SUCCESS or VWRN_ALREADY_EXISTS on success. + * VERR_ALREADY_EXISTS, VERR_NO_MEMORY or VERR_NO_STR_MEMORY on failure. + * @param a_idx Where to insert it. The value ~(size_t)0 is an alias for m_cElements. + * @param a_rValue The value to copy into the map. + * @param a_fReplace Whether to replace existing key-value pair with matching key. + */ + int insertCopyWorker(size_t a_idx, RTCRestObjectBase const &a_rValue, bool a_fReplace) RT_NOEXCEPT; + +private: + /** Copy constructor on this class should never be used. */ + RTCRestArrayBase(RTCRestArrayBase const &a_rThat); + /** Copy assignment operator on this class should never be used. */ + RTCRestArrayBase &operator=(RTCRestArrayBase const &a_rThat); +}; + + + +/** + * Limited array class. + */ +template<class ElementType> class RTCRestArray : public RTCRestArrayBase +{ +public: + /** Default constructor - empty array. */ + RTCRestArray() RT_NOEXCEPT + : RTCRestArrayBase() + { + } + + /** Destructor. */ + ~RTCRestArray() + { + } + + /** Copy constructor. */ + RTCRestArray(RTCRestArray const &a_rThat) + : RTCRestArrayBase() + { + copyArrayWorkerMayThrow(a_rThat); + } + + /** Copy assignment operator. */ + inline RTCRestArray &operator=(RTCRestArray const &a_rThat) + { + copyArrayWorkerMayThrow(a_rThat); + return *this; + } + + /** Safe copy assignment method. */ + inline int assignCopy(RTCRestArray const &a_rThat) RT_NOEXCEPT + { + return copyArrayWorkerNoThrow(a_rThat); + } + + /** Make a clone of this object. */ + inline RTCRestArray *clone() const RT_NOEXCEPT + { + return (RTCRestArray *)baseClone(); + } + + /** Factory method. */ + static DECLCALLBACK(RTCRestObjectBase *) createInstance(void) RT_NOEXCEPT + { + return new (std::nothrow) RTCRestArray<ElementType>(); + } + + /** Factory method for elements. */ + static DECLCALLBACK(RTCRestObjectBase *) createElementInstance(void) RT_NOEXCEPT + { + return new (std::nothrow) ElementType(); + } + + /** @copydoc RTCRestObjectBase::FNDESERIALIZEINSTANCEFROMJSON */ + static DECLCALLBACK(int) deserializeInstanceFromJson(RTCRestJsonCursor const &a_rCursor, RTCRestObjectBase **a_ppInstance) RT_NOEXCEPT + { + *a_ppInstance = new (std::nothrow) RTCRestArray<ElementType>(); + if (*a_ppInstance) + return (*a_ppInstance)->deserializeFromJson(a_rCursor); + return a_rCursor.m_pPrimary->addError(a_rCursor, VERR_NO_MEMORY, "Out of memory"); + } + + + /** + * Insert the given object at the specified index. + * + * @returns VINF_SUCCESS on success. + * VERR_INVALID_POINTER, VERR_NO_MEMORY, VERR_NO_STR_MEMORY or VERR_OUT_OF_RANGE on failure. + * @param a_idx The insertion index. ~(size_t)0 is an alias for the end. + * @param a_pThat The object to insert. The array takes ownership of the object on success. + */ + inline int insert(size_t a_idx, ElementType *a_pThat) RT_NOEXCEPT + { + return insertWorker(a_idx, a_pThat, false /*a_fReplace*/); + } + + /** + * Insert a copy of the object at the specified index. + * + * @returns VINF_SUCCESS on success. + * VERR_NO_MEMORY, VERR_NO_STR_MEMORY or VERR_OUT_OF_RANGE on failure. + * @param a_idx The insertion index. ~(size_t)0 is an alias for the end. + * @param a_rThat The object to insert a copy of. + */ + inline int insertCopy(size_t a_idx, ElementType const &a_rThat) RT_NOEXCEPT + { + return insertCopyWorker(a_idx, a_rThat, false /*a_fReplace*/); + } + + /** + * Appends the given object to the array. + * + * @returns VINF_SUCCESS on success. + * VERR_INVALID_POINTER, VERR_NO_MEMORY, VERR_NO_STR_MEMORY or VERR_OUT_OF_RANGE on failure. + * @param a_pThat The object to insert. The array takes ownership of the object on success. + */ + inline int append(ElementType *a_pThat) RT_NOEXCEPT + { + return insertWorker(~(size_t)0, a_pThat, false /*a_fReplace*/); + } + + /** + * Appends a copy of the object at the specified index. + * + * @returns VINF_SUCCESS on success. + * VERR_NO_MEMORY, VERR_NO_STR_MEMORY or VERR_OUT_OF_RANGE on failure. + * @param a_rThat The object to insert a copy of. + */ + inline int appendCopy(ElementType const &a_rThat) RT_NOEXCEPT + { + return insertCopyWorker(~(size_t)0, a_rThat, false /*a_fReplace*/); + } + + /** + * Prepends the given object to the array. + * + * @returns VINF_SUCCESS on success. + * VERR_INVALID_POINTER, VERR_NO_MEMORY, VERR_NO_STR_MEMORY or VERR_OUT_OF_RANGE on failure. + * @param a_pThat The object to insert. The array takes ownership of the object on success. + */ + inline int prepend(ElementType *a_pThat) RT_NOEXCEPT + { + return insertWorker(0, a_pThat, false /*a_fReplace*/); + } + + /** + * Prepends a copy of the object at the specified index. + * + * @returns VINF_SUCCESS on success. + * VERR_NO_MEMORY, VERR_NO_STR_MEMORY or VERR_OUT_OF_RANGE on failure. + * @param a_rThat The object to insert a copy of. + */ + inline int prependCopy(ElementType const &a_rThat) RT_NOEXCEPT + { + return insertCopyWorker(0, a_rThat, false /*a_fReplace*/); + } + + /** + * Insert the given object at the specified index. + * + * @returns VINF_SUCCESS on success. + * VERR_INVALID_POINTER, VERR_NO_MEMORY, VERR_NO_STR_MEMORY or VERR_OUT_OF_RANGE on failure. + * @param a_idx The index of the existing object to replace. + * @param a_pThat The replacement object. The array takes ownership of the object on success. + */ + inline int replace(size_t a_idx, ElementType *a_pThat) RT_NOEXCEPT + { + return insertWorker(a_idx, a_pThat, true /*a_fReplace*/); + } + + /** + * Insert a copy of the object at the specified index. + * + * @returns VINF_SUCCESS on success. + * VERR_NO_MEMORY, VERR_NO_STR_MEMORY or VERR_OUT_OF_RANGE on failure. + * @param a_idx The index of the existing object to replace. + * @param a_rThat The object to insert a copy of. + */ + inline int replaceCopy(size_t a_idx, ElementType const &a_rThat) RT_NOEXCEPT + { + return insertCopyWorker(a_idx, a_rThat, true /*a_fReplace*/); + } + + /** + * Returns the object at a given index. + * + * @returns The object at @a a_idx, NULL if out of range. + * @param a_idx The array index. + */ + inline ElementType *at(size_t a_idx) RT_NOEXCEPT + { + if (a_idx < m_cElements) + return (ElementType *)m_papElements[a_idx]; + return NULL; + } + + /** + * Returns the object at a given index, const variant. + * + * @returns The object at @a a_idx, NULL if out of range. + * @param a_idx The array index. + */ + inline ElementType const *at(size_t a_idx) const RT_NOEXCEPT + { + if (a_idx < m_cElements) + return (ElementType const *)m_papElements[a_idx]; + return NULL; + } + + /** + * Returns the first object in the array. + * @returns The first object, NULL if empty. + */ + inline ElementType *first() RT_NOEXCEPT + { + return at(0); + } + + /** + * Returns the first object in the array, const variant. + * @returns The first object, NULL if empty. + */ + inline ElementType const *first() const RT_NOEXCEPT + { + return at(0); + } + + /** + * Returns the last object in the array. + * @returns The last object, NULL if empty. + */ + inline ElementType *last() RT_NOEXCEPT + { + return at(m_cElements - 1); + } + + /** + * Returns the last object in the array, const variant. + * @returns The last object, NULL if empty. + */ + inline ElementType const *last() const RT_NOEXCEPT + { + return at(m_cElements - 1); + } + + +protected: + virtual RTCRestArrayBase *createClone(void) const RT_NOEXCEPT RT_OVERRIDE + { + return new (std::nothrow) RTCRestArray(); + } + + virtual RTCRestObjectBase *createValue(void) RT_NOEXCEPT RT_OVERRIDE + { + return new (std::nothrow) ElementType(); + } + + virtual int deserializeValueInstanceFromJson(RTCRestJsonCursor const &a_rCursor, RTCRestObjectBase **a_ppInstance) RT_NOEXCEPT RT_OVERRIDE + { + return ElementType::deserializeInstanceFromJson(a_rCursor, a_ppInstance); + } +}; + + +/** @} */ + +#endif /* !IPRT_INCLUDED_cpp_restarray_h */ + diff --git a/include/iprt/cpp/restbase.h b/include/iprt/cpp/restbase.h new file mode 100644 index 00000000..1fefc439 --- /dev/null +++ b/include/iprt/cpp/restbase.h @@ -0,0 +1,1106 @@ +/** @file + * IPRT - C++ Representational State Transfer (REST) Base Classes. + */ + +/* + * Copyright (C) 2008-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#ifndef IPRT_INCLUDED_cpp_restbase_h +#define IPRT_INCLUDED_cpp_restbase_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include <iprt/cdefs.h> +#include <iprt/types.h> +#include <iprt/errcore.h> /* VERR_NO_MEMORY */ +#include <iprt/json.h> +#include <iprt/stdarg.h> +#include <iprt/time.h> +#include <iprt/cpp/ministring.h> + + +/** @defgroup grp_rt_cpp_restbase C++ Representational State Transfer (REST) Base Classes. + * @ingroup grp_rt_cpp + * @{ + */ + +/* forward decl: */ +class RTCRestOutputBase; +class RTCRestJsonPrimaryCursor; + +/** + * JSON cursor structure. + * + * This reduces the number of parameters passed around when deserializing JSON + * input and also helps constructing full object name for logging and error reporting. + */ +struct RT_DECL_CLASS RTCRestJsonCursor +{ + /** Handle to the value being parsed. */ + RTJSONVAL m_hValue; + /** Name of the value. */ + const char *m_pszName; + /** Pointer to the parent, NULL if primary. */ + struct RTCRestJsonCursor const *m_pParent; + /** Pointer to the primary cursor structure. */ + RTCRestJsonPrimaryCursor *m_pPrimary; + + RTCRestJsonCursor(struct RTCRestJsonCursor const &a_rParent) RT_NOEXCEPT + : m_hValue(NIL_RTJSONVAL), m_pszName(NULL), m_pParent(&a_rParent), m_pPrimary(a_rParent.m_pPrimary) + { } + + RTCRestJsonCursor(RTJSONVAL hValue, const char *pszName, struct RTCRestJsonCursor *pParent) RT_NOEXCEPT + : m_hValue(hValue), m_pszName(pszName), m_pParent(pParent), m_pPrimary(pParent->m_pPrimary) + { } + + RTCRestJsonCursor(RTJSONVAL hValue, const char *pszName) RT_NOEXCEPT + : m_hValue(hValue), m_pszName(pszName), m_pParent(NULL), m_pPrimary(NULL) + { } + + ~RTCRestJsonCursor() + { + if (m_hValue != NIL_RTJSONVAL) + { + RTJsonValueRelease(m_hValue); + m_hValue = NIL_RTJSONVAL; + } + } +}; + + +/** + * The primary JSON cursor class. + */ +class RT_DECL_CLASS RTCRestJsonPrimaryCursor +{ +public: + /** The cursor for the first level. */ + RTCRestJsonCursor m_Cursor; + /** Error info keeper. */ + PRTERRINFO m_pErrInfo; + + /** Creates a primary json cursor with optiona error info. */ + RTCRestJsonPrimaryCursor(RTJSONVAL hValue, const char *pszName, PRTERRINFO pErrInfo = NULL) RT_NOEXCEPT + : m_Cursor(hValue, pszName) + , m_pErrInfo(pErrInfo) + { + m_Cursor.m_pPrimary = this; + } + + virtual ~RTCRestJsonPrimaryCursor() + { } + + /** + * Add an error message. + * + * @returns a_rc + * @param a_rCursor The cursor reporting the error. + * @param a_rc The status code. + * @param a_pszFormat Format string. + * @param ... Format string arguments. + */ + virtual int addError(RTCRestJsonCursor const &a_rCursor, int a_rc, const char *a_pszFormat, ...) RT_NOEXCEPT; + + /** + * Reports that the current field is not known. + * + * @returns Status to propagate. + * @param a_rCursor The cursor for the field. + */ + virtual int unknownField(RTCRestJsonCursor const &a_rCursor) RT_NOEXCEPT; + + /** + * Copies the full path into pszDst. + * + * @returns pszDst + * @param a_rCursor The cursor to start walking at. + * @param a_pszDst Where to put the path. + * @param a_cbDst Size of the destination buffer. + */ + virtual char *getPath(RTCRestJsonCursor const &a_rCursor, char *a_pszDst, size_t a_cbDst) const RT_NOEXCEPT; +}; + + +/** + * Abstract base class for REST primitive types and data objects (via + * RTCRestDataObject). + * + * The only information this keeps is the null indicator. + */ +class RT_DECL_CLASS RTCRestObjectBase +{ +public: + RTCRestObjectBase() RT_NOEXCEPT; + RTCRestObjectBase(RTCRestObjectBase const &a_rThat) RT_NOEXCEPT; + virtual ~RTCRestObjectBase(); + + /** Copy assignment operator. */ + RTCRestObjectBase &operator=(RTCRestObjectBase const &a_rThat) RT_NOEXCEPT; + + /** + * Create a copy of this object. + * + * @returns Pointer to copy. + */ + virtual RTCRestObjectBase *baseClone() const RT_NOEXCEPT = 0; + + /** + * Tests if the object is @a null. + * @returns true if null, false if not. + */ + inline bool isNull(void) const RT_NOEXCEPT { return m_fNullIndicator; }; + + /** + * Sets the object to @a null and fills it with defaults. + * @returns IPRT status code (from resetToDefault). + */ + virtual int setNull(void) RT_NOEXCEPT; + + /** + * Sets the object to not-null state (i.e. undoes setNull()). + * @remarks Only really important for strings. + */ + virtual void setNotNull(void) RT_NOEXCEPT; + + /** + * Resets the object to all default values. + * @returns IPRT status code. + */ + virtual int resetToDefault() RT_NOEXCEPT = 0; + + /** + * Serialize the object as JSON. + * + * @returns a_rDst + * @param a_rDst The destination for the serialization. + */ + virtual RTCRestOutputBase &serializeAsJson(RTCRestOutputBase &a_rDst) const RT_NOEXCEPT = 0; + + /** + * Deserialize object from the given JSON iterator. + * + * @returns IPRT status code. + * @param a_rCursor The JSON cursor. + */ + virtual int deserializeFromJson(RTCRestJsonCursor const &a_rCursor) RT_NOEXCEPT = 0; + + /** + * Polymorphic JSON deserialization helper that instantiate the matching class using + * the discriminator field. + * + * @returns IPRT status code. + * @param a_rCursor The JSON cursor. + * @param a_ppInstance Where to return the deserialized instance. + * May return an object on failure. + */ + typedef DECLCALLBACKTYPE(int, FNDESERIALIZEINSTANCEFROMJSON,(RTCRestJsonCursor const &a_rCursor, RTCRestObjectBase **a_ppInstance)); + /** Pointer to a FNDESERIALIZEINSTANCEFROMJSON function. */ + typedef FNDESERIALIZEINSTANCEFROMJSON *PFNDESERIALIZEINSTANCEFROMJSON; + + /** + * Flags for toString(). + * + * The kCollectionFormat_xxx bunch controls multiple values in arrays + * are formatted. They are ignored by everyone else. + * + * @note When adding collection format types, make sure to also + * update RTCRestArrayBase::toString(). + * @note Bit 24 is reserved (for kHdrField_MapCollection). + */ + enum + { + kCollectionFormat_Unspecified = 0, /**< Not specified. */ + kCollectionFormat_csv, /**< Comma-separated list. */ + kCollectionFormat_ssv, /**< Space-separated list. */ + kCollectionFormat_tsv, /**< Tab-separated list. */ + kCollectionFormat_pipes, /**< Pipe-separated list. */ + kCollectionFormat_multi, /**< Special collection type that must be handled by caller of toString. */ + kCollectionFormat_Mask = 7, /**< Collection type mask. */ + + kToString_Append = 8 /**< Append to the string/object (rather than assigning). */ + }; + + /** + * String conversion. + * + * The default implementation of is a wrapper around serializeAsJson(). + * + * @returns IPRT status code. + * @param a_pDst Pointer to the destionation string. + * @param a_fFlags kCollectionFormat_xxx. + */ + virtual int toString(RTCString *a_pDst, uint32_t a_fFlags = kCollectionFormat_Unspecified) const RT_NOEXCEPT; + + /** + * String convertsion, naive variant. + * + * @returns String represenation. + */ + RTCString toString() const; + + /** + * Convert from (header) string value. + * + * The default implementation of is a wrapper around deserializeFromJson(). + * + * @returns IPRT status code. + * @param a_rValue The string value string to parse. + * @param a_pszName Field name or similar. + * @param a_pErrInfo Where to return additional error info. Optional. + * @param a_fFlags kCollectionFormat_xxx. + */ + virtual int fromString(RTCString const &a_rValue, const char *a_pszName, PRTERRINFO a_pErrInfo = NULL, + uint32_t a_fFlags = kCollectionFormat_Unspecified) RT_NOEXCEPT; + + /** Type classification */ + typedef enum kTypeClass + { + kTypeClass_Invalid = 0, + kTypeClass_Bool, /**< Primitive: bool. */ + kTypeClass_Int64, /**< Primitive: int64_t. */ + kTypeClass_Int32, /**< Primitive: int32_t. */ + kTypeClass_Int16, /**< Primitive: int16_t. */ + kTypeClass_Double, /**< Primitive: double. */ + kTypeClass_String, /**< Primitive: string. */ + kTypeClass_Date, /**< Date. */ + kTypeClass_Uuid, /**< UUID. */ + kTypeClass_Binary, /**< Binary blob. */ + kTypeClass_DataObject, /**< Data object child (RTCRestDataObject). */ + kTypeClass_AnyObject, /**< Any kind of object (RTCRestAnyObject). */ + kTypeClass_Array, /**< Array (containing any kind of object). */ + kTypeClass_StringMap, /**< String map (containing any kind of object). */ + kTypeClass_StringEnum /**< String enum. */ + } kTypeClass; + + /** + * Returns the object type class. + */ + virtual kTypeClass typeClass(void) const RT_NOEXCEPT = 0; + + /** + * Returns the object type name. + */ + virtual const char *typeName(void) const RT_NOEXCEPT = 0; + +protected: + /** Null indicator. + * @remarks The null values could be mapped onto C/C++ NULL pointer values, + * with the consequence that all data members in objects and such would + * have had to been allocated individually, even simple @a bool members. + * Given that we're overly paranoid about heap allocations (std::bad_alloc), + * it's more fitting to use a null indicator for us. + */ + bool m_fNullIndicator; +}; + + +/** + * Class wrapping 'bool'. + */ +class RT_DECL_CLASS RTCRestBool : public RTCRestObjectBase +{ +public: + /** Default constructor. */ + RTCRestBool() RT_NOEXCEPT; + /** Copy constructor. */ + RTCRestBool(RTCRestBool const &a_rThat) RT_NOEXCEPT; + /** From value constructor. */ + RTCRestBool(bool fValue) RT_NOEXCEPT; + /** Destructor. */ + virtual ~RTCRestBool(); + /** Copy assignment operator. */ + RTCRestBool &operator=(RTCRestBool const &a_rThat) RT_NOEXCEPT; + /** Safe copy assignment method. */ + int assignCopy(RTCRestBool const &a_rThat) RT_NOEXCEPT; + /** Assign value and clear null indicator. */ + void assignValue(bool a_fValue) RT_NOEXCEPT; + /** Make a clone of this object. */ + inline RTCRestBool *clone() const RT_NOEXCEPT { return (RTCRestBool *)baseClone(); } + + /* Overridden methods: */ + virtual RTCRestObjectBase *baseClone() const RT_NOEXCEPT RT_OVERRIDE; + virtual int resetToDefault() RT_NOEXCEPT RT_OVERRIDE; + virtual RTCRestOutputBase &serializeAsJson(RTCRestOutputBase &a_rDst) const RT_NOEXCEPT RT_OVERRIDE; + virtual int deserializeFromJson(RTCRestJsonCursor const &a_rCursor) RT_NOEXCEPT RT_OVERRIDE; + virtual int toString(RTCString *a_pDst, uint32_t a_fFlags = 0) const RT_NOEXCEPT RT_OVERRIDE; + virtual int fromString(RTCString const &a_rValue, const char *a_pszName, PRTERRINFO a_pErrInfo = NULL, + uint32_t a_fFlags = kCollectionFormat_Unspecified) RT_NOEXCEPT RT_OVERRIDE; + virtual kTypeClass typeClass(void) const RT_NOEXCEPT RT_OVERRIDE; + virtual const char *typeName(void) const RT_NOEXCEPT RT_OVERRIDE; + + /** Factory method. */ + static DECLCALLBACK(RTCRestObjectBase *) createInstance(void) RT_NOEXCEPT; + /** @copydoc RTCRestObjectBase::FNDESERIALIZEINSTANCEFROMJSON */ + static DECLCALLBACK(int) deserializeInstanceFromJson(RTCRestJsonCursor const &a_rCursor, RTCRestObjectBase **a_ppInstance) RT_NOEXCEPT; + +public: + /** The value. */ + bool m_fValue; +}; + + +/** + * Class wrapping 'int64_t'. + */ +class RT_DECL_CLASS RTCRestInt64 : public RTCRestObjectBase +{ +public: + /** Default constructor. */ + RTCRestInt64() RT_NOEXCEPT; + /** Copy constructor. */ + RTCRestInt64(RTCRestInt64 const &a_rThat) RT_NOEXCEPT; + /** From value constructor. */ + RTCRestInt64(int64_t a_iValue) RT_NOEXCEPT; + /** Destructor. */ + virtual ~RTCRestInt64(); + /** Copy assignment operator. */ + RTCRestInt64 &operator=(RTCRestInt64 const &a_rThat) RT_NOEXCEPT; + /** Safe copy assignment method. */ + int assignCopy(RTCRestInt64 const &a_rThat) RT_NOEXCEPT; + /** Assign value and clear null indicator. */ + void assignValue(int64_t a_iValue) RT_NOEXCEPT; + /** Make a clone of this object. */ + inline RTCRestInt64 *clone() const RT_NOEXCEPT { return (RTCRestInt64 *)baseClone(); } + + /* Overridden methods: */ + virtual RTCRestObjectBase *baseClone() const RT_NOEXCEPT RT_OVERRIDE; + virtual int resetToDefault() RT_NOEXCEPT RT_OVERRIDE; + virtual RTCRestOutputBase &serializeAsJson(RTCRestOutputBase &a_rDst) const RT_NOEXCEPT RT_OVERRIDE; + virtual int deserializeFromJson(RTCRestJsonCursor const &a_rCursor) RT_NOEXCEPT RT_OVERRIDE; + virtual int toString(RTCString *a_pDst, uint32_t a_fFlags = 0) const RT_NOEXCEPT RT_OVERRIDE; + virtual int fromString(RTCString const &a_rValue, const char *a_pszName, PRTERRINFO a_pErrInfo = NULL, + uint32_t a_fFlags = kCollectionFormat_Unspecified) RT_NOEXCEPT RT_OVERRIDE; + virtual kTypeClass typeClass(void) const RT_NOEXCEPT RT_OVERRIDE; + virtual const char *typeName(void) const RT_NOEXCEPT RT_OVERRIDE; + + /** Factory method. */ + static DECLCALLBACK(RTCRestObjectBase *) createInstance(void) RT_NOEXCEPT; + /** @copydoc RTCRestObjectBase::FNDESERIALIZEINSTANCEFROMJSON */ + static DECLCALLBACK(int) deserializeInstanceFromJson(RTCRestJsonCursor const &a_rCursor, RTCRestObjectBase **a_ppInstance) RT_NOEXCEPT; + +public: + /** The value. */ + int64_t m_iValue; +}; + + +/** + * Class wrapping 'int32_t'. + */ +class RT_DECL_CLASS RTCRestInt32 : public RTCRestObjectBase +{ +public: + /** Default constructor. */ + RTCRestInt32() RT_NOEXCEPT; + /** Copy constructor. */ + RTCRestInt32(RTCRestInt32 const &a_rThat) RT_NOEXCEPT; + /** From value constructor. */ + RTCRestInt32(int32_t iValue) RT_NOEXCEPT; + /** Destructor. */ + virtual ~RTCRestInt32() RT_NOEXCEPT; + /** Copy assignment operator. */ + RTCRestInt32 &operator=(RTCRestInt32 const &a_rThat) RT_NOEXCEPT; + /** Safe copy assignment method. */ + int assignCopy(RTCRestInt32 const &a_rThat) RT_NOEXCEPT; + /** Assign value and clear null indicator. */ + void assignValue(int32_t a_iValue) RT_NOEXCEPT; + /** Make a clone of this object. */ + inline RTCRestInt32 *clone() const { return (RTCRestInt32 *)baseClone(); } + + /* Overridden methods: */ + virtual RTCRestObjectBase *baseClone() const RT_NOEXCEPT RT_OVERRIDE; + virtual int resetToDefault() RT_NOEXCEPT RT_OVERRIDE; + virtual RTCRestOutputBase &serializeAsJson(RTCRestOutputBase &a_rDst) const RT_NOEXCEPT RT_OVERRIDE; + virtual int deserializeFromJson(RTCRestJsonCursor const &a_rCursor) RT_NOEXCEPT RT_OVERRIDE; + virtual int toString(RTCString *a_pDst, uint32_t a_fFlags = 0) const RT_NOEXCEPT RT_OVERRIDE; + virtual int fromString(RTCString const &a_rValue, const char *a_pszName, PRTERRINFO a_pErrInfo = NULL, + uint32_t a_fFlags = kCollectionFormat_Unspecified) RT_NOEXCEPT RT_OVERRIDE; + virtual kTypeClass typeClass(void) const RT_NOEXCEPT RT_OVERRIDE; + virtual const char *typeName(void) const RT_NOEXCEPT RT_OVERRIDE; + + /** Factory method. */ + static DECLCALLBACK(RTCRestObjectBase *) createInstance(void) RT_NOEXCEPT; + /** @copydoc RTCRestObjectBase::FNDESERIALIZEINSTANCEFROMJSON */ + static DECLCALLBACK(int) deserializeInstanceFromJson(RTCRestJsonCursor const &a_rCursor, RTCRestObjectBase **a_ppInstance) RT_NOEXCEPT; + +public: + /** The value. */ + int32_t m_iValue; +}; + + +/** + * Class wrapping 'int16_t'. + */ +class RT_DECL_CLASS RTCRestInt16 : public RTCRestObjectBase +{ +public: + /** Default constructor. */ + RTCRestInt16() RT_NOEXCEPT; + /** Copy constructor. */ + RTCRestInt16(RTCRestInt16 const &a_rThat) RT_NOEXCEPT; + /** From value constructor. */ + RTCRestInt16(int16_t iValue) RT_NOEXCEPT; + /** Destructor. */ + virtual ~RTCRestInt16(); + /** Copy assignment operator. */ + RTCRestInt16 &operator=(RTCRestInt16 const &a_rThat) RT_NOEXCEPT; + /** Safe copy assignment method. */ + int assignCopy(RTCRestInt16 const &a_rThat) RT_NOEXCEPT; + /** Assign value and clear null indicator. */ + void assignValue(int16_t a_iValue) RT_NOEXCEPT; + /** Make a clone of this object. */ + inline RTCRestInt16 *clone() const RT_NOEXCEPT { return (RTCRestInt16 *)baseClone(); } + + /* Overridden methods: */ + virtual RTCRestObjectBase *baseClone() const RT_NOEXCEPT RT_OVERRIDE; + virtual int resetToDefault() RT_NOEXCEPT RT_OVERRIDE; + virtual RTCRestOutputBase &serializeAsJson(RTCRestOutputBase &a_rDst) const RT_NOEXCEPT RT_OVERRIDE; + virtual int deserializeFromJson(RTCRestJsonCursor const &a_rCursor) RT_NOEXCEPT RT_OVERRIDE; + virtual int toString(RTCString *a_pDst, uint32_t a_fFlags = 0) const RT_NOEXCEPT RT_OVERRIDE; + virtual int fromString(RTCString const &a_rValue, const char *a_pszName, PRTERRINFO a_pErrInfo = NULL, + uint32_t a_fFlags = kCollectionFormat_Unspecified) RT_NOEXCEPT RT_OVERRIDE; + virtual kTypeClass typeClass(void) const RT_NOEXCEPT RT_OVERRIDE; + virtual const char *typeName(void) const RT_NOEXCEPT RT_OVERRIDE; + + /** Factory method. */ + static DECLCALLBACK(RTCRestObjectBase *) createInstance(void) RT_NOEXCEPT; + /** @copydoc RTCRestObjectBase::FNDESERIALIZEINSTANCEFROMJSON */ + static DECLCALLBACK(int) deserializeInstanceFromJson(RTCRestJsonCursor const &a_rCursor, RTCRestObjectBase **a_ppInstance) RT_NOEXCEPT; + +public: + /** The value. */ + int16_t m_iValue; +}; + + +/** + * Class wrapping 'double'. + */ +class RT_DECL_CLASS RTCRestDouble : public RTCRestObjectBase +{ +public: + /** Default constructor. */ + RTCRestDouble() RT_NOEXCEPT; + /** Copy constructor. */ + RTCRestDouble(RTCRestDouble const &a_rThat) RT_NOEXCEPT; + /** From value constructor. */ + RTCRestDouble(double rdValue) RT_NOEXCEPT; + /** Destructor. */ + virtual ~RTCRestDouble(); + /** Copy assignment operator. */ + RTCRestDouble &operator=(RTCRestDouble const &a_rThat) RT_NOEXCEPT; + /** Safe copy assignment method. */ + int assignCopy(RTCRestDouble const &a_rThat) RT_NOEXCEPT; + /** Assign value and clear null indicator. */ + void assignValue(double a_rdValue) RT_NOEXCEPT; + /** Make a clone of this object. */ + inline RTCRestDouble *clone() const RT_NOEXCEPT { return (RTCRestDouble *)baseClone(); } + + /* Overridden methods: */ + virtual RTCRestObjectBase *baseClone() const RT_NOEXCEPT RT_OVERRIDE; + virtual int resetToDefault() RT_NOEXCEPT RT_OVERRIDE; + virtual RTCRestOutputBase &serializeAsJson(RTCRestOutputBase &a_rDst) const RT_NOEXCEPT RT_OVERRIDE; + virtual int deserializeFromJson(RTCRestJsonCursor const &a_rCursor) RT_NOEXCEPT RT_OVERRIDE; + virtual int toString(RTCString *a_pDst, uint32_t a_fFlags = 0) const RT_NOEXCEPT RT_OVERRIDE; + virtual int fromString(RTCString const &a_rValue, const char *a_pszName, PRTERRINFO a_pErrInfo = NULL, + uint32_t a_fFlags = kCollectionFormat_Unspecified) RT_NOEXCEPT RT_OVERRIDE; + virtual kTypeClass typeClass(void) const RT_NOEXCEPT RT_OVERRIDE; + virtual const char *typeName(void) const RT_NOEXCEPT RT_OVERRIDE; + + /** Factory method. */ + static DECLCALLBACK(RTCRestObjectBase *) createInstance(void) RT_NOEXCEPT; + /** @copydoc RTCRestObjectBase::FNDESERIALIZEINSTANCEFROMJSON */ + static DECLCALLBACK(int) deserializeInstanceFromJson(RTCRestJsonCursor const &a_rCursor, RTCRestObjectBase **a_ppInstance) RT_NOEXCEPT; + +public: + /** The value. */ + double m_rdValue; +}; + + +/** + * Class wrapping 'RTCString'. + */ +class RT_DECL_CLASS RTCRestString : public RTCRestObjectBase, public RTCString +{ +public: + /** Default constructor. */ + RTCRestString() RT_NOEXCEPT; + /** Destructor. */ + virtual ~RTCRestString(); + + /** Copy constructor. */ + RTCRestString(RTCRestString const &a_rThat); + /** From value constructor. */ + RTCRestString(RTCString const &a_rThat); + /** From value constructor. */ + RTCRestString(const char *a_pszSrc); + /** Safe copy assignment method. */ + int assignCopy(RTCRestString const &a_rThat) RT_NOEXCEPT; + /** Safe copy assignment method. */ + int assignCopy(RTCString const &a_rThat) RT_NOEXCEPT; + /** Safe copy assignment method. */ + int assignCopy(const char *a_pszThat) RT_NOEXCEPT; + /** Make a clone of this object. */ + inline RTCRestString *clone() const RT_NOEXCEPT { return (RTCRestString *)baseClone(); } + + /* Overridden methods: */ + virtual RTCRestObjectBase *baseClone() const RT_NOEXCEPT RT_OVERRIDE; + virtual int setNull(void) RT_NOEXCEPT RT_OVERRIDE; /* (ambigious, so overrider it to make sure.) */ + virtual int resetToDefault() RT_NOEXCEPT RT_OVERRIDE; + virtual RTCRestOutputBase &serializeAsJson(RTCRestOutputBase &a_rDst) const RT_NOEXCEPT RT_OVERRIDE; + virtual int deserializeFromJson(RTCRestJsonCursor const &a_rCursor) RT_NOEXCEPT RT_OVERRIDE; + virtual int toString(RTCString *a_pDst, uint32_t a_fFlags = kCollectionFormat_Unspecified) const RT_NOEXCEPT RT_OVERRIDE; + virtual int fromString(RTCString const &a_rValue, const char *a_pszName, PRTERRINFO a_pErrInfo = NULL, + uint32_t a_fFlags = kCollectionFormat_Unspecified) RT_NOEXCEPT RT_OVERRIDE; + virtual kTypeClass typeClass(void) const RT_NOEXCEPT RT_OVERRIDE; + virtual const char *typeName(void) const RT_NOEXCEPT RT_OVERRIDE; + + /** Factory method. */ + static DECLCALLBACK(RTCRestObjectBase *) createInstance(void) RT_NOEXCEPT; + /** @copydoc RTCRestObjectBase::FNDESERIALIZEINSTANCEFROMJSON */ + static DECLCALLBACK(int) deserializeInstanceFromJson(RTCRestJsonCursor const &a_rCursor, RTCRestObjectBase **a_ppInstance) RT_NOEXCEPT; + + /** @name RTCString assignment methods we need to replace to manage the null indicator + * @{ */ + int assignNoThrow(const RTCString &a_rSrc) RT_NOEXCEPT; + int assignNoThrow(const char *a_pszSrc) RT_NOEXCEPT; + int assignNoThrow(const RTCString &a_rSrc, size_t a_offSrc, size_t a_cchSrc = npos) RT_NOEXCEPT; + int assignNoThrow(const char *a_pszSrc, size_t a_cchSrc) RT_NOEXCEPT; + int assignNoThrow(size_t a_cTimes, char a_ch) RT_NOEXCEPT; + int printfNoThrow(const char *pszFormat, ...) RT_NOEXCEPT RT_IPRT_FORMAT_ATTR(1, 2); + int printfVNoThrow(const char *pszFormat, va_list va) RT_NOEXCEPT RT_IPRT_FORMAT_ATTR(1, 0); + RTCRestString &operator=(const char *a_pcsz); + RTCRestString &operator=(const RTCString &a_rThat); + RTCRestString &operator=(const RTCRestString &a_rThat); + RTCRestString &assign(const RTCString &a_rSrc); + RTCRestString &assign(const char *a_pszSrc); + RTCRestString &assign(const RTCString &a_rSrc, size_t a_offSrc, size_t a_cchSrc = npos); + RTCRestString &assign(const char *a_pszSrc, size_t a_cchSrc); + RTCRestString &assign(size_t a_cTimes, char a_ch); + RTCRestString &printf(const char *pszFormat, ...) RT_IPRT_FORMAT_ATTR(1, 2); + RTCRestString &printfV(const char *pszFormat, va_list va) RT_IPRT_FORMAT_ATTR(1, 0); + /** @} */ +}; + + +/** + * Date class. + * + * There are numerous ways of formatting a timestamp and the specifications + * we're currently working with doesn't have a way of telling it seems. + * Thus, decoding need to have fail safes built in so the user can give hints. + * The formatting likewise needs to be told which format to use by the user. + * + * Two side-effects of the format stuff is that the default constructor creates + * an object that is null, and resetToDefault will do the same bug leave the + * format as a hint. + */ +class RT_DECL_CLASS RTCRestDate : public RTCRestObjectBase +{ +public: + /** Default constructor. + * @note The result is a null-object. */ + RTCRestDate() RT_NOEXCEPT; + /** Copy constructor. */ + RTCRestDate(RTCRestDate const &a_rThat); + /** Destructor. */ + virtual ~RTCRestDate(); + /** Copy assignment operator. */ + RTCRestDate &operator=(RTCRestDate const &a_rThat); + /** Safe copy assignment method. */ + int assignCopy(RTCRestDate const &a_rThat) RT_NOEXCEPT; + /** Make a clone of this object. */ + inline RTCRestDate *clone() const RT_NOEXCEPT{ return (RTCRestDate *)baseClone(); } + + /* Overridden methods: */ + virtual RTCRestObjectBase *baseClone() const RT_NOEXCEPT RT_OVERRIDE; + virtual int resetToDefault() RT_NOEXCEPT RT_OVERRIDE; + virtual RTCRestOutputBase &serializeAsJson(RTCRestOutputBase &a_rDst) const RT_NOEXCEPT RT_OVERRIDE; + virtual int deserializeFromJson(RTCRestJsonCursor const &a_rCursor) RT_NOEXCEPT RT_OVERRIDE; + virtual int toString(RTCString *a_pDst, uint32_t a_fFlags = 0) const RT_NOEXCEPT RT_OVERRIDE; + virtual int fromString(RTCString const &a_rValue, const char *a_pszName, PRTERRINFO a_pErrInfo = NULL, + uint32_t a_fFlags = kCollectionFormat_Unspecified) RT_NOEXCEPT RT_OVERRIDE; + virtual kTypeClass typeClass(void) const RT_NOEXCEPT RT_OVERRIDE; + virtual const char *typeName(void) const RT_NOEXCEPT RT_OVERRIDE; + + /** Factory method. */ + static DECLCALLBACK(RTCRestObjectBase *) createInstance(void) RT_NOEXCEPT; + /** @copydoc RTCRestObjectBase::FNDESERIALIZEINSTANCEFROMJSON */ + static DECLCALLBACK(int) deserializeInstanceFromJson(RTCRestJsonCursor const &a_rCursor, RTCRestObjectBase **a_ppInstance) RT_NOEXCEPT; + + /** Date formats. */ + typedef enum + { + kFormat_Invalid = 0, + kFormat_Rfc2822, /**< Format it according to RFC-2822. */ + kFormat_Rfc7131, /**< Format it according to RFC-7131 (HTTP). */ + kFormat_Rfc3339, /**< Format it according to RFC-3339 (ISO-8601) (no fraction). */ + kFormat_Rfc3339_Fraction_2, /**< Format it according to RFC-3339 (ISO-8601) with two digit fraction (hundreths). */ + kFormat_Rfc3339_Fraction_3, /**< Format it according to RFC-3339 (ISO-8601) with three digit fraction (milliseconds). */ + kFormat_Rfc3339_Fraction_6, /**< Format it according to RFC-3339 (ISO-8601) with six digit fraction (microseconds). */ + kFormat_Rfc3339_Fraction_9, /**< Format it according to RFC-3339 (ISO-8601) with nine digit fraction (nanoseconds). */ + kFormat_End + } kFormat; + + /** + * Assigns the value, formats it as a string and clears the null indicator. + * + * @returns VINF_SUCCESS, VERR_NO_STR_MEMORY or VERR_INVALID_PARAMETER. + * @param a_pTimeSpec The time spec to set. + * @param a_enmFormat The date format to use when formatting it. + */ + int assignValue(PCRTTIMESPEC a_pTimeSpec, kFormat a_enmFormat) RT_NOEXCEPT; + int assignValueRfc2822(PCRTTIMESPEC a_pTimeSpec) RT_NOEXCEPT; /**< Convenience method for email/whatnot. */ + int assignValueRfc7131(PCRTTIMESPEC a_pTimeSpec) RT_NOEXCEPT; /**< Convenience method for HTTP date. */ + int assignValueRfc3339(PCRTTIMESPEC a_pTimeSpec) RT_NOEXCEPT; /**< Convenience method for ISO-8601 timstamp. */ + + /** + * Assigns the current UTC time and clears the null indicator . + * + * @returns VINF_SUCCESS, VERR_NO_STR_MEMORY or VERR_INVALID_PARAMETER. + * @returns VINF_SUCCESS or VERR_NO_STR_MEMORY. + * @param a_enmFormat The date format to use when formatting it. + */ + int assignNow(kFormat a_enmFormat) RT_NOEXCEPT; + int assignNowRfc2822() RT_NOEXCEPT; /**< Convenience method for email/whatnot. */ + int assignNowRfc7131() RT_NOEXCEPT; /**< Convenience method for HTTP date. */ + int assignNowRfc3339() RT_NOEXCEPT; /**< Convenience method for ISO-8601 timstamp. */ + + /** + * Sets the format to help deal with decoding issues. + * + * This can also be used to change the date format for an okay timespec. + * @returns IPRT status code. + * @param a_enmFormat The date format to try/set. + */ + int setFormat(kFormat a_enmFormat) RT_NOEXCEPT; + + /** Check if the value is okay (m_TimeSpec & m_Exploded). */ + inline bool isOkay() const RT_NOEXCEPT { return m_fTimeSpecOkay; } + /** Get the timespec value. */ + inline RTTIMESPEC const &getTimeSpec() const RT_NOEXCEPT { return m_TimeSpec; } + /** Get the exploded time. */ + inline RTTIME const &getExploded() const RT_NOEXCEPT { return m_Exploded; } + /** Gets the format. */ + inline kFormat getFormat() const RT_NOEXCEPT { return m_enmFormat; } + /** Get the formatted/raw string value. */ + inline RTCString const &getString() const RT_NOEXCEPT { return m_strFormatted; } + + /** Get nanoseconds since unix epoch. */ + inline int64_t getEpochNano() const RT_NOEXCEPT { return RTTimeSpecGetNano(&m_TimeSpec); } + /** Get seconds since unix epoch. */ + inline int64_t getEpochSeconds() const RT_NOEXCEPT { return RTTimeSpecGetSeconds(&m_TimeSpec); } + /** Checks if UTC time. */ + inline bool isUtc() const RT_NOEXCEPT { return (m_Exploded.fFlags & RTTIME_FLAGS_TYPE_MASK) != RTTIME_FLAGS_TYPE_LOCAL; } + /** Checks if local time. */ + inline bool isLocal() const RT_NOEXCEPT { return (m_Exploded.fFlags & RTTIME_FLAGS_TYPE_MASK) == RTTIME_FLAGS_TYPE_LOCAL; } + +protected: + /** The value. */ + RTTIMESPEC m_TimeSpec; + /** The exploded time value. */ + RTTIME m_Exploded; + /** Set if m_TimeSpec is okay, consult m_strFormatted if not. */ + bool m_fTimeSpecOkay; + /** The format / format hint. */ + kFormat m_enmFormat; + /** The formatted date string. + * This will be the raw input string for a deserialized value, where as for + * a value set by the user it will be the formatted value. */ + RTCString m_strFormatted; + + /** + * Explodes and formats the m_TimeSpec value. + * + * Sets m_Exploded, m_strFormatted, m_fTimeSpecOkay, and m_enmFormat, clears m_fNullIndicator. + * + * @returns VINF_SUCCESS or VERR_NO_STR_MEMORY. + * @param a_enmFormat The format to use. + */ + int explodeAndFormat(kFormat a_enmFormat) RT_NOEXCEPT; + + /** + * Formats the m_Exploded value. + * + * Sets m_strFormatted, m_fTimeSpecOkay, and m_enmFormat, clears m_fNullIndicator. + * + * @returns VINF_SUCCESS or VERR_NO_STR_MEMORY. + * @param a_enmFormat The format to use. + */ + int format(kFormat a_enmFormat) RT_NOEXCEPT; + + /** + * Internal worker that attempts to decode m_strFormatted. + * + * Sets m_fTimeSpecOkay. + * + * @returns IPRT status code. + * @param enmFormat Specific format to try, kFormat_Invalid (default) to try guess it. + */ + int decodeFormattedString(kFormat enmFormat = kFormat_Invalid) RT_NOEXCEPT; +}; + + +/** We should provide a proper UUID class eventually. Currently it is not used. */ +typedef RTCRestString RTCRestUuid; + + +/** + * String enum base class. + */ +class RT_DECL_CLASS RTCRestStringEnumBase : public RTCRestObjectBase +{ +public: + /** Enum map entry. */ + typedef struct ENUMMAPENTRY + { + const char *pszName; + uint32_t cchName; + int32_t iValue; + } ENUMMAPENTRY; + + /** Default constructor. */ + RTCRestStringEnumBase() RT_NOEXCEPT; + /** Destructor. */ + virtual ~RTCRestStringEnumBase(); + + /** Copy constructor. */ + RTCRestStringEnumBase(RTCRestStringEnumBase const &a_rThat); + /** Copy assignment operator. */ + RTCRestStringEnumBase &operator=(RTCRestStringEnumBase const &a_rThat); + + /** Safe copy assignment method. */ + int assignCopy(RTCRestStringEnumBase const &a_rThat) RT_NOEXCEPT; + /** Safe copy assignment method. */ + inline int assignCopy(RTCString const &a_rThat) RT_NOEXCEPT { return setByString(a_rThat); } + /** Safe copy assignment method. */ + inline int assignCopy(const char *a_pszThat) RT_NOEXCEPT { return setByString(a_pszThat); } + + /* Overridden methods: */ + virtual int resetToDefault() RT_NOEXCEPT RT_OVERRIDE; + virtual RTCRestOutputBase &serializeAsJson(RTCRestOutputBase &a_rDst) const RT_NOEXCEPT RT_OVERRIDE; + virtual int deserializeFromJson(RTCRestJsonCursor const &a_rCursor) RT_NOEXCEPT RT_OVERRIDE; + virtual int toString(RTCString *a_pDst, uint32_t a_fFlags = kCollectionFormat_Unspecified) const RT_NOEXCEPT RT_OVERRIDE; + virtual int fromString(RTCString const &a_rValue, const char *a_pszName, PRTERRINFO a_pErrInfo = NULL, + uint32_t a_fFlags = kCollectionFormat_Unspecified) RT_NOEXCEPT RT_OVERRIDE; + virtual kTypeClass typeClass(void) const RT_NOEXCEPT RT_OVERRIDE; + + /** + * Sets the value given a C-string value. + * + * @retval VINF_SUCCESS on success. + * @retval VWRN_NOT_FOUND if not mappable to enum value. + * @retval VERR_NO_STR_MEMORY if not mappable and we're out of memory. + * @param a_pszValue The string value. + * @param a_cchValue The string value length. Optional. + */ + int setByString(const char *a_pszValue, size_t a_cchValue = RTSTR_MAX) RT_NOEXCEPT; + + /** + * Sets the value given a string value. + * + * @retval VINF_SUCCESS on success. + * @retval VWRN_NOT_FOUND if not mappable to enum value. + * @retval VERR_NO_STR_MEMORY if not mappable and we're out of memory. + * @param a_rValue The string value. + */ + int setByString(RTCString const &a_rValue) RT_NOEXCEPT; + + /** + * Gets the string value. + */ + const char *getString() const RT_NOEXCEPT; + + /** Maps the given string value to an enum. */ + int stringToEnum(const char *a_pszValue, size_t a_cchValue = RTSTR_MAX) RT_NOEXCEPT; + /** Maps the given string value to an enum. */ + int stringToEnum(RTCString const &a_rStrValue) RT_NOEXCEPT; + /** Maps the given string value to an enum. */ + const char *enumToString(int a_iEnumValue, size_t *a_pcchString) RT_NOEXCEPT; + + +protected: + /** The enum value. */ + int m_iEnumValue; + /** The string value if not a match. */ + RTCString m_strValue; + + /** + * Worker for setting the object to the given enum value. + * + * @retval true on success. + * @retval false if a_iEnumValue can't be translated. + * @param a_iEnumValue The enum value to set. + */ + bool setWorker(int a_iEnumValue) RT_NOEXCEPT; + + /** Helper for implementing RTCRestObjectBase::clone(). */ + RTCRestObjectBase *cloneWorker(RTCRestStringEnumBase *a_pDst) const RT_NOEXCEPT; + + /** + * Gets the mapping table. + * + * @returns Pointer to the translation table. + * @param pcEntries Where to return the translation table size. + */ + virtual ENUMMAPENTRY const *getMappingTable(size_t *pcEntries) const RT_NOEXCEPT = 0; +}; + + +/** + * String enum template class. + * + * Takes the enum type as argument. + */ +template <typename EnumType> +class RTCRestStringEnum : public RTCRestStringEnumBase +{ +public: + typedef EnumType Type; /**< The enum type. */ + + /** Default constructor */ + RTCRestStringEnum() RT_NOEXCEPT : RTCRestStringEnumBase() { } + /** Constructor with initial enum value. */ + RTCRestStringEnum(Type a_enmValue) RT_NOEXCEPT : RTCRestStringEnumBase() { set(a_enmValue); } + /** Constructor with string default. */ + RTCRestStringEnum(const char *a_pszDefault) : RTCRestStringEnumBase() { setByString(a_pszDefault); } + /** Copy constructor */ + RTCRestStringEnum(RTCRestStringEnum const &a_rThat) : RTCRestStringEnumBase(a_rThat) { } + /** Make a clone of this object. */ + inline RTCRestStringEnum *clone() const RT_NOEXCEPT { return (RTCRestStringEnum *)baseClone(); } + + virtual RTCRestObjectBase *baseClone() const RT_NOEXCEPT RT_OVERRIDE + { + return cloneWorker(new (std::nothrow) RTCRestStringEnum()); + } + + /** Copy assignment operator. */ + RTCRestStringEnum &operator=(RTCRestStringEnum const &a_rThat) RT_NOEXCEPT + { + RTCRestStringEnumBase::operator=(a_rThat); + return *this; + } + + /** + * Gets the enum value. + * @returns enum value. + * @retval kXxxxInvalid means there was no mapping for the string, or that + * no value has been assigned yet. + */ + Type get() const RT_NOEXCEPT { return (Type)m_iEnumValue; } + + /** + * Sets the object value to @a a_enmType + * + * @returns true if a_enmType is valid, false if not. + * @param a_enmType The new value. + */ + bool set(Type a_enmType) RT_NOEXCEPT { return setWorker((int)a_enmType); } + + virtual const char *typeName(void) const RT_NOEXCEPT RT_OVERRIDE { return "RTCRestStringEnum<EnumType>"; } + + /** Factory method. */ + static DECLCALLBACK(RTCRestObjectBase *) createInstance(void) RT_NOEXCEPT + { + return new (std::nothrow) RTCRestStringEnum(); + } + + /** @copydoc RTCRestObjectBase::FNDESERIALIZEINSTANCEFROMJSON */ + static DECLCALLBACK(int) deserializeInstanceFromJson(RTCRestJsonCursor const &a_rCursor, RTCRestObjectBase **a_ppInstance) RT_NOEXCEPT + { + *a_ppInstance = new (std::nothrow) RTCRestStringEnum(); + if (*a_ppInstance) + return (*a_ppInstance)->deserializeFromJson(a_rCursor); + return a_rCursor.m_pPrimary->addError(a_rCursor, VERR_NO_MEMORY, "Out of memory"); + } + +protected: + /** Enum mapping table. */ + static const ENUMMAPENTRY s_aMappingTable[]; + /** Enum mapping table size. */ + static const size_t s_cMappingTable; + + virtual ENUMMAPENTRY const *getMappingTable(size_t *pcEntries) const RT_NOEXCEPT RT_OVERRIDE + { + *pcEntries = s_cMappingTable; + return s_aMappingTable; + } +}; + + +/** + * Class for handling binary blobs (strings). + * + * There are specializations of this class for body parameters and responses, + * see RTCRestBinaryParameter and RTCRestBinaryResponse. + */ +class RT_DECL_CLASS RTCRestBinary : public RTCRestObjectBase +{ +public: + /** Default constructor. */ + RTCRestBinary() RT_NOEXCEPT; + /** Destructor. */ + virtual ~RTCRestBinary(); + + /** Safe copy assignment method. */ + virtual int assignCopy(RTCRestBinary const &a_rThat) RT_NOEXCEPT; + /** Safe buffer copy method. */ + virtual int assignCopy(void const *a_pvData, size_t a_cbData) RT_NOEXCEPT; + + /** Use the specified data buffer directly. */ + virtual int assignReadOnly(void const *a_pvData, size_t a_cbData) RT_NOEXCEPT; + /** Use the specified data buffer directly. */ + virtual int assignWriteable(void *a_pvBuf, size_t a_cbBuf) RT_NOEXCEPT; + /** Frees the data held by the object and resets it default state. */ + virtual void freeData() RT_NOEXCEPT; + + /** Returns a pointer to the data blob. */ + inline const uint8_t *getPtr() const RT_NOEXCEPT { return m_pbData; } + /** Gets the size of the data. */ + inline size_t getSize() const RT_NOEXCEPT { return m_cbData; } + + /** Make a clone of this object. */ + inline RTCRestBinary *clone() const RT_NOEXCEPT { return (RTCRestBinary *)baseClone(); } + + /* Overridden methods: */ + virtual RTCRestObjectBase *baseClone() const RT_NOEXCEPT RT_OVERRIDE; + virtual int setNull(void) RT_NOEXCEPT RT_OVERRIDE; + virtual int resetToDefault(void) RT_NOEXCEPT RT_OVERRIDE; + virtual RTCRestOutputBase &serializeAsJson(RTCRestOutputBase &a_rDst) const RT_NOEXCEPT RT_OVERRIDE; + virtual int deserializeFromJson(RTCRestJsonCursor const &a_rCursor) RT_NOEXCEPT RT_OVERRIDE; + virtual int toString(RTCString *a_pDst, uint32_t a_fFlags = kCollectionFormat_Unspecified) const RT_NOEXCEPT RT_OVERRIDE; + virtual int fromString(RTCString const &a_rValue, const char *a_pszName, PRTERRINFO a_pErrInfo = NULL, + uint32_t a_fFlags = kCollectionFormat_Unspecified) RT_NOEXCEPT RT_OVERRIDE; + virtual kTypeClass typeClass(void) const RT_NOEXCEPT RT_OVERRIDE; + virtual const char *typeName(void) const RT_NOEXCEPT RT_OVERRIDE; + + /** Factory method. */ + static DECLCALLBACK(RTCRestObjectBase *) createInstance(void) RT_NOEXCEPT; + /** @copydoc RTCRestObjectBase::FNDESERIALIZEINSTANCEFROMJSON */ + static DECLCALLBACK(int) deserializeInstanceFromJson(RTCRestJsonCursor const &a_rCursor, RTCRestObjectBase **a_ppInstance) RT_NOEXCEPT; + +protected: + /** Pointer to data blob. */ + uint8_t *m_pbData; + /** Amount of valid data in the blob. */ + size_t m_cbData; + /** Number of bytes allocated for the m_pbData buffer. */ + size_t m_cbAllocated; + /** Set if the data is freeable, only ever clear if user data. */ + bool m_fFreeable; + /** Set if the data blob is readonly user provided data. */ + bool m_fReadOnly; + +private: + /* No copy constructor or copy assignment: */ + RTCRestBinary(RTCRestBinary const &a_rThat); + RTCRestBinary &operator=(RTCRestBinary const &a_rThat); +}; + + +/** + * Abstract base class for REST data model classes. + */ +class RT_DECL_CLASS RTCRestDataObject : public RTCRestObjectBase +{ +public: + RTCRestDataObject() RT_NOEXCEPT; + RTCRestDataObject(RTCRestDataObject const &a_rThat) RT_NOEXCEPT; + virtual ~RTCRestDataObject(); + + /* Overridden methods:*/ + virtual int resetToDefault() RT_NOEXCEPT RT_OVERRIDE; + virtual RTCRestOutputBase &serializeAsJson(RTCRestOutputBase &a_rDst) const RT_NOEXCEPT RT_OVERRIDE; + virtual int deserializeFromJson(RTCRestJsonCursor const &a_rCursor) RT_NOEXCEPT RT_OVERRIDE; + virtual kTypeClass typeClass(void) const RT_NOEXCEPT RT_OVERRIDE; + + /** + * Serialize the object members as JSON. + * + * @returns a_rDst + * @param a_rDst The destination for the serialization. + */ + virtual RTCRestOutputBase &serializeMembersAsJson(RTCRestOutputBase &a_rDst) const RT_NOEXCEPT; + + /** + * Deserialize object from the given JSON iterator. + * + * @returns IPRT status code. + * @retval VERR_NOT_FOUND if field is unknown. Top level caller will do + * invoke unknownField() on it. + * + * @param a_rCursor The JSON cursor with the current member. + * @param a_cchName The length of a_rCursor.m_pszName. + */ + virtual int deserializeMemberFromJson(RTCRestJsonCursor const &a_rCursor, size_t a_cchName) RT_NOEXCEPT; + +protected: + /** The is-set bits for all the fields. */ + uint64_t m_fIsSet; + + /** Copy assignment operator. */ + RTCRestDataObject &operator=(RTCRestDataObject const &a_rThat) RT_NOEXCEPT; + + /** Safe copy assignment method. */ + virtual int assignCopy(RTCRestDataObject const &a_rThat) RT_NOEXCEPT; +}; + + +/** + * Abstract base class for polymorphic REST data model classes. + */ +class RT_DECL_CLASS RTCRestPolyDataObject : public RTCRestDataObject +{ +public: + RTCRestPolyDataObject() RT_NOEXCEPT; + RTCRestPolyDataObject(RTCRestPolyDataObject const &a_rThat) RT_NOEXCEPT; + virtual ~RTCRestPolyDataObject(); + + /* Overridden methods:*/ + virtual int resetToDefault() RT_NOEXCEPT RT_OVERRIDE; + + /** Checks if the instance is of a child class (@c true) or of the parent (@c false). */ + virtual bool isChild() const RT_NOEXCEPT; + +protected: + + /** Copy assignment operator. */ + RTCRestPolyDataObject &operator=(RTCRestPolyDataObject const &a_rThat) RT_NOEXCEPT; +}; + + +/** @} */ + +#endif /* !IPRT_INCLUDED_cpp_restbase_h */ + diff --git a/include/iprt/cpp/restclient.h b/include/iprt/cpp/restclient.h new file mode 100644 index 00000000..56a9a892 --- /dev/null +++ b/include/iprt/cpp/restclient.h @@ -0,0 +1,824 @@ +/** @file + * IPRT - C++ Representational State Transfer (REST) Client Classes. + */ + +/* + * Copyright (C) 2008-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#ifndef IPRT_INCLUDED_cpp_restclient_h +#define IPRT_INCLUDED_cpp_restclient_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include <iprt/http.h> +#include <iprt/cpp/restbase.h> +#include <iprt/cpp/reststringmap.h> + + +/** @defgroup grp_rt_cpp_restclient C++ Representational State Transfer (REST) Client Classes. + * @ingroup grp_rt_cpp + * @{ + */ + +/** + * Specialization of RTCRestBinary for use with body parameters in a client. + * + * This enables registering data callbacks for provinding data to upload. + */ +class RT_DECL_CLASS RTCRestBinaryParameter : public RTCRestBinary +{ +public: + /** Default constructor. */ + RTCRestBinaryParameter() RT_NOEXCEPT; + + /** Safe copy assignment method. */ + virtual int assignCopy(RTCRestBinaryParameter const &a_rThat) RT_NOEXCEPT; + /** Safe copy assignment method. + * @note Resets callbacks and ASSUMES that @a a_cbData is the content length. */ + virtual int assignCopy(RTCRestBinary const &a_rThat) RT_NOEXCEPT RT_OVERRIDE; + /** Safe copy assignment method. + * @note Resets callbacks and ASSUMES that @a a_cbData is the content length. */ + virtual int assignCopy(void const *a_pvData, size_t a_cbData) RT_NOEXCEPT RT_OVERRIDE; + + /** + * Use the specified data buffer directly. + * @note Resets callbacks and ASSUMES that @a a_cbData is the content length. */ + virtual int assignReadOnly(void const *a_pvData, size_t a_cbData) RT_NOEXCEPT RT_OVERRIDE; + /** + * Use the specified data buffer directly. + * @note This will assert and work like assignReadOnly. */ + virtual int assignWriteable(void *a_pvBuf, size_t a_cbBuf) RT_NOEXCEPT RT_OVERRIDE; + + /** Make a clone of this object. */ + inline RTCRestBinaryParameter *clone() const RT_NOEXCEPT { return (RTCRestBinaryParameter *)baseClone(); } + + /* Overridden methods: */ + virtual RTCRestObjectBase *baseClone() const RT_NOEXCEPT RT_OVERRIDE; + virtual int resetToDefault() RT_NOEXCEPT RT_OVERRIDE; + virtual const char *typeName(void) const RT_NOEXCEPT RT_OVERRIDE; + + /** Factory method. */ + static DECLCALLBACK(RTCRestObjectBase *) createInstance(void) RT_NOEXCEPT; + + /** + * Retrieves the callback data. + */ + inline void *getCallbackData() const RT_NOEXCEPT { return m_pvCallbackData; } + + /** + * Sets the content-type for an upload. + * + * @returns VINF_SUCCESS or VERR_NO_STR_MEMORY. + * @param a_pszContentType The content type to set. + * If NULL, no content type is set. + */ + int setContentType(const char *a_pszContentType) RT_NOEXCEPT; + + /** + * Gets the content type that was set. + */ + inline RTCString const &getContentType() const RT_NOEXCEPT { return m_strContentType; } + + /** + * Gets the content-length value (UINT64_MAX if not available). + */ + inline uint64_t getContentLength() const RT_NOEXCEPT { return m_cbContentLength; } + + /** + * Callback for producing bytes to upload. + * + * @returns IPRT status code. + * @param a_pThis The related string object. + * @param a_pvDst Where to put the bytes. + * @param a_cbDst Max number of bytes to produce. + * @param a_offContent The byte offset corresponding to the start of @a a_pvDst. + * @param a_pcbActual Where to return the number of bytes actually produced. + * + * @remarks Use getCallbackData to get the user data. + * + * @note The @a a_offContent parameter does not imply random access or anthing + * like that, it is just a convenience provided by the caller. The value + * is the sum of the previously returned @a *pcbActual values. + */ + typedef DECLCALLBACKTYPE(int, FNPRODUCER,(RTCRestBinaryParameter *a_pThis, void *a_pvDst, size_t a_cbDst, + uint64_t a_offContent, size_t *a_pcbActual)) /*RT_NOEXCEPT*/; + /** Pointer to a byte producer callback. */ + typedef FNPRODUCER *PFNPRODUCER; + + /** + * Sets the producer callback. + * + * @param a_pfnProducer The callback function for producing data. + * @param a_pvCallbackData Data the can be retrieved from the callback + * using getCallbackData(). + * @param a_cbContentLength The amount of data that will be uploaded and + * to be set as the value of the content-length + * header field. Pass UINT64_MAX if not known. + * + * @note This will drop any buffer previously registered using setUploadData(). + */ + void setProducerCallback(PFNPRODUCER a_pfnProducer, void *a_pvCallbackData = NULL, uint64_t a_cbContentLength = UINT64_MAX) RT_NOEXCEPT; + + /** + * Preprares transmission via the @a a_hHttp client instance. + * + * @returns IPRT status code. + * @param a_hHttp The HTTP client instance. + * @internal + */ + virtual int xmitPrepare(RTHTTP a_hHttp) const RT_NOEXCEPT; + + /** + * For completing and/or undoing setup from xmitPrepare. + * + * @param a_hHttp The HTTP client instance. + * @internal + */ + virtual void xmitComplete(RTHTTP a_hHttp) const RT_NOEXCEPT; + +protected: + /** Number of bytes corresponding to content-length. + * UINT64_MAX if not known. Used both for unploads and downloads. */ + uint64_t m_cbContentLength; + /** The content type if set (upload only). */ + RTCString m_strContentType; + /** Pointer to user-registered producer callback function (upload only). */ + PFNPRODUCER m_pfnProducer; + /** User argument for both callbacks (both). */ + void *m_pvCallbackData; + + /** @copydoc FNRTHTTPUPLOADCALLBACK */ + static DECLCALLBACK(int) xmitHttpCallback(RTHTTP hHttp, void *pvBuf, size_t cbBuf, uint64_t offContent, + size_t *pcbActual, void *pvUser) RT_NOEXCEPT; + +private: + /* No copy constructor or copy assignment: */ + RTCRestBinaryParameter(RTCRestBinaryParameter const &a_rThat); + RTCRestBinaryParameter &operator=(RTCRestBinaryParameter const &a_rThat); +}; + + +/** + * Specialization of RTCRestBinary for use with responses in a client. + * + * This enables registering data callbacks for consuming downloaded data. + */ +class RT_DECL_CLASS RTCRestBinaryResponse : public RTCRestBinary +{ +public: + /** Default constructor. */ + RTCRestBinaryResponse() RT_NOEXCEPT; + + /** Safe copy assignment method. */ + virtual int assignCopy(RTCRestBinaryResponse const &a_rThat) RT_NOEXCEPT; + /** Safe copy assignment method. */ + virtual int assignCopy(RTCRestBinary const &a_rThat) RT_NOEXCEPT RT_OVERRIDE; + /** Safe copy assignment method. + * @note This will assert and fail as it makes no sense for a download. */ + virtual int assignCopy(void const *a_pvData, size_t a_cbData) RT_NOEXCEPT RT_OVERRIDE; + + /** + * Use the specified data buffer directly. + * @note This will assert and fail as it makes no sense for a download. + */ + virtual int assignReadOnly(void const *a_pvData, size_t a_cbData) RT_NOEXCEPT RT_OVERRIDE; + /** + * Use the specified data buffer directly. + * @note This will drop any previously registered producer callback and user data. + */ + virtual int assignWriteable(void *a_pvBuf, size_t a_cbBuf) RT_NOEXCEPT RT_OVERRIDE; + + /** Make a clone of this object. */ + inline RTCRestBinaryResponse *clone() const RT_NOEXCEPT { return (RTCRestBinaryResponse *)baseClone(); } + + /* Overridden methods: */ + virtual RTCRestObjectBase *baseClone() const RT_NOEXCEPT RT_OVERRIDE; + virtual int resetToDefault() RT_NOEXCEPT RT_OVERRIDE; + virtual const char *typeName(void) const RT_NOEXCEPT RT_OVERRIDE; + + /** Factory method. */ + static DECLCALLBACK(RTCRestObjectBase *) createInstance(void) RT_NOEXCEPT; + + /** + * Retrieves the callback data. + */ + inline void *getCallbackData() const RT_NOEXCEPT { return m_pvCallbackData; } + + /** + * Sets the max size to download to memory. + * + * This also indicates the intention to download to a memory buffer, so it + * will drop any previously registered consumer callback and its user data. + * + * @param a_cbMaxDownload Maximum number of bytes to download to memory. + * If 0, a default is selected (currently 32MiB for + * 32-bit hosts and 128MiB for 64-bit). + */ + void setMaxDownloadSize(size_t a_cbMaxDownload) RT_NOEXCEPT; + + /** + * Gets the content-length value (UINT64_MAX if not available). + */ + inline uint64_t getContentLength() const RT_NOEXCEPT { return m_cbContentLength; } + + /** + * Callback for consuming downloaded bytes. + * + * @returns IPRT status code. + * @param a_pThis The related string object. + * @param a_pvSrc Buffer containing the bytes. + * @param a_cbSrc The number of bytes in the buffer. + * @param a_uHttpStatus The HTTP status code. + * @param a_offContent The byte offset corresponding to the start of @a a_pvSrc. + * @param a_cbContent The content length field value, UINT64_MAX if not available. + * + * @remarks Use getCallbackData to get the user data. + * + * @note The @a a_offContent parameter does not imply random access or anthing + * like that, it is just a convenience provided by the caller. The value + * is the sum of the previous @a a_cbSrc values. + */ + typedef DECLCALLBACKTYPE(int, FNCONSUMER,(RTCRestBinaryResponse *a_pThis, const void *a_pvSrc, size_t a_cbSrc, + uint32_t a_uHttpStatus, uint64_t a_offContent, uint64_t a_cbContent)) /*RT_NOEXCEPT*/; + /** Pointer to a byte consumer callback. */ + typedef FNCONSUMER *PFNCONSUMER; + + /** + * Sets the consumer callback. + * + * @param a_pfnConsumer The callback function for consuming downloaded data. + * NULL if data should be stored in m_pbData (the default). + * @param a_pvCallbackData Data the can be retrieved from the callback + * using getCallbackData(). + */ + void setConsumerCallback(PFNCONSUMER a_pfnConsumer, void *a_pvCallbackData = NULL) RT_NOEXCEPT; + + /** + * Preprares for receiving via the @a a_hHttp client instance. + * + * @returns IPRT status code. + * @param a_hHttp The HTTP client instance. + * @param a_fCallbackFlags The HTTP callback flags (status code spec). + * @internal + */ + virtual int receivePrepare(RTHTTP a_hHttp, uint32_t a_fCallbackFlags) RT_NOEXCEPT; + + /** + * For completing and/or undoing setup from receivePrepare. + * + * @param a_hHttp The HTTP client instance. + * @internal + */ + virtual void receiveComplete(RTHTTP a_hHttp) RT_NOEXCEPT; + +protected: + /** Number of bytes corresponding to content-length. + * UINT64_MAX if not known. Used both for unploads and downloads. */ + uint64_t m_cbContentLength; + /** Number of bytes downloaded thus far. */ + uint64_t m_cbDownloaded; + /** Pointer to user-registered consumer callback function (download only). */ + PFNCONSUMER m_pfnConsumer; + /** User argument for both callbacks (both). */ + void *m_pvCallbackData; + /** Maximum data to download to memory (download only). */ + size_t m_cbMaxDownload; + + /** @copydoc FNRTHTTPDOWNLOADCALLBACK */ + static DECLCALLBACK(int) receiveHttpCallback(RTHTTP hHttp, void const *pvBuf, size_t cbBuf, uint32_t uHttpStatus, + uint64_t offContent, uint64_t cbContent, void *pvUser) RT_NOEXCEPT; + +private: + /* No copy constructor or copy assignment: */ + RTCRestBinaryResponse(RTCRestBinaryResponse const &a_rThat); + RTCRestBinaryResponse &operator=(RTCRestBinaryResponse const &a_rThat); +}; + + +/** + * Base class for REST client requests. + * + * This encapsulates parameters and helps transform them into a HTTP request. + * + * Parameters can be transfered in a number of places: + * - Path part of the URL. + * - Query part of the URL. + * - HTTP header fields. + * - FORM body. + * - JSON body. + * - XML body. + * - ... + * + * They can be require or optional. The latter may have default values. In + * swagger 3 they can also be nullable, which means the null-indicator cannot + * be used for tracking optional parameters. + */ +class RT_DECL_CLASS RTCRestClientRequestBase +{ +public: + RTCRestClientRequestBase() RT_NOEXCEPT; + virtual ~RTCRestClientRequestBase(); + RTCRestClientRequestBase(RTCRestClientRequestBase const &a_rThat) RT_NOEXCEPT; + RTCRestClientRequestBase &operator=(RTCRestClientRequestBase const &a_rThat) RT_NOEXCEPT; + + /** + * Reset all members to default values. + * @returns IPRT status code. + */ + virtual int resetToDefault() RT_NOEXCEPT = 0; + + /** + * Getter for the operation name. Provided by the generated + * subclasses so that base class code may use it for more + * informative logs. + */ + virtual const char *getOperationName() const RT_NOEXCEPT = 0; + + /** + * Prepares the HTTP handle for transmitting this request. + * + * @returns IPRT status code. + * @param a_pStrPath Where to set path parameters. Will be appended to the base path. + * @param a_pStrQuery Where to set query parameters. + * @param a_hHttp Where to set header parameters and such. + * @param a_pStrBody Where to set body parameters. + */ + virtual int xmitPrepare(RTCString *a_pStrPath, RTCString *a_pStrQuery, RTHTTP a_hHttp, RTCString *a_pStrBody) const RT_NOEXCEPT = 0; + + /** + * Always called after the request has been transmitted. + * + * @param a_rcStatus Negative numbers are IPRT errors, positive are HTTP status codes. + * @param a_hHttp The HTTP handle the request was performed on. + */ + virtual void xmitComplete(int a_rcStatus, RTHTTP a_hHttp) const RT_NOEXCEPT = 0; + + /** + * Checks if there are were any assignment errors. + */ + inline bool hasAssignmentErrors() const RT_NOEXCEPT { return m_fErrorSet != 0; } + +protected: + /** Set of fields that have been explicitly assigned a value. */ + uint64_t m_fIsSet; + /** Set of fields where value assigning failed. */ + uint64_t m_fErrorSet; + + /** Path parameter descriptor. */ + typedef struct + { + const char *pszName; /**< The name string to replace (including {}). */ + size_t cchName; /**< Length of pszName. */ + uint32_t fFlags; /**< The toString flags. */ + uint8_t iBitNo; /**< The parameter bit number. */ + } PATHPARAMDESC; + + /** Path parameter state. */ + typedef struct + { + RTCRestObjectBase const *pObj; /**< Pointer to the parameter object. */ + size_t offName; /**< Maintained by worker. */ + } PATHPARAMSTATE; + + /** + * Do path parameters. + * + * @returns IPRT status code + * @param a_pStrPath The destination path. + * @param a_pszPathTemplate The path template string. + * @param a_cchPathTemplate The length of the path template string. + * @param a_paPathParams The path parameter descriptors (static). + * @param a_paPathParamStates The path parameter objects and states. + * @param a_cPathParams Number of path parameters. + */ + int doPathParameters(RTCString *a_pStrPath, const char *a_pszPathTemplate, size_t a_cchPathTemplate, + PATHPARAMDESC const *a_paPathParams, PATHPARAMSTATE *a_paPathParamStates, size_t a_cPathParams) const RT_NOEXCEPT; + + /** Query parameter descriptor. */ + typedef struct + { + const char *pszName; /**< The parameter name. */ + uint32_t fFlags; /**< The toString flags. */ + bool fRequired; /**< Required or not. */ + uint8_t iBitNo; /**< The parameter bit number. */ + } QUERYPARAMDESC; + + /** + * Do query parameters. + * + * @returns IPRT status code + * @param a_pStrQuery The destination string. + * @param a_paQueryParams The query parameter descriptors. + * @param a_papQueryParamObjs The query parameter objects, parallel to @a a_paQueryParams. + * @param a_cQueryParams Number of query parameters. + */ + int doQueryParameters(RTCString *a_pStrQuery, QUERYPARAMDESC const *a_paQueryParams, + RTCRestObjectBase const **a_papQueryParamObjs, size_t a_cQueryParams) const RT_NOEXCEPT; + + /** Header parameter descriptor. */ + typedef struct + { + const char *pszName; /**< The parameter name. */ + uint32_t fFlags; /**< The toString flags. */ + bool fRequired; /**< Required or not. */ + uint8_t iBitNo; /**< The parameter bit number. */ + bool fMapCollection; /**< Collect headers starting with pszName into a map. */ + } HEADERPARAMDESC; + + /** + * Do header parameters. + * + * @returns IPRT status code + * @param a_hHttp Where to set header parameters. + * @param a_paHeaderParams The header parameter descriptors. + * @param a_papHeaderParamObjs The header parameter objects, parallel to @a a_paHeaderParams. + * @param a_cHeaderParams Number of header parameters. + */ + int doHeaderParameters(RTHTTP a_hHttp, HEADERPARAMDESC const *a_paHeaderParams, + RTCRestObjectBase const **a_papHeaderParamObjs, size_t a_cHeaderParams) const RT_NOEXCEPT; +}; + + +/** + * Base class for REST client responses. + */ +class RT_DECL_CLASS RTCRestClientResponseBase +{ +public: + /** Default constructor. */ + RTCRestClientResponseBase() RT_NOEXCEPT; + /** Destructor. */ + virtual ~RTCRestClientResponseBase(); + /** Copy constructor. */ + RTCRestClientResponseBase(RTCRestClientResponseBase const &a_rThat); + /** Copy assignment operator. */ + RTCRestClientResponseBase &operator=(RTCRestClientResponseBase const &a_rThat); + + /** + * Resets the object state. + */ + virtual void reset(void) RT_NOEXCEPT; + + /** + * Getter for the operation name. Provided by the generated + * subclasses so that base class code may use it for more + * informative logs. + */ + virtual const char *getOperationName() const RT_NOEXCEPT = 0; + + /** + * Prepares the HTTP handle for receiving the response. + * + * This may install callbacks and such like. + * + * When overridden, the parent class must always be called. + * + * @returns IPRT status code. + * @param a_hHttp The HTTP handle to prepare for receiving. + */ + virtual int receivePrepare(RTHTTP a_hHttp) RT_NOEXCEPT; + + /** + * Called when the HTTP request has been completely received. + * + * @param a_rcStatus Negative numbers are IPRT errors, positive are HTTP status codes. + * @param a_hHttp The HTTP handle the request was performed on. + * This can be NIL_RTHTTP should something fail early, in + * which case it is possible receivePrepare() wasn't called. + * + * @note Called before consumeBody() but after consumeHeader(). + */ + virtual void receiveComplete(int a_rcStatus, RTHTTP a_hHttp) RT_NOEXCEPT; + + /** + * Callback that consumes HTTP body data from the server. + * + * @param a_pchData Body data. + * @param a_cbData Amount of body data. + * + * @note Called after consumeHeader(). + */ + virtual void consumeBody(const char *a_pchData, size_t a_cbData) RT_NOEXCEPT; + + /** + * Called after status, headers and body all have been presented. + */ + virtual void receiveFinal() RT_NOEXCEPT; + + /** + * Getter for m_rcStatus. + * @returns Negative numbers are IPRT errors, positive are HTTP status codes. + */ + inline int getStatus() const RT_NOEXCEPT { return m_rcStatus; } + + /** + * Getter for m_rcHttp. + * @returns HTTP status code or VERR_NOT_AVAILABLE. + */ + inline int getHttpStatus() const RT_NOEXCEPT { return m_rcHttp; } + + /** + * Getter for m_pErrInfo. + */ + inline PCRTERRINFO getErrInfo(void) const RT_NOEXCEPT { return m_pErrInfo; } + + /** + * Getter for m_strContentType. + */ + inline RTCString const &getContentType(void) const RT_NOEXCEPT { return m_strContentType; } + + +protected: + /** Negative numbers are IPRT errors, positive are HTTP status codes. */ + int m_rcStatus; + /** The HTTP status code, VERR_NOT_AVAILABLE if not set. */ + int m_rcHttp; + /** Error information. */ + PRTERRINFO m_pErrInfo; + /** The value of the Content-Type header field. */ + RTCString m_strContentType; + + PRTERRINFO getErrInfoInternal(void) RT_NOEXCEPT; + void deleteErrInfo(void) RT_NOEXCEPT; + void copyErrInfo(PCRTERRINFO pErrInfo) RT_NOEXCEPT; + + /** + * Reports an error (or warning if a_rc non-negative). + * + * @returns a_rc + * @param a_rc The status code to report and return. The first + * error status is assigned to m_rcStatus, subsequent + * ones as well as informational statuses are not + * recorded by m_rcStatus. + * @param a_pszFormat The message format string. + * @param ... Message arguments. + */ + int addError(int a_rc, const char *a_pszFormat, ...) RT_NOEXCEPT; + + /** + * Deserializes a header field value. + * + * @returns IPRT status code. + * @param a_pObj The object to deserialize into. + * @param a_pchValue Pointer to the value (not zero terminated). + * Not necessarily valid UTF-8! + * @param a_cchValue The value length. + * @param a_fFlags Flags to pass to fromString(). + * @param a_pszErrorTag The error tag (field name). + */ + int deserializeHeader(RTCRestObjectBase *a_pObj, const char *a_pchValue, size_t a_cchValue, + uint32_t a_fFlags, const char *a_pszErrorTag) RT_NOEXCEPT; + + /** + * Deserializes a header field value. + * + * @returns IPRT status code. + * @param a_pMap The string map object to deserialize into. + * @param a_pchField Pointer to the map field name. (Caller dropped the prefix.) + * Not necessarily valid UTF-8! + * @param a_cchField Length of field name. + * @param a_pchValue Pointer to the value (not zero terminated). + * Not necessarily valid UTF-8! + * @param a_cchValue The value length. + * @param a_fFlags Flags to pass to fromString(). + * @param a_pszErrorTag The error tag (field name). + */ + int deserializeHeaderIntoMap(RTCRestStringMapBase *a_pMap, const char *a_pchField, size_t a_cchField, + const char *a_pchValue, size_t a_cchValue, uint32_t a_fFlags, const char *a_pszErrorTag) RT_NOEXCEPT; + + /** + * Helper that does the deserializing of the response body + * via deserializeBodyFromJsonCursor(). + * + * @param a_pchData The body blob. + * @param a_cbData The size of the body blob. + * @param a_pszBodyName The name of the body parameter. + */ + void deserializeBody(const char *a_pchData, size_t a_cbData, const char *a_pszBodyName) RT_NOEXCEPT; + + /** + * Called by deserializeBody to do the actual body deserialization. + * + * @param a_rCursor The JSON cursor. + */ + virtual void deserializeBodyFromJsonCursor(RTCRestJsonCursor const &a_rCursor) RT_NOEXCEPT; + + /** + * Primary json cursor for parsing bodies. + */ + class PrimaryJsonCursorForBody : public RTCRestJsonPrimaryCursor + { + public: + RTCRestClientResponseBase *m_pThat; /**< Pointer to response object. */ + PrimaryJsonCursorForBody(RTJSONVAL hValue, const char *pszName, RTCRestClientResponseBase *a_pThat) RT_NOEXCEPT; + virtual int addError(RTCRestJsonCursor const &a_rCursor, int a_rc, const char *a_pszFormat, ...) RT_NOEXCEPT RT_OVERRIDE; + virtual int unknownField(RTCRestJsonCursor const &a_rCursor) RT_NOEXCEPT RT_OVERRIDE; + }; + + + /** + * Consumes a header. + * + * Child classes can override this to pick up their header fields, but must + * always call the parent class. + * + * @returns IPRT status code. + * @param a_uMatchWord Match word constructed by RTHTTP_MAKE_HDR_MATCH_WORD + * @param a_pchField The field name (not zero terminated). + * Not necessarily valid UTF-8! + * @param a_cchField The length of the field. + * @param a_pchValue The field value (not zero terminated). + * @param a_cchValue The length of the value. + */ + virtual int consumeHeader(uint32_t a_uMatchWord, const char *a_pchField, size_t a_cchField, + const char *a_pchValue, size_t a_cchValue) RT_NOEXCEPT; + +private: + /** Callback for use with RTHttpSetHeaderCallback. */ + static DECLCALLBACK(int) receiveHttpHeaderCallback(RTHTTP hHttp, uint32_t uMatchWord, const char *pchField, size_t cchField, + const char *pchValue, size_t cchValue, void *pvUser) RT_NOEXCEPT; +}; + + +/** + * Base class for REST client responses. + */ +class RT_DECL_CLASS RTCRestClientApiBase +{ +public: + RTCRestClientApiBase() RT_NOEXCEPT; + virtual ~RTCRestClientApiBase(); + + /** @name Host and Base path (URL) handling. + * @{ */ + /** + * Gets the server URL. + */ + const char *getServerUrl(void) const RT_NOEXCEPT; + + /** + * Sets the whole server URL. + * @returns IPRT status code. + * @param a_pszUrl The new server URL. NULL/empty to reset to default. + */ + int setServerUrl(const char *a_pszUrl) RT_NOEXCEPT; + + /** + * Sets the scheme part of the the server URL. + * @returns IPRT status code. + * @param a_pszScheme The new scheme. Does not accept NULL or empty string. + */ + int setServerScheme(const char *a_pszScheme) RT_NOEXCEPT; + + /** + * Sets the authority (hostname + port) part of the the server URL. + * @returns IPRT status code. + * @param a_pszAuthority The new authority. Does not accept NULL or empty string. + */ + int setServerAuthority(const char *a_pszAuthority) RT_NOEXCEPT; + + /** + * Sets the base path part of the the server URL. + * @returns IPRT status code. + * @param a_pszBasePath The new base path. Does not accept NULL or empty string. + */ + int setServerBasePath(const char *a_pszBasePath) RT_NOEXCEPT; + + /** + * Gets the default server URL as specified in the specs. + * @returns Server URL. + */ + virtual const char *getDefaultServerUrl() const RT_NOEXCEPT = 0; + + /** + * Gets the default server base path as specified in the specs. + * @returns Host string (start of URL). + */ + virtual const char *getDefaultServerBasePath() const RT_NOEXCEPT = 0; + /** @} */ + + /** + * Sets the CA file to use for HTTPS. + */ + int setCAFile(const char *pcszCAFile) RT_NOEXCEPT; + /** @overload */ + int setCAFile(const RTCString &strCAFile) RT_NOEXCEPT; + + /** Flags to doCall. */ + enum + { + kDoCall_OciReqSignExcludeBody = 1, /**< Exclude the body when doing OCI request signing. */ + kDoCall_RequireBody = 2 /**< The body is required. */ + }; + +protected: + /** Handle to the HTTP connection object. */ + RTHTTP m_hHttp; + /** The server URL to use. If empty use the default. */ + RTCString m_strServerUrl; + /** The CA file to use. If empty use the default. */ + RTCString m_strCAFile; + + /* Make non-copyable (RTCNonCopyable causes warnings): */ + RTCRestClientApiBase(RTCRestClientApiBase const &); + RTCRestClientApiBase *operator=(RTCRestClientApiBase const &); + + /** + * Re-initializes the HTTP instance. + * + * @returns IPRT status code. + */ + virtual int reinitHttpInstance() RT_NOEXCEPT; + + /** + * Hook that's called when doCall has fully assembled the request. + * + * Can be used for request signing and similar final steps. + * + * @returns IPRT status code. + * @param a_hHttp The HTTP client instance. + * @param a_rStrFullUrl The full URL. + * @param a_enmHttpMethod The HTTP request method. + * @param a_rStrXmitBody The body text. + * @param a_fFlags kDoCall_XXX. + */ + virtual int xmitReady(RTHTTP a_hHttp, RTCString const &a_rStrFullUrl, RTHTTPMETHOD a_enmHttpMethod, + RTCString const &a_rStrXmitBody, uint32_t a_fFlags) RT_NOEXCEPT; + + /** + * Implements stuff for making an API call. + * + * @returns a_pResponse->getStatus() + * @param a_rRequest Reference to the request object. + * @param a_enmHttpMethod The HTTP request method. + * @param a_pResponse Pointer to the response object. + * @param a_pszMethod The method name, for logging purposes. + * @param a_fFlags kDoCall_XXX. + */ + virtual int doCall(RTCRestClientRequestBase const &a_rRequest, RTHTTPMETHOD a_enmHttpMethod, + RTCRestClientResponseBase *a_pResponse, const char *a_pszMethod, uint32_t a_fFlags) RT_NOEXCEPT; + + /** + * Implements OCI style request signing. + * + * @returns IPRT status code. + * @param a_hHttp The HTTP client instance. + * @param a_rStrFullUrl The full URL. + * @param a_enmHttpMethod The HTTP request method. + * @param a_rStrXmitBody The body text. + * @param a_fFlags kDoCall_XXX. + * @param a_hKey The key to use for signing. + * @param a_rStrKeyId The key ID. + * + * @remarks The signing scheme is covered by a series of drafts RFC, the latest being: + * https://tools.ietf.org/html/draft-cavage-http-signatures-10 + */ + int ociSignRequest(RTHTTP a_hHttp, RTCString const &a_rStrFullUrl, RTHTTPMETHOD a_enmHttpMethod, + RTCString const &a_rStrXmitBody, uint32_t a_fFlags, RTCRKEY a_hKey, RTCString const &a_rStrKeyId) RT_NOEXCEPT; + + /** + * Worker for the server URL modifiers. + * + * @returns IPRT status code. + * @param a_pszServerUrl The current server URL (for comparing). + * @param a_offDst The offset of the component in the current server URL. + * @param a_cchDst The current component length. + * @param a_pszSrc The new URL component value. + * @param a_cchSrc The length of the new component. + */ + int setServerUrlPart(const char *a_pszServerUrl, size_t a_offDst, size_t a_cchDst, const char *a_pszSrc, size_t a_cchSrc) RT_NOEXCEPT; +}; + +/** @} */ + +#endif /* !IPRT_INCLUDED_cpp_restclient_h */ + diff --git a/include/iprt/cpp/restoutput.h b/include/iprt/cpp/restoutput.h new file mode 100644 index 00000000..d68566c0 --- /dev/null +++ b/include/iprt/cpp/restoutput.h @@ -0,0 +1,280 @@ +/** @file + * IPRT - C++ Representational State Transfer (REST) Output Classes. + */ + +/* + * Copyright (C) 2008-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#ifndef IPRT_INCLUDED_cpp_restoutput_h +#define IPRT_INCLUDED_cpp_restoutput_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include <iprt/cdefs.h> +#include <iprt/types.h> +#include <iprt/stdarg.h> +#include <iprt/cpp/ministring.h> + + +/** @defgroup grp_rt_cpp_restoutput C++ Representational State Transfer (REST) Output Classes. + * @ingroup grp_rt_cpp + * @{ + */ + + +/** + * Abstract base class for serializing data objects. + */ +class RT_DECL_CLASS RTCRestOutputBase +{ +public: + RTCRestOutputBase() RT_NOEXCEPT; + virtual ~RTCRestOutputBase(); + + /** + * Raw output function. + * + * @returns Number of bytes outputted. + * @param a_pchString The string to output (not necessarily terminated). + * @param a_cchToWrite The length of the string + */ + virtual size_t output(const char *a_pchString, size_t a_cchToWrite) RT_NOEXCEPT = 0; + + /** + * RTStrPrintf like function (see @ref pg_rt_str_format). + * + * @returns Number of bytes outputted. + * @param pszFormat The format string. + * @param ... Argument specfied in @a pszFormat. + */ + inline size_t printf(const char *pszFormat, ...) RT_NOEXCEPT RT_IPRT_FORMAT_ATTR(2, 3) + { + va_list va; + va_start(va, pszFormat); + size_t cchWritten = this->vprintf(pszFormat, va); + va_end(va); + return cchWritten; + } + + /** + * RTStrPrintfV like function (see @ref pg_rt_str_format). + * + * @returns Number of bytes outputted. + * @param pszFormat The format string. + * @param va Argument specfied in @a pszFormat. + */ + size_t vprintf(const char *pszFormat, va_list va) RT_NOEXCEPT RT_IPRT_FORMAT_ATTR(2, 0); + + /** + * Begins an array. + * @returns Previous output state. Pass to endArray() when done. + */ + virtual uint32_t beginArray() RT_NOEXCEPT; + + /** + * Ends an array. + * @param a_uOldState Previous output state (returned by beginArray()). + */ + virtual void endArray(uint32_t a_uOldState) RT_NOEXCEPT; + + /** + * Begins an object. + * @returns Previous output state. Pass to endObject() when done. + */ + virtual uint32_t beginObject() RT_NOEXCEPT; + + /** + * Ends an array. + * @param a_uOldState Previous output state (returned by beginObject()). + */ + virtual void endObject(uint32_t a_uOldState) RT_NOEXCEPT; + + /** + * Outputs a value separator. + * This is called before a value, not after. + */ + virtual void valueSeparator() RT_NOEXCEPT; + + /** + * Outputs a value separator, name and name separator. + */ + virtual void valueSeparatorAndName(const char *a_pszName, size_t a_cchName) RT_NOEXCEPT; + + /** Outputs a null-value. */ + void nullValue() RT_NOEXCEPT; + +protected: + /** The current indentation level (bits 15:0) and separator state (bit 31). */ + uint32_t m_uState; + + /** @callback_method_impl{FNRTSTROUTPUT} */ + static DECLCALLBACK(size_t) printfOutputCallback(void *pvArg, const char *pachChars, size_t cbChars) RT_NOEXCEPT; +}; + + +/** + * Abstract base class for pretty output. + */ +class RT_DECL_CLASS RTCRestOutputPrettyBase : public RTCRestOutputBase +{ +public: + RTCRestOutputPrettyBase() RT_NOEXCEPT; + virtual ~RTCRestOutputPrettyBase(); + + /** + * Begins an array. + * @returns Previous output state. Pass to endArray() when done. + */ + virtual uint32_t beginArray() RT_NOEXCEPT RT_OVERRIDE; + + /** + * Ends an array. + * @param a_uOldState Previous output state (returned by beginArray()). + */ + virtual void endArray(uint32_t a_uOldState) RT_NOEXCEPT RT_OVERRIDE; + + /** + * Begins an object. + * @returns Previous output state. Pass to endObject() when done. + */ + virtual uint32_t beginObject() RT_NOEXCEPT RT_OVERRIDE; + + /** + * Ends an array. + * @param a_uOldState Previous output state (returned by beginObject()). + */ + virtual void endObject(uint32_t a_uOldState) RT_NOEXCEPT RT_OVERRIDE; + + /** + * Outputs a value separator. + * This is called before a value, not after. + */ + virtual void valueSeparator() RT_NOEXCEPT RT_OVERRIDE; + + /** + * Outputs a value separator, name and name separator. + */ + virtual void valueSeparatorAndName(const char *a_pszName, size_t a_cchName) RT_NOEXCEPT RT_OVERRIDE; + +protected: + /** Helper for outputting the correct amount of indentation. */ + void outputIndentation() RT_NOEXCEPT; +}; + + +/** + * Serialize to a string object. + */ +class RT_DECL_CLASS RTCRestOutputToString : public RTCRestOutputBase +{ +public: + /** + * Creates an instance that appends to @a a_pDst. + * @param a_pDst Pointer to the destination string object. + * NULL is not accepted and will assert. + * @param a_fAppend Whether to append to the current string value, or + * nuke the string content before starting the output. + */ + RTCRestOutputToString(RTCString *a_pDst, bool a_fAppend = false) RT_NOEXCEPT; + virtual ~RTCRestOutputToString(); + + virtual size_t output(const char *a_pchString, size_t a_cchToWrite) RT_NOEXCEPT RT_OVERRIDE; + + /** + * Finalizes the output and releases the string object to the caller. + * + * @returns The released string object. NULL if we ran out of memory or if + * called already. + * + * @remark This sets m_pDst to NULL and the object cannot be use for any + * more output afterwards. + */ + virtual RTCString *finalize() RT_NOEXCEPT; + +protected: + /** Pointer to the destination string. NULL after finalize(). */ + RTCString *m_pDst; + /** Set if we ran out of memory and should ignore subsequent calls. */ + bool m_fOutOfMemory; + + /* Make non-copyable (RTCNonCopyable causes warnings): */ + RTCRestOutputToString(RTCRestOutputToString const &); + RTCRestOutputToString *operator=(RTCRestOutputToString const &); +}; + + +/** + * Serialize pretty JSON to a string object. + */ +class RT_DECL_CLASS RTCRestOutputPrettyToString : public RTCRestOutputPrettyBase +{ +public: + /** + * Creates an instance that appends to @a a_pDst. + * @param a_pDst Pointer to the destination string object. + * NULL is not accepted and will assert. + * @param a_fAppend Whether to append to the current string value, or + * nuke the string content before starting the output. + */ + RTCRestOutputPrettyToString(RTCString *a_pDst, bool a_fAppend = false) RT_NOEXCEPT; + virtual ~RTCRestOutputPrettyToString(); + + virtual size_t output(const char *a_pchString, size_t a_cchToWrite) RT_NOEXCEPT RT_OVERRIDE; + + /** + * Finalizes the output and releases the string object to the caller. + * + * @returns The released string object. NULL if we ran out of memory or if + * called already. + * + * @remark This sets m_pDst to NULL and the object cannot be use for any + * more output afterwards. + */ + virtual RTCString *finalize() RT_NOEXCEPT; + +protected: + /** Pointer to the destination string. NULL after finalize(). */ + RTCString *m_pDst; + /** Set if we ran out of memory and should ignore subsequent calls. */ + bool m_fOutOfMemory; + + /* Make non-copyable (RTCNonCopyable causes warnings): */ + RTCRestOutputPrettyToString(RTCRestOutputToString const &); + RTCRestOutputPrettyToString *operator=(RTCRestOutputToString const &); +}; + + + +/** @} */ + +#endif /* !IPRT_INCLUDED_cpp_restoutput_h */ + diff --git a/include/iprt/cpp/reststringmap.h b/include/iprt/cpp/reststringmap.h new file mode 100644 index 00000000..179ca994 --- /dev/null +++ b/include/iprt/cpp/reststringmap.h @@ -0,0 +1,499 @@ +/** @file + * IPRT - C++ Representational State Transfer (REST) String Map Template. + */ + +/* + * Copyright (C) 2008-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#ifndef IPRT_INCLUDED_cpp_reststringmap_h +#define IPRT_INCLUDED_cpp_reststringmap_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include <iprt/list.h> +#include <iprt/string.h> +#include <iprt/cpp/restbase.h> + + +/** @defgroup grp_rt_cpp_reststingmap C++ Representational State Transfer (REST) String Map Template + * @ingroup grp_rt_cpp + * @{ + */ + +/** + * Abstract base class for the RTCRestStringMap template. + */ +class RT_DECL_CLASS RTCRestStringMapBase : public RTCRestObjectBase +{ +public: + /** Default destructor. */ + RTCRestStringMapBase() RT_NOEXCEPT; + /** Copy constructor. */ + RTCRestStringMapBase(RTCRestStringMapBase const &a_rThat); + /** Destructor. */ + virtual ~RTCRestStringMapBase(); + /** Copy assignment operator. */ + RTCRestStringMapBase &operator=(RTCRestStringMapBase const &a_rThat); + + /* Overridden methods: */ + virtual RTCRestObjectBase *baseClone() const RT_NOEXCEPT RT_OVERRIDE; + virtual int resetToDefault() RT_NOEXCEPT RT_OVERRIDE; + virtual RTCRestOutputBase &serializeAsJson(RTCRestOutputBase &a_rDst) const RT_NOEXCEPT RT_OVERRIDE; + virtual int deserializeFromJson(RTCRestJsonCursor const &a_rCursor) RT_NOEXCEPT RT_OVERRIDE; + // later? + //virtual int toString(RTCString *a_pDst, uint32_t a_fFlags = kCollectionFormat_Unspecified) const RT_NOEXCEPT RT_OVERRIDE; + //virtual int fromString(RTCString const &a_rValue, const char *a_pszName, PRTERRINFO a_pErrInfo = NULL, + // uint32_t a_fFlags = kCollectionFormat_Unspecified) RT_NOEXCEPT RT_OVERRIDE; + virtual kTypeClass typeClass(void) const RT_NOEXCEPT RT_OVERRIDE; + virtual const char *typeName(void) const RT_NOEXCEPT RT_OVERRIDE; + + /** + * Clear the content of the map. + */ + void clear() RT_NOEXCEPT; + + /** + * Checks if the map is empty. + */ + inline bool isEmpty() const RT_NOEXCEPT { return m_cEntries == 0; } + + /** + * Gets the number of entries in the map. + */ + size_t size() const RT_NOEXCEPT; + + /** + * Checks if the map contains the given key. + * @returns true if key found, false if not. + * @param a_pszKey The key to check fo. + */ + bool containsKey(const char *a_pszKey) const RT_NOEXCEPT; + + /** + * Checks if the map contains the given key. + * @returns true if key found, false if not. + * @param a_rStrKey The key to check fo. + */ + bool containsKey(RTCString const &a_rStrKey) const RT_NOEXCEPT; + + /** + * Remove any key-value pair with the given key. + * @returns true if anything was removed, false if not found. + * @param a_pszKey The key to remove. + */ + bool remove(const char *a_pszKey) RT_NOEXCEPT; + + /** + * Remove any key-value pair with the given key. + * @returns true if anything was removed, false if not found. + * @param a_rStrKey The key to remove. + */ + bool remove(RTCString const &a_rStrKey) RT_NOEXCEPT; + + /** + * Creates a new value and inserts it under the given key, returning the new value. + * + * @returns VINF_SUCCESS or VWRN_ALREADY_EXISTS on success. + * VERR_ALREADY_EXISTS, VERR_NO_MEMORY or VERR_NO_STR_MEMORY on failure. + * @param a_ppValue Where to return the pointer to the value. + * @param a_pszKey The key to put it under. + * @param a_cchKey The length of the key. Default is the entire string. + * @param a_fReplace Whether to replace or fail on key collision. + */ + int putNewValue(RTCRestObjectBase **a_ppValue, const char *a_pszKey, size_t a_cchKey = RTSTR_MAX, bool a_fReplace = false) RT_NOEXCEPT; + + /** + * Creates a new value and inserts it under the given key, returning the new value. + * + * @returns VINF_SUCCESS or VWRN_ALREADY_EXISTS on success. + * VERR_ALREADY_EXISTS, VERR_NO_MEMORY or VERR_NO_STR_MEMORY on failure. + * @param a_ppValue Where to return the pointer to the value. + * @param a_rStrKey The key to put it under. + * @param a_fReplace Whether to replace or fail on key collision. + */ + int putNewValue(RTCRestObjectBase **a_ppValue, RTCString const &a_rStrKey, bool a_fReplace = false) RT_NOEXCEPT; + +protected: + /** Map entry. */ + typedef struct MapEntry + { + /** String space core. */ + RTSTRSPACECORE Core; + /** List node for enumeration. */ + RTLISTNODE ListEntry; + /** The key. + * @remarks Core.pszString points to the value of this object. So, consider it const. */ + RTCString strKey; + /** The value. */ + RTCRestObjectBase *pValue; + } MapEntry; + /** The map tree. */ + RTSTRSPACE m_Map; + /** The enumeration list head (MapEntry). */ + RTLISTANCHOR m_ListHead; + /** Number of map entries. */ + size_t m_cEntries; + +public: + /** @name Map Iteration + * @{ */ + /** Const iterator. */ + class ConstIterator + { + private: + MapEntry *m_pCur; + ConstIterator() RT_NOEXCEPT; + protected: + ConstIterator(MapEntry *a_pEntry) RT_NOEXCEPT : m_pCur(a_pEntry) { } + public: + ConstIterator(ConstIterator const &a_rThat) RT_NOEXCEPT : m_pCur(a_rThat.m_pCur) { } + + /** Gets the key string. */ + inline RTCString const &getKey() RT_NOEXCEPT { return m_pCur->strKey; } + /** Gets poitner to the value object. */ + inline RTCRestObjectBase const *getValue() RT_NOEXCEPT { return m_pCur->pValue; } + + /** Advance to the next map entry. */ + inline ConstIterator &operator++() RT_NOEXCEPT + { + m_pCur = RTListNodeGetNextCpp(&m_pCur->ListEntry, MapEntry, ListEntry); + return *this; + } + + /** Advance to the previous map entry. */ + inline ConstIterator &operator--() RT_NOEXCEPT + { + m_pCur = RTListNodeGetPrevCpp(&m_pCur->ListEntry, MapEntry, ListEntry); + return *this; + } + + /** Compare equal. */ + inline bool operator==(ConstIterator const &a_rThat) RT_NOEXCEPT { return m_pCur == a_rThat.m_pCur; } + /** Compare not equal. */ + inline bool operator!=(ConstIterator const &a_rThat) RT_NOEXCEPT { return m_pCur != a_rThat.m_pCur; } + + /* Map class must be friend so it can use the MapEntry constructor. */ + friend class RTCRestStringMapBase; + }; + + /** Returns iterator for the first map entry (unless it's empty and it's also the end). */ + inline ConstIterator begin() const RT_NOEXCEPT + { + if (!RTListIsEmpty(&m_ListHead)) + return ConstIterator(RTListNodeGetNextCpp(&m_ListHead, MapEntry, ListEntry)); + return end(); + } + /** Returns iterator for the last map entry (unless it's empty and it's also the end). */ + inline ConstIterator last() const RT_NOEXCEPT + { + if (!RTListIsEmpty(&m_ListHead)) + return ConstIterator(RTListNodeGetPrevCpp(&m_ListHead, MapEntry, ListEntry)); + return end(); + } + /** Returns the end iterator. This does not ever refer to an actual map entry. */ + inline ConstIterator end() const RT_NOEXCEPT + { + return ConstIterator(RT_FROM_CPP_MEMBER(&m_ListHead, MapEntry, ListEntry)); + } + /** @} */ + + +protected: + /** + * Helper for creating a clone. + * + * @returns Pointer to new map object on success, NULL if out of memory. + */ + virtual RTCRestStringMapBase *createClone(void) const RT_NOEXCEPT = 0; + + /** + * Wrapper around the value constructor. + * + * @returns Pointer to new value object on success, NULL if out of memory. + */ + virtual RTCRestObjectBase *createValue(void) RT_NOEXCEPT = 0; + + /** + * For accessing the static deserializeInstanceFromJson() method of the value. + */ + virtual int deserializeValueInstanceFromJson(RTCRestJsonCursor const &a_rCursor, RTCRestObjectBase **a_ppInstance) RT_NOEXCEPT = 0; + + /** + * Worker for the copy assignment method and copyMapWorkerMayThrow. + * + * This will use createEntryCopy to do the copying. + * + * @returns VINF_SUCCESS on success, VERR_NO_MEMORY or VERR_NO_STR_MEMORY on failure. + * @param a_rThat The map to copy. Caller makes 100% sure the it has + * the same type as the destination. + */ + int copyMapWorkerNoThrow(RTCRestStringMapBase const &a_rThat) RT_NOEXCEPT; + + /** + * Wrapper around copyMapWorkerNoThrow() that throws allocation errors, making + * it suitable for copy constructors and assignment operators. + */ + void copyMapWorkerMayThrow(RTCRestStringMapBase const &a_rThat); + + /** + * Worker for performing inserts. + * + * @returns VINF_SUCCESS or VWRN_ALREADY_EXISTS on success. + * VERR_ALREADY_EXISTS, VERR_NO_MEMORY or VERR_NO_STR_MEMORY on failure. + * @param a_pszKey The key. + * @param a_pValue The value to insert. Ownership is transferred to the map on success. + * @param a_fReplace Whether to replace existing key-value pair with matching key. + * @param a_cchKey The key length, the whole string by default. + */ + int putWorker(const char *a_pszKey, RTCRestObjectBase *a_pValue, bool a_fReplace, size_t a_cchKey = RTSTR_MAX) RT_NOEXCEPT; + + /** + * Worker for performing inserts. + * + * @returns VINF_SUCCESS or VWRN_ALREADY_EXISTS on success. + * VERR_ALREADY_EXISTS, VERR_NO_MEMORY or VERR_NO_STR_MEMORY on failure. + * @param a_pszKey The key. + * @param a_rValue The value to copy into the map. + * @param a_fReplace Whether to replace existing key-value pair with matching key. + * @param a_cchKey The key length, the whole string by default. + */ + int putCopyWorker(const char *a_pszKey, RTCRestObjectBase const &a_rValue, bool a_fReplace, size_t a_cchKey = RTSTR_MAX) RT_NOEXCEPT; + + /** + * Worker for getting the value corresponding to the given key. + * + * @returns Pointer to the value object if found, NULL if key not in the map. + * @param a_pszKey The key which value to look up. + */ + RTCRestObjectBase *getWorker(const char *a_pszKey) RT_NOEXCEPT; + + /** + * Worker for getting the value corresponding to the given key, const variant. + * + * @returns Pointer to the value object if found, NULL if key not in the map. + * @param a_pszKey The key which value to look up. + */ + RTCRestObjectBase const *getWorker(const char *a_pszKey) const RT_NOEXCEPT; + +private: + static DECLCALLBACK(int) stringSpaceDestructorCallback(PRTSTRSPACECORE pStr, void *pvUser) RT_NOEXCEPT; +}; + + +/** + * Limited map class. + */ +template<class ValueType> class RTCRestStringMap : public RTCRestStringMapBase +{ +public: + /** Default constructor, creates emtpy map. */ + RTCRestStringMap() RT_NOEXCEPT + : RTCRestStringMapBase() + {} + + /** Copy constructor. */ + RTCRestStringMap(RTCRestStringMap const &a_rThat) + : RTCRestStringMapBase() + { + copyMapWorkerMayThrow(a_rThat); + } + + /** Destructor. */ + virtual ~RTCRestStringMap() + { + /* nothing to do here. */ + } + + /** Copy assignment operator. */ + RTCRestStringMap &operator=(RTCRestStringMap const &a_rThat) + { + copyMapWorkerMayThrow(a_rThat); + return *this; + } + + /** Safe copy assignment method. */ + int assignCopy(RTCRestStringMap const &a_rThat) RT_NOEXCEPT + { + return copyMapWorkerNoThrow(a_rThat); + } + + /** Make a clone of this object. */ + inline RTCRestStringMap *clone() const RT_NOEXCEPT + { + return (RTCRestStringMap *)baseClone(); + } + + /** Factory method. */ + static DECLCALLBACK(RTCRestObjectBase *) createInstance(void) RT_NOEXCEPT + { + return new (std::nothrow) RTCRestStringMap<ValueType>(); + } + + /** Factory method for values. */ + static DECLCALLBACK(RTCRestObjectBase *) createValueInstance(void) RT_NOEXCEPT + { + return new (std::nothrow) ValueType(); + } + + /** @copydoc RTCRestObjectBase::FNDESERIALIZEINSTANCEFROMJSON */ + static DECLCALLBACK(int) deserializeInstanceFromJson(RTCRestJsonCursor const &a_rCursor, RTCRestObjectBase **a_ppInstance) RT_NOEXCEPT + { + *a_ppInstance = new (std::nothrow) RTCRestStringMap<ValueType>(); + if (*a_ppInstance) + return (*a_ppInstance)->deserializeFromJson(a_rCursor); + return a_rCursor.m_pPrimary->addError(a_rCursor, VERR_NO_MEMORY, "Out of memory"); + } + + /** + * Inserts the given object into the map. + * + * @returns VINF_SUCCESS or VWRN_ALREADY_EXISTS on success. + * VERR_ALREADY_EXISTS, VERR_NO_MEMORY or VERR_NO_STR_MEMORY on failure. + * @param a_pszKey The key. + * @param a_pValue The value to insert. Ownership is transferred to the map on success. + * @param a_fReplace Whether to replace existing key-value pair with matching key. + */ + inline int put(const char *a_pszKey, ValueType *a_pValue, bool a_fReplace = false) RT_NOEXCEPT + { + return putWorker(a_pszKey, a_pValue, a_fReplace); + } + + /** + * Inserts the given object into the map. + * + * @returns VINF_SUCCESS or VWRN_ALREADY_EXISTS on success. + * VERR_ALREADY_EXISTS, VERR_NO_MEMORY or VERR_NO_STR_MEMORY on failure. + * @param a_rStrKey The key. + * @param a_pValue The value to insert. Ownership is transferred to the map on success. + * @param a_fReplace Whether to replace existing key-value pair with matching key. + */ + inline int put(RTCString const &a_rStrKey, ValueType *a_pValue, bool a_fReplace = false) RT_NOEXCEPT + { + return putWorker(a_rStrKey.c_str(), a_pValue, a_fReplace, a_rStrKey.length()); + } + + /** + * Inserts a copy of the given object into the map. + * + * @returns VINF_SUCCESS or VWRN_ALREADY_EXISTS on success. + * VERR_ALREADY_EXISTS, VERR_NO_MEMORY or VERR_NO_STR_MEMORY on failure. + * @param a_pszKey The key. + * @param a_rValue The value to insert a copy of. + * @param a_fReplace Whether to replace existing key-value pair with matching key. + */ + inline int putCopy(const char *a_pszKey, const ValueType &a_rValue, bool a_fReplace = false) RT_NOEXCEPT + { + return putCopyWorker(a_pszKey, a_rValue, a_fReplace); + } + + /** + * Inserts a copy of the given object into the map. + * + * @returns VINF_SUCCESS or VWRN_ALREADY_EXISTS on success. + * VERR_ALREADY_EXISTS, VERR_NO_MEMORY or VERR_NO_STR_MEMORY on failure. + * @param a_rStrKey The key. + * @param a_rValue The value to insert a copy of. + * @param a_fReplace Whether to replace existing key-value pair with matching key. + */ + inline int putCopy(RTCString const &a_rStrKey, const ValueType &a_rValue, bool a_fReplace = false) RT_NOEXCEPT + { + return putCopyWorker(a_rStrKey.c_str(), a_rValue, a_fReplace, a_rStrKey.length()); + } + + /** + * Gets the value corresponding to the given key. + * + * @returns Pointer to the value object if found, NULL if key not in the map. + * @param a_pszKey The key which value to look up. + */ + inline ValueType *get(const char *a_pszKey) RT_NOEXCEPT + { + return (ValueType *)getWorker(a_pszKey); + } + + /** + * Gets the value corresponding to the given key. + * + * @returns Pointer to the value object if found, NULL if key not in the map. + * @param a_rStrKey The key which value to look up. + */ + inline ValueType *get(RTCString const &a_rStrKey) RT_NOEXCEPT + { + return (ValueType *)getWorker(a_rStrKey.c_str()); + } + + /** + * Gets the const value corresponding to the given key. + * + * @returns Pointer to the value object if found, NULL if key not in the map. + * @param a_pszKey The key which value to look up. + */ + inline ValueType const *get(const char *a_pszKey) const RT_NOEXCEPT + { + return (ValueType const *)getWorker(a_pszKey); + } + + /** + * Gets the const value corresponding to the given key. + * + * @returns Pointer to the value object if found, NULL if key not in the map. + * @param a_rStrKey The key which value to look up. + */ + inline ValueType const *get(RTCString const &a_rStrKey) const RT_NOEXCEPT + { + return (ValueType const *)getWorker(a_rStrKey.c_str()); + } + + /** @todo enumerator*/ + +protected: + virtual RTCRestStringMapBase *createClone(void) const RT_NOEXCEPT RT_OVERRIDE + { + return new (std::nothrow) RTCRestStringMap(); + } + + virtual RTCRestObjectBase *createValue(void) RT_NOEXCEPT RT_OVERRIDE + { + return new (std::nothrow) ValueType(); + } + + virtual int deserializeValueInstanceFromJson(RTCRestJsonCursor const &a_rCursor, RTCRestObjectBase **a_ppInstance) RT_NOEXCEPT RT_OVERRIDE + { + return ValueType::deserializeInstanceFromJson(a_rCursor, a_ppInstance); + } +}; + + +/** @} */ + +#endif /* !IPRT_INCLUDED_cpp_reststringmap_h */ + diff --git a/include/iprt/cpp/utils.h b/include/iprt/cpp/utils.h new file mode 100644 index 00000000..4d577001 --- /dev/null +++ b/include/iprt/cpp/utils.h @@ -0,0 +1,149 @@ +/** @file + * IPRT - C++ Utilities (useful templates, defines and such). + */ + +/* + * Copyright (C) 2006-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#ifndef IPRT_INCLUDED_cpp_utils_h +#define IPRT_INCLUDED_cpp_utils_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include <iprt/types.h> + +/** @defgroup grp_rt_cpp IPRT C++ APIs */ + +/** @defgroup grp_rt_cpp_util C++ Utilities + * @ingroup grp_rt_cpp + * @{ + */ + +#define DPTR(CLASS) CLASS##Private *d = static_cast<CLASS##Private *>(d_ptr) +#define QPTR(CLASS) CLASS *q = static_cast<CLASS *>(q_ptr) + +/** + * A simple class used to prevent copying and assignment. + * + * Inherit from this class in order to prevent automatic generation + * of the copy constructor and assignment operator in your class. + */ +class RTCNonCopyable +{ +protected: + RTCNonCopyable() {} + ~RTCNonCopyable() {} +private: + RTCNonCopyable(RTCNonCopyable const &); + RTCNonCopyable &operator=(RTCNonCopyable const &); +}; + + +/** + * Shortcut to |const_cast<C &>()| that automatically derives the correct + * type (class) for the const_cast template's argument from its own argument. + * + * Can be used to temporarily cancel the |const| modifier on the left-hand side + * of assignment expressions, like this: + * @code + * const Class That; + * ... + * unconst(That) = SomeValue; + * @endcode + * + * @todo What to do about the prefix here? + */ +template <class C> +inline C &unconst(const C &that) +{ + return const_cast<C &>(that); +} + + +/** + * Shortcut to |const_cast<C *>()| that automatically derives the correct + * type (class) for the const_cast template's argument from its own argument. + * + * Can be used to temporarily cancel the |const| modifier on the left-hand side + * of assignment expressions, like this: + * @code + * const Class *pThat; + * ... + * unconst(pThat) = SomeValue; + * @endcode + * + * @todo What to do about the prefix here? + */ +template <class C> +inline C *unconst(const C *that) +{ + return const_cast<C *>(that); +} + + +/** + * Macro for generating a non-const getter version from a const getter. + * + * @param a_RetType The getter return type. + * @param a_Class The class name. + * @param a_Getter The getter name. + * @param a_ArgDecls The argument declaration for the getter method. + * @param a_ArgList The argument list for the call. + */ +#define RT_CPP_GETTER_UNCONST(a_RetType, a_Class, a_Getter, a_ArgDecls, a_ArgList) \ + a_RetType a_Getter a_ArgDecls \ + { \ + return static_cast< a_Class const *>(this)-> a_Getter a_ArgList; \ + } + + +/** + * Macro for generating a non-const getter version from a const getter, + * unconsting the return value as well. + * + * @param a_RetType The getter return type. + * @param a_Class The class name. + * @param a_Getter The getter name. + * @param a_ArgDecls The argument declaration for the getter method. + * @param a_ArgList The argument list for the call. + */ +#define RT_CPP_GETTER_UNCONST_RET(a_RetType, a_Class, a_Getter, a_ArgDecls, a_ArgList) \ + a_RetType a_Getter a_ArgDecls \ + { \ + return const_cast<a_RetType>(static_cast< a_Class const *>(this)-> a_Getter a_ArgList); \ + } + + +/** @} */ + +#endif /* !IPRT_INCLUDED_cpp_utils_h */ + diff --git a/include/iprt/cpp/xml.h b/include/iprt/cpp/xml.h new file mode 100644 index 00000000..40975e86 --- /dev/null +++ b/include/iprt/cpp/xml.h @@ -0,0 +1,1252 @@ +/** @file + * IPRT - XML Helper APIs. + */ + +/* + * Copyright (C) 2007-2023 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see <https://www.gnu.org/licenses>. + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#ifndef IPRT_INCLUDED_cpp_xml_h +#define IPRT_INCLUDED_cpp_xml_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#ifndef IN_RING3 +# error "There are no XML APIs available in Ring-0 Context!" +#endif +#ifdef IPRT_NO_CRT +# error "Not available in no-CRT mode because it depends on exceptions, std::list, std::map and stdio.h." +#endif + +#include <iprt/list.h> +#include <iprt/cpp/exception.h> +#include <iprt/cpp/utils.h> + +#include <list> +#include <memory> + + +/** @defgroup grp_rt_cpp_xml C++ XML support + * @ingroup grp_rt_cpp + * @{ + */ + +/* Forwards */ +typedef struct _xmlParserInput xmlParserInput; +typedef xmlParserInput *xmlParserInputPtr; +typedef struct _xmlParserCtxt xmlParserCtxt; +typedef xmlParserCtxt *xmlParserCtxtPtr; +typedef struct _xmlError xmlError; +typedef xmlError *xmlErrorPtr; + +typedef struct _xmlAttr xmlAttr; +typedef struct _xmlNode xmlNode; + +#define RT_XML_CONTENT_SMALL _8K +#define RT_XML_CONTENT_LARGE _128K +#define RT_XML_ATTR_TINY 64 +#define RT_XML_ATTR_SMALL _1K +#define RT_XML_ATTR_MEDIUM _8K +#define RT_XML_ATTR_LARGE _64K + +/** @} */ + +namespace xml +{ + +/** + * @addtogroup grp_rt_cpp_xml + * @{ + */ + +// Exceptions +////////////////////////////////////////////////////////////////////////////// + +class RT_DECL_CLASS LogicError : public RTCError +{ +public: + + LogicError(const char *aMsg = NULL) + : RTCError(aMsg) + {} + + LogicError(RT_SRC_POS_DECL); +}; + +class RT_DECL_CLASS RuntimeError : public RTCError +{ +public: + + RuntimeError(const char *aMsg = NULL) + : RTCError(aMsg) + {} +}; + +class RT_DECL_CLASS XmlError : public RuntimeError +{ +public: + XmlError(xmlErrorPtr aErr); + + static char* Format(xmlErrorPtr aErr); +}; + +// Logical errors +////////////////////////////////////////////////////////////////////////////// + +class RT_DECL_CLASS ENotImplemented : public LogicError +{ +public: + ENotImplemented(const char *aMsg = NULL) : LogicError(aMsg) {} + ENotImplemented(RT_SRC_POS_DECL) : LogicError(RT_SRC_POS_ARGS) {} +}; + +class RT_DECL_CLASS EInvalidArg : public LogicError +{ +public: + EInvalidArg(const char *aMsg = NULL) : LogicError(aMsg) {} + EInvalidArg(RT_SRC_POS_DECL) : LogicError(RT_SRC_POS_ARGS) {} +}; + +class RT_DECL_CLASS EDocumentNotEmpty : public LogicError +{ +public: + EDocumentNotEmpty(const char *aMsg = NULL) : LogicError(aMsg) {} + EDocumentNotEmpty(RT_SRC_POS_DECL) : LogicError(RT_SRC_POS_ARGS) {} +}; + +class RT_DECL_CLASS ENodeIsNotElement : public LogicError +{ +public: + ENodeIsNotElement(const char *aMsg = NULL) : LogicError(aMsg) {} + ENodeIsNotElement(RT_SRC_POS_DECL) : LogicError(RT_SRC_POS_ARGS) {} +}; + +// Runtime errors +////////////////////////////////////////////////////////////////////////////// + +class RT_DECL_CLASS EIPRTFailure : public RuntimeError +{ +public: + + EIPRTFailure(int aRC, const char *pszContextFmt, ...); + + int rc() const + { + return mRC; + } + + int getStatus() const + { + return mRC; + } + +private: + int mRC; +}; + +/** + * The Stream class is a base class for I/O streams. + */ +class RT_DECL_CLASS Stream +{ +public: + + virtual ~Stream() {} + + virtual const char *uri() const = 0; + + /** + * Returns the current read/write position in the stream. The returned + * position is a zero-based byte offset from the beginning of the file. + * + * Throws ENotImplemented if this operation is not implemented for the + * given stream. + */ + virtual uint64_t pos() const = 0; + + /** + * Sets the current read/write position in the stream. + * + * @param aPos Zero-based byte offset from the beginning of the stream. + * + * Throws ENotImplemented if this operation is not implemented for the + * given stream. + */ + virtual void setPos (uint64_t aPos) = 0; +}; + +/** + * The Input class represents an input stream. + * + * This input stream is used to read the settings tree from. + * This is an abstract class that must be subclassed in order to fill it with + * useful functionality. + */ +class RT_DECL_CLASS Input : virtual public Stream +{ +public: + + /** + * Reads from the stream to the supplied buffer. + * + * @param aBuf Buffer to store read data to. + * @param aLen Buffer length. + * + * @return Number of bytes read. + */ + virtual int read (char *aBuf, int aLen) = 0; +}; + +/** + * + */ +class RT_DECL_CLASS Output : virtual public Stream +{ +public: + + /** + * Writes to the stream from the supplied buffer. + * + * @param aBuf Buffer to write data from. + * @param aLen Buffer length. + * + * @return Number of bytes written. + */ + virtual int write (const char *aBuf, int aLen) = 0; + + /** + * Truncates the stream from the current position and upto the end. + * The new file size will become exactly #pos() bytes. + * + * Throws ENotImplemented if this operation is not implemented for the + * given stream. + */ + virtual void truncate() = 0; +}; + + +////////////////////////////////////////////////////////////////////////////// + +/** + * The File class is a stream implementation that reads from and writes to + * regular files. + * + * The File class uses IPRT File API for file operations. Note that IPRT File + * API is not thread-safe. This means that if you pass the same RTFILE handle to + * different File instances that may be simultaneously used on different + * threads, you should care about serialization; otherwise you will get garbage + * when reading from or writing to such File instances. + */ +class RT_DECL_CLASS File : public Input, public Output +{ +public: + + /** + * Possible file access modes. + */ + enum Mode { Mode_Read, Mode_WriteCreate, Mode_Overwrite, Mode_ReadWrite }; + + /** + * Opens a file with the given name in the given mode. If @a aMode is Read + * or ReadWrite, the file must exist. If @a aMode is Write, the file must + * not exist. Otherwise, an EIPRTFailure exception will be thrown. + * + * @param aMode File mode. + * @param aFileName File name. + * @param aFlushIt Whether to flush a writable file before closing it. + */ + File(Mode aMode, const char *aFileName, bool aFlushIt = false); + + /** + * Uses the given file handle to perform file operations. This file + * handle must be already open in necessary mode (read, or write, or mixed). + * + * The read/write position of the given handle will be reset to the + * beginning of the file on success. + * + * Note that the given file handle will not be automatically closed upon + * this object destruction. + * + * @note It you pass the same RTFILE handle to more than one File instance, + * please make sure you have provided serialization in case if these + * instasnces are to be simultaneously used by different threads. + * Otherwise you may get garbage when reading or writing. + * + * @param aHandle Open file handle. + * @param aFileName File name (for reference). + * @param aFlushIt Whether to flush a writable file before closing it. + */ + File(RTFILE aHandle, const char *aFileName = NULL, bool aFlushIt = false); + + /** + * Destroys the File object. If the object was created from a file name + * the corresponding file will be automatically closed. If the object was + * created from a file handle, it will remain open. + */ + virtual ~File(); + + const char *uri() const; + + uint64_t pos() const; + void setPos(uint64_t aPos); + + /** + * See Input::read(). If this method is called in wrong file mode, + * LogicError will be thrown. + */ + int read(char *aBuf, int aLen); + + /** + * See Output::write(). If this method is called in wrong file mode, + * LogicError will be thrown. + */ + int write(const char *aBuf, int aLen); + + /** + * See Output::truncate(). If this method is called in wrong file mode, + * LogicError will be thrown. + */ + void truncate(); + +private: + + /* Obscure class data */ + struct Data; + Data *m; + + /* auto_ptr data doesn't have proper copy semantics */ + DECLARE_CLS_COPY_CTOR_ASSIGN_NOOP(File); +}; + +/** + * The MemoryBuf class represents a stream implementation that reads from the + * memory buffer. + */ +class RT_DECL_CLASS MemoryBuf : public Input +{ +public: + + MemoryBuf (const char *aBuf, size_t aLen, const char *aURI = NULL); + + virtual ~MemoryBuf(); + + const char *uri() const; + + int read(char *aBuf, int aLen); + uint64_t pos() const; + void setPos(uint64_t aPos); + +private: + /* Obscure class data */ + struct Data; + Data *m; + + /* auto_ptr data doesn't have proper copy semantics */ + DECLARE_CLS_COPY_CTOR_ASSIGN_NOOP(MemoryBuf); +}; + + +/* + * GlobalLock + * + * + */ + + +typedef DECLCALLBACKTYPE_EX(xmlParserInput *, RT_NOTHING, FNEXTERNALENTITYLOADER,(const char *aURI, const char *aID, + xmlParserCtxt *aCtxt)); +typedef FNEXTERNALENTITYLOADER *PFNEXTERNALENTITYLOADER; /**< xmlExternalEntityLoader w/ noexcept. */ + +class RT_DECL_CLASS GlobalLock +{ +public: + GlobalLock(); + ~GlobalLock(); + + void setExternalEntityLoader(PFNEXTERNALENTITYLOADER pFunc); + + static xmlParserInput* callDefaultLoader(const char *aURI, + const char *aID, + xmlParserCtxt *aCtxt); + +private: + /* Obscure class data. */ + struct Data; + struct Data *m; +}; + +class ElementNode; +typedef std::list<const ElementNode*> ElementNodesList; + +class AttributeNode; + +class ContentNode; + +/** + * Node base class. + * + * Cannot be used directly, but ElementNode, ContentNode and AttributeNode + * derive from this. This does implement useful public methods though. + * + * + */ +class RT_DECL_CLASS Node +{ +public: + virtual ~Node(); + + const char *getName() const; + const char *getPrefix() const; + const char *getNamespaceURI() const; + bool nameEqualsNS(const char *pcszNamespace, const char *pcsz) const; + bool nameEquals(const char *pcsz) const + { + return nameEqualsNS(NULL, pcsz); + } + bool nameEqualsN(const char *pcsz, size_t cchMax, const char *pcszNamespace = NULL) const; + + const char *getValue() const; + const char *getValueN(size_t cchValueLimit) const; + bool copyValue(int32_t &i) const; + bool copyValue(uint32_t &i) const; + bool copyValue(int64_t &i) const; + bool copyValue(uint64_t &i) const; + + /** @name Introspection. + * @{ */ + /** Is this an ElementNode instance. + * @returns true / false */ + bool isElement() const + { + return m_Type == IsElement; + } + + /** Is this an ContentNode instance. + * @returns true / false */ + bool isContent() const + { + return m_Type == IsContent; + } + + /** Is this an AttributeNode instance. + * @returns true / false */ + bool isAttribute() const + { + return m_Type == IsAttribute; + } + + int getLineNumber() const; + /** @} */ + + /** @name General tree enumeration. + * + * Use the introspection methods isElement() and isContent() before doing static + * casting. Parents are always or ElementNode type, but siblings and children + * can be of both ContentNode and ElementNode types. + * + * @remarks Attribute node are in the attributes list, while both content and + * element nodes are in the list of children. See ElementNode. + * + * @remarks Careful mixing tree walking with node removal! + * @{ + */ + /** Get the parent node + * @returns Pointer to the parent node, or NULL if root. */ + const Node *getParent() const + { + return m_pParent; + } + + /** Get the previous sibling. + * @returns Pointer to the previous sibling node, NULL if first child. + */ + const Node *getPrevSibiling() const + { + if (!m_pParentListAnchor) + return NULL; + return RTListGetPrevCpp(m_pParentListAnchor, this, const Node, m_listEntry); + } + + /** Get the next sibling. + * @returns Pointer to the next sibling node, NULL if last child. */ + const Node *getNextSibiling() const + { + if (!m_pParentListAnchor) + return NULL; + return RTListGetNextCpp(m_pParentListAnchor, this, const Node, m_listEntry); + } + /** @} */ + +protected: + /** Node types. */ + typedef enum { IsElement, IsAttribute, IsContent } EnumType; + + /** The type of node this is an instance of. */ + EnumType m_Type; + /** The parent node (always an element), NULL if root. */ + Node *m_pParent; + + xmlNode *m_pLibNode; ///< != NULL if this is an element or content node + xmlAttr *m_pLibAttr; ///< != NULL if this is an attribute node + const char *m_pcszNamespacePrefix; ///< not always set + const char *m_pcszNamespaceHref; ///< full http:// spec + const char *m_pcszName; ///< element or attribute name, points either into pLibNode or pLibAttr; + ///< NULL if this is a content node + + /** Child list entry of this node. (List head m_pParent->m_children or + * m_pParent->m_attribute depending on the type.) */ + RTLISTNODE m_listEntry; + /** Pointer to the parent list anchor. + * This allows us to use m_listEntry both for children and attributes. */ + PRTLISTANCHOR m_pParentListAnchor; + + // hide the default constructor so people use only our factory methods + Node(EnumType type, + Node *pParent, + PRTLISTANCHOR pListAnchor, + xmlNode *pLibNode, + xmlAttr *pLibAttr); + Node(const Node &x); // no copying + + friend class AttributeNode; + friend class ElementNode; /* C list hack. */ +}; + +/** + * Node subclass that represents an attribute of an element. + * + * For attributes, Node::getName() returns the attribute name, and Node::getValue() + * returns the attribute value, if any. + * + * Since the Node constructor is private, one can create new attribute nodes + * only through the following factory methods: + * + * -- ElementNode::setAttribute() + */ +class RT_DECL_CLASS AttributeNode : public Node +{ +public: + +protected: + // hide the default constructor so people use only our factory methods + AttributeNode(const ElementNode *pElmRoot, + Node *pParent, + PRTLISTANCHOR pListAnchor, + xmlAttr *pLibAttr); + AttributeNode(const AttributeNode &x); // no copying + + friend class Node; + friend class ElementNode; +}; + +/** + * Node subclass that represents an element. + * + * For elements, Node::getName() returns the element name, and Node::getValue() + * returns the text contents, if any. + * + * Since the Node constructor is private, one can create element nodes + * only through the following factory methods: + * + * -- Document::createRootElement() + * -- ElementNode::createChild() + */ +class RT_DECL_CLASS ElementNode : public Node +{ +public: + int getChildElements(ElementNodesList &children, const char *pcszMatch = NULL) const; + + const ElementNode *findChildElementNS(const char *pcszNamespace, const char *pcszMatch) const; + const ElementNode *findChildElement(const char *pcszMatch) const + { + return findChildElementNS(NULL, pcszMatch); + } + const ElementNode *findChildElementFromId(const char *pcszId) const; + + /** Finds the first decendant matching the name at the end of @a pcszPath and + * optionally namespace. + * + * @returns Pointer to the child string value, NULL if not found or no value. + * @param pcszPath Path to the child element. Slashes can be used to + * make a simple path to any decendant. + * @param pcszNamespace The namespace to match, NULL (default) match any + * namespace. When using a path, this matches all + * elements along the way. + * @see findChildElement, findChildElementP + */ + const ElementNode *findChildElementP(const char *pcszPath, const char *pcszNamespace = NULL) const; + + /** Finds the first child with matching the give name and optionally namspace, + * returning its value. + * + * @returns Pointer to the child string value, NULL if not found or no value. + * @param pcszPath Path to the child element. Slashes can be used to + * make a simple path to any decendant. + * @param pcszNamespace The namespace to match, NULL (default) match any + * namespace. When using a path, this matches all + * elements along the way. + * @see findChildElement, findChildElementP + */ + const char *findChildElementValueP(const char *pcszPath, const char *pcszNamespace = NULL) const + { + const ElementNode *pElem = findChildElementP(pcszPath, pcszNamespace); + if (pElem) + return pElem->getValue(); + return NULL; + } + + /** Finds the first child with matching the give name and optionally namspace, + * returning its value. Checks the length against the limit. + * + * @returns Pointer to the child string value, NULL if not found or no value. + * @param pcszPath Path to the child element. Slashes can be used to + * make a simple path to any decendant. + * @param cchValueLimit If the length of the returned value exceeds this + * limit a EIPRTFailure exception will be thrown. + * @param pcszNamespace The namespace to match, NULL (default) match any + * namespace. When using a path, this matches all + * elements along the way. + * @see findChildElement, findChildElementP + */ + const char *findChildElementValuePN(const char *pcszPath, size_t cchValueLimit, const char *pcszNamespace = NULL) const + { + const ElementNode *pElem = findChildElementP(pcszPath, pcszNamespace); + if (pElem) + return pElem->getValueN(cchValueLimit); + return NULL; + } + + /** Combines findChildElementNS and findAttributeValue. + * + * @returns Pointer to attribute string value, NULL if either the element or + * the attribute was not found. + * @param pcszChild The child element name. + * @param pcszAttribute The attribute name. + * @param pcszChildNamespace The namespace to match @a pcszChild with, NULL + * (default) match any namespace. + * @param pcszAttributeNamespace The namespace prefix to apply to the + * attribute, NULL (default) match any namespace. + * @see findChildElementNS and findAttributeValue + * @note The findChildElementAttributeValueP() method would do the same thing + * given the same inputs, but it would be slightly slower, thus the + * separate method. + */ + const char *findChildElementAttributeValue(const char *pcszChild, const char *pcszAttribute, + const char *pcszChildNamespace = NULL, + const char *pcszAttributeNamespace = NULL) const + { + const ElementNode *pElem = findChildElementNS(pcszChildNamespace, pcszChild); + if (pElem) + return pElem->findAttributeValue(pcszAttribute, pcszAttributeNamespace); + return NULL; + } + + /** Combines findChildElementP and findAttributeValue. + * + * @returns Pointer to attribute string value, NULL if either the element or + * the attribute was not found. + * @param pcszPath Path to the child element. Slashes can be used + * to make a simple path to any decendant. + * @param pcszAttribute The attribute name. + * @param pcszPathNamespace The namespace to match @a pcszPath with, NULL + * (default) match any namespace. When using a + * path, this matches all elements along the way. + * @param pcszAttributeNamespace The namespace prefix to apply to the + * attribute, NULL (default) match any namespace. + * @see findChildElementP and findAttributeValue + */ + const char *findChildElementAttributeValueP(const char *pcszPath, const char *pcszAttribute, + const char *pcszPathNamespace = NULL, + const char *pcszAttributeNamespace = NULL) const + { + const ElementNode *pElem = findChildElementP(pcszPath, pcszPathNamespace); + if (pElem) + return pElem->findAttributeValue(pcszAttribute, pcszAttributeNamespace); + return NULL; + } + + /** Combines findChildElementP and findAttributeValueN. + * + * @returns Pointer to attribute string value, NULL if either the element or + * the attribute was not found. + * @param pcszPath The attribute name. Slashes can be used to make a + * simple path to any decendant. + * @param pcszAttribute The attribute name. + * @param cchValueLimit If the length of the returned value exceeds this + * limit a EIPRTFailure exception will be thrown. + * @param pcszPathNamespace The namespace to match @a pcszPath with, NULL + * (default) match any namespace. When using a + * path, this matches all elements along the way. + * @param pcszAttributeNamespace The namespace prefix to apply to the + * attribute, NULL (default) match any namespace. + * @see findChildElementP and findAttributeValue + */ + const char *findChildElementAttributeValuePN(const char *pcszPath, const char *pcszAttribute, + size_t cchValueLimit, + const char *pcszPathNamespace = NULL, + const char *pcszAttributeNamespace = NULL) const + { + const ElementNode *pElem = findChildElementP(pcszPath, pcszPathNamespace); + if (pElem) + return pElem->findAttributeValueN(pcszAttribute, cchValueLimit, pcszAttributeNamespace); + return NULL; + } + + + /** @name Tree enumeration. + * @{ */ + + /** Get the next tree element in a full tree enumeration. + * + * By starting with the root node, this can be used to enumerate the entire tree + * (or sub-tree if @a pElmRoot is used). + * + * @returns Pointer to the next element in the tree, NULL if we're done. + * @param pElmRoot The root of the tree we're enumerating. NULL if + * it's the entire tree. + */ + ElementNode const *getNextTreeElement(ElementNode const *pElmRoot = NULL) const; + RT_CPP_GETTER_UNCONST_RET(ElementNode *, ElementNode, getNextTreeElement, (const ElementNode *pElmRoot = NULL), (pElmRoot)) + + /** Get the first child node. + * @returns Pointer to the first child node, NULL if no children. */ + const Node *getFirstChild() const + { + return RTListGetFirstCpp(&m_children, const Node, m_listEntry); + } + RT_CPP_GETTER_UNCONST_RET(Node *, ElementNode, getFirstChild,(),()) + + /** Get the last child node. + * @returns Pointer to the last child node, NULL if no children. */ + const Node *getLastChild() const + { + return RTListGetLastCpp(&m_children, const Node, m_listEntry); + } + + /** Get the first child node. + * @returns Pointer to the first child node, NULL if no children. */ + const ElementNode *getFirstChildElement() const; + + /** Get the last child node. + * @returns Pointer to the last child node, NULL if no children. */ + const ElementNode *getLastChildElement() const; + + /** Get the previous sibling element. + * @returns Pointer to the previous sibling element, NULL if first child + * element. + * @see getNextSibilingElement, getPrevSibling + */ + const ElementNode *getPrevSibilingElement() const; + + /** Get the next sibling element. + * @returns Pointer to the next sibling element, NULL if last child element. + * @see getPrevSibilingElement, getNextSibling + */ + const ElementNode *getNextSibilingElement() const; + + /** Find the previous element matching the given name and namespace (optionally). + * @returns Pointer to the previous sibling element, NULL if first child + * element. + * @param pcszName The element name to match. + * @param pcszNamespace The namespace name, default is NULL which means + * anything goes. + * @note Changed the order of the arguments. + */ + const ElementNode *findPrevSibilingElement(const char *pcszName, const char *pcszNamespace = NULL) const; + + /** Find the next element matching the given name and namespace (optionally). + * @returns Pointer to the previous sibling element, NULL if first child + * element. + * @param pcszName The element name to match. + * @param pcszNamespace The namespace name, default is NULL which means + * anything goes. + * @note Changed the order of the arguments. + */ + const ElementNode *findNextSibilingElement(const char *pcszName, const char *pcszNamespace = NULL) const; + /** @} */ + + /** @name Attribute enumeration + * @{ */ + + /** Get the first attribute node. + * @returns Pointer to the first child node, NULL if no attributes. */ + const AttributeNode *getFirstAttribute() const + { + return RTListGetFirstCpp(&m_attributes, const AttributeNode, m_listEntry); + } + + /** Get the last attribute node. + * @returns Pointer to the last child node, NULL if no attributes. */ + const AttributeNode *getLastAttribute() const + { + return RTListGetLastCpp(&m_attributes, const AttributeNode, m_listEntry); + } + + /** @} */ + + const AttributeNode *findAttribute(const char *pcszMatch, const char *pcszNamespace = NULL) const; + /** Find the first attribute with the given name, returning its value string. + * @returns Pointer to the attribute string value. + * @param pcszName The attribute name. + * @param pcszNamespace The namespace name, default is NULL which means + * anything goes. + * @see getAttributeValue + */ + const char *findAttributeValue(const char *pcszName, const char *pcszNamespace = NULL) const + { + const AttributeNode *pAttr = findAttribute(pcszName, pcszNamespace); + if (pAttr) + return pAttr->getValue(); + return NULL; + } + /** Find the first attribute with the given name, returning its value string. + * @returns Pointer to the attribute string value. + * @param pcszName The attribute name. + * @param cchValueLimit If the length of the returned value exceeds this + * limit a EIPRTFailure exception will be thrown. + * @param pcszNamespace The namespace name, default is NULL which means + * anything goes. + * @see getAttributeValue + */ + const char *findAttributeValueN(const char *pcszName, size_t cchValueLimit, const char *pcszNamespace = NULL) const + { + const AttributeNode *pAttr = findAttribute(pcszName, pcszNamespace); + if (pAttr) + return pAttr->getValueN(cchValueLimit); + return NULL; + } + + bool getAttributeValue(const char *pcszMatch, const char *&pcsz, const char *pcszNamespace = NULL) const + { return getAttributeValue(pcszMatch, &pcsz, pcszNamespace); } + bool getAttributeValue(const char *pcszMatch, RTCString &str, const char *pcszNamespace = NULL) const + { return getAttributeValue(pcszMatch, &str, pcszNamespace); } + bool getAttributeValuePath(const char *pcszMatch, RTCString &str, const char *pcszNamespace = NULL) const + { return getAttributeValue(pcszMatch, &str, pcszNamespace); } + bool getAttributeValue(const char *pcszMatch, int32_t &i, const char *pcszNamespace = NULL) const + { return getAttributeValue(pcszMatch, &i, pcszNamespace); } + bool getAttributeValue(const char *pcszMatch, uint32_t &i, const char *pcszNamespace = NULL) const + { return getAttributeValue(pcszMatch, &i, pcszNamespace); } + bool getAttributeValue(const char *pcszMatch, int64_t &i, const char *pcszNamespace = NULL) const + { return getAttributeValue(pcszMatch, &i, pcszNamespace); } + bool getAttributeValue(const char *pcszMatch, uint64_t &u, const char *pcszNamespace = NULL) const + { return getAttributeValue(pcszMatch, &u, pcszNamespace); } + bool getAttributeValue(const char *pcszMatch, bool &f, const char *pcszNamespace = NULL) const + { return getAttributeValue(pcszMatch, &f, pcszNamespace); } + bool getAttributeValueN(const char *pcszMatch, const char *&pcsz, size_t cchValueLimit, const char *pcszNamespace = NULL) const + { return getAttributeValueN(pcszMatch, &pcsz, cchValueLimit, pcszNamespace); } + bool getAttributeValueN(const char *pcszMatch, RTCString &str, size_t cchValueLimit, const char *pcszNamespace = NULL) const + { return getAttributeValueN(pcszMatch, &str, cchValueLimit, pcszNamespace); } + bool getAttributeValuePathN(const char *pcszMatch, RTCString &str, size_t cchValueLimit, const char *pcszNamespace = NULL) const + { return getAttributeValueN(pcszMatch, &str, cchValueLimit, pcszNamespace); } + + /** @name Variants that for clarity does not use references for output params. + * @{ */ + bool getAttributeValue(const char *pcszMatch, const char **ppcsz, const char *pcszNamespace = NULL) const; + bool getAttributeValue(const char *pcszMatch, RTCString *pStr, const char *pcszNamespace = NULL) const; + bool getAttributeValuePath(const char *pcszMatch, RTCString *pStr, const char *pcszNamespace = NULL) const; + bool getAttributeValue(const char *pcszMatch, int32_t *pi, const char *pcszNamespace = NULL) const; + bool getAttributeValue(const char *pcszMatch, uint32_t *pu, const char *pcszNamespace = NULL) const; + bool getAttributeValue(const char *pcszMatch, int64_t *piValue, const char *pcszNamespace = NULL) const; + bool getAttributeValue(const char *pcszMatch, uint64_t *pu, const char *pcszNamespace = NULL) const; + bool getAttributeValue(const char *pcszMatch, bool *pf, const char *pcszNamespace = NULL) const; + bool getAttributeValueN(const char *pcszMatch, const char **ppcsz, size_t cchValueLimit, const char *pcszNamespace = NULL) const; + bool getAttributeValueN(const char *pcszMatch, RTCString *pStr, size_t cchValueLimit, const char *pcszNamespace = NULL) const; + bool getAttributeValuePathN(const char *pcszMatch, RTCString *pStr, size_t cchValueLimit, const char *pcszNamespace = NULL) const; + /** @} */ + + /** @name Convenience methods for convering the element value. + * @{ */ + bool getElementValue(int32_t *piValue) const; + bool getElementValue(uint32_t *puValue) const; + bool getElementValue(int64_t *piValue) const; + bool getElementValue(uint64_t *puValue) const; + bool getElementValue(bool *pfValue) const; + /** @} */ + + /** @name Convenience findChildElementValueP and getElementValue. + * @{ */ + bool getChildElementValueP(const char *pcszPath, int32_t *piValue, const char *pcszNamespace = NULL) const + { + const ElementNode *pElem = findChildElementP(pcszPath, pcszNamespace); + return pElem && pElem->getElementValue(piValue); + } + bool getChildElementValueP(const char *pcszPath, uint32_t *puValue, const char *pcszNamespace = NULL) const + { + const ElementNode *pElem = findChildElementP(pcszPath, pcszNamespace); + return pElem && pElem->getElementValue(puValue); + } + bool getChildElementValueP(const char *pcszPath, int64_t *piValue, const char *pcszNamespace = NULL) const + { + const ElementNode *pElem = findChildElementP(pcszPath, pcszNamespace); + return pElem && pElem->getElementValue(piValue); + } + bool getChildElementValueP(const char *pcszPath, uint64_t *puValue, const char *pcszNamespace = NULL) const + { + const ElementNode *pElem = findChildElementP(pcszPath, pcszNamespace); + return pElem && pElem->getElementValue(puValue); + } + bool getChildElementValueP(const char *pcszPath, bool *pfValue, const char *pcszNamespace = NULL) const + { + const ElementNode *pElem = findChildElementP(pcszPath, pcszNamespace); + return pElem && pElem->getElementValue(pfValue); + } + + /** @} */ + + /** @name Convenience findChildElementValueP and getElementValue with a + * default value being return if the child element isn't present. + * + * @remarks These will return false on conversion errors. + * @{ */ + bool getChildElementValueDefP(const char *pcszPath, int32_t iDefault, int32_t *piValue, const char *pcszNamespace = NULL) const + { + const ElementNode *pElem = findChildElementP(pcszPath, pcszNamespace); + if (pElem) + return pElem->getElementValue(piValue); + *piValue = iDefault; + return true; + } + bool getChildElementValueDefP(const char *pcszPath, uint32_t uDefault, uint32_t *puValue, const char *pcszNamespace = NULL) const + { + const ElementNode *pElem = findChildElementP(pcszPath, pcszNamespace); + if (pElem) + return pElem->getElementValue(puValue); + *puValue = uDefault; + return true; + } + bool getChildElementValueDefP(const char *pcszPath, int64_t iDefault, int64_t *piValue, const char *pcszNamespace = NULL) const + { + const ElementNode *pElem = findChildElementP(pcszPath, pcszNamespace); + if (pElem) + return pElem->getElementValue(piValue); + *piValue = iDefault; + return true; + } + bool getChildElementValueDefP(const char *pcszPath, uint64_t uDefault, uint64_t *puValue, const char *pcszNamespace = NULL) const + { + const ElementNode *pElem = findChildElementP(pcszPath, pcszNamespace); + if (pElem) + return pElem->getElementValue(puValue); + *puValue = uDefault; + return true; + } + bool getChildElementValueDefP(const char *pcszPath, bool fDefault, bool *pfValue, const char *pcszNamespace = NULL) const + { + const ElementNode *pElem = findChildElementP(pcszPath, pcszNamespace); + if (pElem) + return pElem->getElementValue(pfValue); + *pfValue = fDefault; + return true; + } + /** @} */ + + ElementNode *createChild(const char *pcszElementName); + + ContentNode *addContent(const char *pcszContent); + ContentNode *addContent(const RTCString &strContent) + { + return addContent(strContent.c_str()); + } + + ContentNode *setContent(const char *pcszContent); + ContentNode *setContent(const RTCString &strContent) + { + return setContent(strContent.c_str()); + } + + AttributeNode *setAttribute(const char *pcszName, const char *pcszValue); + AttributeNode *setAttribute(const char *pcszName, const RTCString &strValue) + { + return setAttribute(pcszName, strValue.c_str()); + } + AttributeNode *setAttributePath(const char *pcszName, const RTCString &strValue); + AttributeNode *setAttribute(const char *pcszName, int32_t i); + AttributeNode *setAttribute(const char *pcszName, uint32_t i); + AttributeNode *setAttribute(const char *pcszName, int64_t i); + AttributeNode *setAttribute(const char *pcszName, uint64_t i); + AttributeNode *setAttributeHex(const char *pcszName, uint32_t i); + AttributeNode *setAttribute(const char *pcszName, bool f); + + virtual ~ElementNode(); + +protected: + // hide the default constructor so people use only our factory methods + ElementNode(const ElementNode *pElmRoot, Node *pParent, PRTLISTANCHOR pListAnchor, xmlNode *pLibNode); + ElementNode(const ElementNode &x); // no copying + + /** We keep a pointer to the root element for attribute namespace handling. */ + const ElementNode *m_pElmRoot; + + /** List of child elements and content nodes. */ + RTLISTANCHOR m_children; + /** List of attributes nodes. */ + RTLISTANCHOR m_attributes; + + static void buildChildren(ElementNode *pElmRoot); + + friend class Node; + friend class Document; + friend class XmlFileParser; +}; + +/** + * Node subclass that represents content (non-element text). + * + * Since the Node constructor is private, one can create new content nodes + * only through the following factory methods: + * + * -- ElementNode::addContent() + */ +class RT_DECL_CLASS ContentNode : public Node +{ +public: + +protected: + // hide the default constructor so people use only our factory methods + ContentNode(Node *pParent, PRTLISTANCHOR pListAnchor, xmlNode *pLibNode); + ContentNode(const ContentNode &x); // no copying + + friend class Node; + friend class ElementNode; +}; + + +/** + * Handy helper class with which one can loop through all or some children + * of a particular element. See NodesLoop::forAllNodes() for details. + */ +class RT_DECL_CLASS NodesLoop +{ +public: + NodesLoop(const ElementNode &node, const char *pcszMatch = NULL); + ~NodesLoop(); + const ElementNode* forAllNodes() const; + +private: + /* Obscure class data */ + struct Data; + Data *m; +}; + +/** + * The XML document class. An instance of this needs to be created by a user + * of the XML classes and then passed to + * + * -- XmlMemParser or XmlFileParser to read an XML document; those classes then + * fill the caller's Document with ElementNode, ContentNode and AttributeNode + * instances. The typical sequence then is: + * @code + Document doc; + XmlFileParser parser; + parser.read("file.xml", doc); + Element *pElmRoot = doc.getRootElement(); + @endcode + * + * -- XmlMemWriter or XmlFileWriter to write out an XML document after it has + * been created and filled. Example: + * + * @code + Document doc; + Element *pElmRoot = doc.createRootElement(); + // add children + xml::XmlFileWriter writer(doc); + writer.write("file.xml", true); + @endcode + */ +class RT_DECL_CLASS Document +{ +public: + Document(); + ~Document(); + + Document(const Document &x); + Document& operator=(const Document &x); + + const ElementNode* getRootElement() const; + ElementNode* getRootElement(); + + ElementNode* createRootElement(const char *pcszRootElementName, + const char *pcszComment = NULL); + +private: + friend class XmlMemParser; + friend class XmlFileParser; + friend class XmlMemWriter; + friend class XmlStringWriter; + friend class XmlFileWriter; + + void refreshInternals(); + + /* Obscure class data */ + struct Data; + Data *m; +}; + +/* + * XmlParserBase + * + */ + +class RT_DECL_CLASS XmlParserBase +{ +protected: + XmlParserBase(); + ~XmlParserBase(); + + xmlParserCtxtPtr m_ctxt; +}; + +/* + * XmlMemParser + * + */ + +class RT_DECL_CLASS XmlMemParser : public XmlParserBase +{ +public: + XmlMemParser(); + ~XmlMemParser(); + + void read(const void* pvBuf, size_t cbSize, const RTCString &strFilename, Document &doc); +}; + +/* + * XmlFileParser + * + */ + +class RT_DECL_CLASS XmlFileParser : public XmlParserBase +{ +public: + XmlFileParser(); + ~XmlFileParser(); + + void read(const RTCString &strFilename, Document &doc); + +private: + /* Obscure class data */ + struct Data; + struct Data *m; + + static int ReadCallback(void *aCtxt, char *aBuf, int aLen) RT_NOTHROW_PROTO; + static int CloseCallback(void *aCtxt) RT_NOTHROW_PROTO; +}; + +/** + * XmlMemWriter + */ +class RT_DECL_CLASS XmlMemWriter +{ +public: + XmlMemWriter(); + ~XmlMemWriter(); + + void write(const Document &doc, void** ppvBuf, size_t *pcbSize); + +private: + void* m_pBuf; +}; + + +/** + * XmlStringWriter - writes the XML to an RTCString instance. + */ +class RT_DECL_CLASS XmlStringWriter +{ +public: + XmlStringWriter(); + + int write(const Document &rDoc, RTCString *pStrDst); + +private: + static int WriteCallbackForSize(void *pvUser, const char *pachBuf, int cbToWrite) RT_NOTHROW_PROTO; + static int WriteCallbackForReal(void *pvUser, const char *pachBuf, int cbToWrite) RT_NOTHROW_PROTO; + static int CloseCallback(void *pvUser) RT_NOTHROW_PROTO; + + /** Pointer to the destination string while we're in the write() call. */ + RTCString *m_pStrDst; + /** Set by WriteCallback if we cannot grow the destination string. */ + bool m_fOutOfMemory; +}; + + +/** + * XmlFileWriter + */ +class RT_DECL_CLASS XmlFileWriter +{ +public: + XmlFileWriter(Document &doc); + ~XmlFileWriter(); + + /** + * Writes the XML document to the specified file. + * + * @param pcszFilename The name of the output file. + * @param fSafe If @c true, some extra safety precautions will be + * taken when writing the file: + * -# The file is written with a '-tmp' suffix. + * -# It is flushed to disk after writing. + * -# Any original file is renamed to '-prev'. + * -# The '-tmp' file is then renamed to the + * specified name. + * -# The directory changes are flushed to disk. + * The suffixes are available via s_pszTmpSuff and + * s_pszPrevSuff. + */ + void write(const char *pcszFilename, bool fSafe); + + static int WriteCallback(void *aCtxt, const char *aBuf, int aLen) RT_NOTHROW_PROTO; + static int CloseCallback(void *aCtxt) RT_NOTHROW_PROTO; + + /** The suffix used by XmlFileWriter::write() for the temporary file. */ + static const char * const s_pszTmpSuff; + /** The suffix used by XmlFileWriter::write() for the previous (backup) file. */ + static const char * const s_pszPrevSuff; + +private: + void writeInternal(const char *pcszFilename, bool fSafe); + + /* Obscure class data */ + struct Data; + Data *m; +}; + +#if defined(_MSC_VER) +#pragma warning (default:4251) +#endif + +/** @} */ + +} // end namespace xml + +#endif /* !IPRT_INCLUDED_cpp_xml_h */ + |