Source code of Windows XP (NT5)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

849 lines
22 KiB

  1. /*++
  2. Copyright (C) 1996-2001 Microsoft Corporation
  3. Module Name:
  4. FLEXARRAY.CPP
  5. Abstract:
  6. CFlexArray and CWStringArray implementation.
  7. These objects can operate from any allocator, and be constructed
  8. on arbitrary memory blocks.
  9. History:
  10. 11-Apr-96 a-raymcc Created.
  11. 24-Apr-96 a-raymcc Updated for CArena support.
  12. --*/
  13. #include "precomp.h"
  14. #include <stdio.h>
  15. #include <flexarry.h>
  16. #include <corex.h>
  17. #include "strutils.h"
  18. //***************************************************************************
  19. //
  20. // CFlexArray::CFlexArray
  21. //
  22. // Constructs the array.
  23. //
  24. // Parameters:
  25. // <nSize> The starting preallocated size of the array.
  26. // <nGrowBy> The amount to grow by when the array fills up.
  27. //
  28. // Size() returns the number of elements in use, not the 'true' size.
  29. //
  30. //***************************************************************************
  31. // ok
  32. CFlexArray::CFlexArray(
  33. int nSize,
  34. int nGrowByPercent
  35. )
  36. {
  37. m_nExtent = nSize;
  38. m_nSize = 0;
  39. m_nGrowByPercent = nGrowByPercent;
  40. if(nSize > 0)
  41. {
  42. m_pArray =
  43. (void**)CWin32DefaultArena::WbemMemAlloc(sizeof(void *) * nSize);
  44. // Check for allocation failures
  45. if ( NULL == m_pArray )
  46. {
  47. m_nExtent = 0;
  48. throw CX_MemoryException();
  49. }
  50. }
  51. else
  52. m_pArray = NULL;
  53. }
  54. //***************************************************************************
  55. //
  56. // CFlexArray::~CFlexArray
  57. //
  58. //***************************************************************************
  59. // ok
  60. CFlexArray::~CFlexArray()
  61. {
  62. CWin32DefaultArena::WbemMemFree(m_pArray);
  63. }
  64. //***************************************************************************
  65. //
  66. // Copy constructor.
  67. //
  68. // Copies the pointers, not their contents.
  69. //
  70. //***************************************************************************
  71. // ok
  72. CFlexArray::CFlexArray(CFlexArray &Src)
  73. {
  74. m_pArray = 0;
  75. m_nSize = 0;
  76. m_nExtent = 0;
  77. m_nGrowByPercent = 0;
  78. *this = Src;
  79. }
  80. //***************************************************************************
  81. //
  82. // operator =
  83. //
  84. // Assignment operator.
  85. //
  86. // Arenas are not copied. This allows transfer of arrays between arenas.
  87. // Arrays are copied by pointer only.
  88. //
  89. //***************************************************************************
  90. // ok
  91. CFlexArray& CFlexArray::operator=(CFlexArray &Src)
  92. {
  93. m_nSize = Src.m_nSize;
  94. m_nExtent = Src.m_nExtent;
  95. m_nGrowByPercent = Src.m_nGrowByPercent;
  96. CWin32DefaultArena::WbemMemFree(m_pArray);
  97. if(m_nExtent > 0)
  98. {
  99. m_pArray =
  100. (void**)CWin32DefaultArena::WbemMemAlloc(sizeof(void *) * m_nExtent);
  101. // Check for allocation failures
  102. if ( NULL == m_pArray )
  103. {
  104. m_nExtent = m_nSize = 0;
  105. throw CX_MemoryException();
  106. }
  107. }
  108. else
  109. m_pArray = NULL;
  110. memcpy(m_pArray, Src.m_pArray, sizeof(void *) * m_nSize);
  111. return *this;
  112. }
  113. //***************************************************************************
  114. //
  115. // CFlexArray::RemoveAt
  116. //
  117. // Removes the element at the specified location. Does not
  118. // actually delete the pointer. Shrinks the array over the top of
  119. // the 'doomed' element.
  120. //
  121. // Parameters:
  122. // <nIndex> The location of the element.
  123. //
  124. // Return value:
  125. // range_error The index is not legal.
  126. // no_error Success.
  127. //
  128. //***************************************************************************
  129. // ok
  130. int CFlexArray::RemoveAt(int nIndex)
  131. {
  132. if (nIndex >= m_nSize)
  133. return range_error;
  134. // Account for the index being 0 based and size being 1 based
  135. MoveMemory( &m_pArray[nIndex], &m_pArray[nIndex+1], ( ( m_nSize - nIndex ) - 1 ) * sizeof(void *) );
  136. m_nSize--;
  137. m_pArray[m_nSize] = 0;
  138. return no_error;
  139. }
  140. int CFlexArray::EnsureExtent(int nExtent)
  141. {
  142. if(m_nExtent < nExtent)
  143. {
  144. m_nExtent = nExtent;
  145. if(m_pArray)
  146. {
  147. register void** pTmp = (void **) CWin32DefaultArena::WbemMemReAlloc(m_pArray, sizeof(void *) * m_nExtent);
  148. if (pTmp == 0)
  149. return out_of_memory;
  150. m_pArray = pTmp;
  151. }
  152. else
  153. {
  154. m_pArray = (void **) CWin32DefaultArena::WbemMemAlloc(sizeof(void *) * m_nExtent);
  155. if (!m_pArray)
  156. {
  157. m_nExtent = 0;
  158. return out_of_memory;
  159. }
  160. }
  161. }
  162. return no_error;
  163. }
  164. //***************************************************************************
  165. //
  166. // CFlexArray::InsertAt
  167. //
  168. // Inserts a new element at the specified location. The pointer is copied.
  169. //
  170. // Parameters:
  171. // <nIndex> The 0-origin location at which to insert the new element.
  172. // <pSrc> The pointer to copy. (contents are not copied).
  173. //
  174. // Return value:
  175. // array_full
  176. // out_of_memory
  177. // no_error
  178. //
  179. //***************************************************************************
  180. // ok
  181. int CFlexArray::InsertAt(int nIndex, void *pSrc)
  182. {
  183. // TEMP: fix for sparse functionality in stdprov
  184. // =============================================
  185. while(nIndex > m_nSize)
  186. Add(NULL);
  187. // If the array is full, we need to expand it.
  188. // ===========================================
  189. if (m_nSize == m_nExtent) {
  190. if (m_nGrowByPercent == 0)
  191. return array_full;
  192. register nTmpExtent = m_nExtent;
  193. m_nExtent += 1;
  194. m_nExtent *= (100 + m_nGrowByPercent);
  195. m_nExtent /= 100;
  196. if(m_pArray)
  197. {
  198. register void** pTmp = (void **) CWin32DefaultArena::WbemMemReAlloc(m_pArray, sizeof(void *) * m_nExtent);
  199. if (pTmp == 0)
  200. {
  201. m_nExtent = nTmpExtent; //Change it back, otherwise the extent could constantly grow even though it keeps failing...
  202. return out_of_memory;
  203. }
  204. m_pArray = pTmp;
  205. }
  206. else
  207. {
  208. m_pArray = (void **) CWin32DefaultArena::WbemMemAlloc(sizeof(void *) * m_nExtent);
  209. if (!m_pArray)
  210. {
  211. m_nExtent = 0;
  212. return out_of_memory;
  213. }
  214. }
  215. }
  216. // Special case of appending. This is so frequent
  217. // compared to true insertion that we want to optimize.
  218. // ====================================================
  219. if (nIndex == m_nSize) {
  220. m_pArray[m_nSize++] = pSrc;
  221. return no_error;
  222. }
  223. // If here, we are inserting at some random location.
  224. // We start at the end of the array and copy all the elements
  225. // one position farther to the end to make a 'hole' for
  226. // the new element.
  227. // ==========================================================
  228. // Account for nIndex being 0 based and m_nSize being 1 based
  229. MoveMemory( &m_pArray[nIndex+1], &m_pArray[nIndex], ( m_nSize - nIndex ) * sizeof(void *) );
  230. m_pArray[nIndex] = pSrc;
  231. m_nSize++;
  232. return no_error;
  233. }
  234. void CFlexArray::Sort()
  235. {
  236. if(m_pArray)
  237. qsort((void*)m_pArray, m_nSize, sizeof(void*), CFlexArray::CompareEls);
  238. }
  239. int __cdecl CFlexArray::CompareEls(const void* pelem1, const void* pelem2)
  240. {
  241. return *(int*)pelem1 - *(int*)pelem2;
  242. }
  243. //***************************************************************************
  244. //
  245. // CFlexArray::DebugDump
  246. //
  247. //***************************************************************************
  248. void CFlexArray::DebugDump()
  249. {
  250. printf("----CFlexArray Debug Dump----\n");
  251. printf("m_pArray = 0x%p\n", m_pArray);
  252. printf("m_nSize = %d\n", m_nSize);
  253. printf("m_nExtent = %d\n", m_nExtent);
  254. printf("m_nGrowByPercent = %d\n", m_nGrowByPercent);
  255. for (int i = 0; i < m_nExtent; i++)
  256. {
  257. if (i < m_nSize)
  258. printf("![%d] = %p\n", i, m_pArray[i]);
  259. else
  260. printf("?[%d] = %p\n", i, m_pArray[i]);
  261. }
  262. }
  263. //***************************************************************************
  264. //
  265. // CFlexArray::Compress
  266. //
  267. // Removes NULL elements by moving all non-NULL pointers to the beginning
  268. // of the array. The array "Size" changes, but the extent is untouched.
  269. //
  270. //***************************************************************************
  271. // ok
  272. void CFlexArray::Compress()
  273. {
  274. int nLeftCursor = 0, nRightCursor = 0;
  275. while (nLeftCursor < m_nSize - 1) {
  276. if (m_pArray[nLeftCursor]) {
  277. nLeftCursor++;
  278. continue;
  279. }
  280. else {
  281. nRightCursor = nLeftCursor + 1;
  282. while ( nRightCursor < m_nSize && m_pArray[nRightCursor] == 0 )
  283. nRightCursor++;
  284. if (nRightCursor == m_nSize)
  285. break; // Short circuit, no more nonzero elements.
  286. m_pArray[nLeftCursor] = m_pArray[nRightCursor];
  287. m_pArray[nRightCursor] = 0;
  288. }
  289. }
  290. Trim();
  291. }
  292. void CFlexArray::Trim()
  293. {
  294. while (m_nSize > 0 && m_pArray[m_nSize - 1] == NULL) m_nSize--;
  295. }
  296. //***************************************************************************
  297. //
  298. // CFlexArray::Empty
  299. //
  300. // Clears the array of all pointers (does not deallocate them) and sets
  301. // its apparent size to zero.
  302. //
  303. //***************************************************************************
  304. // ok
  305. void CFlexArray::Empty()
  306. {
  307. CWin32DefaultArena::WbemMemFree(m_pArray);
  308. m_pArray = NULL;
  309. m_nSize = 0;
  310. m_nExtent = 0;
  311. }
  312. //***************************************************************************
  313. //
  314. // CFlexArray::UnbindPtr
  315. //
  316. // Empties the array and returns the pointer to the data it contained
  317. //
  318. //***************************************************************************
  319. void** CFlexArray::UnbindPtr()
  320. {
  321. void** pp = m_pArray;
  322. m_pArray = NULL;
  323. Empty();
  324. return pp;
  325. }
  326. //***************************************************************************
  327. //
  328. // CFlexArray::CopyData
  329. //
  330. // Copies the data but not the settings of another flexarray
  331. //
  332. //***************************************************************************
  333. int CFlexArray::CopyDataFrom(const CFlexArray& aOther)
  334. {
  335. // Check if there is enough room
  336. // =============================
  337. if(aOther.m_nSize > m_nExtent)
  338. {
  339. // Extend the array to the requisite size
  340. // ======================================
  341. m_nExtent = aOther.m_nSize;
  342. if(m_pArray)
  343. {
  344. register void** pTmp = (void **) CWin32DefaultArena::WbemMemReAlloc(m_pArray, sizeof(void *) * m_nExtent);
  345. if (pTmp == 0)
  346. return out_of_memory;
  347. m_pArray = pTmp;
  348. }
  349. else
  350. {
  351. m_pArray = (void **) CWin32DefaultArena::WbemMemAlloc(sizeof(void *) * m_nExtent);
  352. if (!m_pArray)
  353. {
  354. m_nExtent = 0;
  355. return out_of_memory;
  356. }
  357. }
  358. }
  359. // Copy the data
  360. // =============
  361. m_nSize = aOther.m_nSize;
  362. memcpy(m_pArray, aOther.m_pArray, sizeof(void*) * m_nSize);
  363. return no_error;
  364. }
  365. //***************************************************************************
  366. //
  367. // CWStringArray::CWStringArray
  368. //
  369. // Constructs a wide-string array.
  370. //
  371. // Parameters:
  372. // <nSize> The starting preallocated size of the array.
  373. // <nGrowBy> The amount to grow by when the array fills up.
  374. //
  375. // Size() returns the number of elements in use, not the 'true' size.
  376. //
  377. //***************************************************************************
  378. CWStringArray::CWStringArray(
  379. int nSize,
  380. int nGrowBy
  381. )
  382. :
  383. m_Array(nSize, nGrowBy)
  384. {
  385. }
  386. //***************************************************************************
  387. //
  388. // Copy constructor.
  389. //
  390. //***************************************************************************
  391. CWStringArray::CWStringArray(CWStringArray &Src)
  392. {
  393. *this = Src;
  394. }
  395. //***************************************************************************
  396. //
  397. // Destructor. Cleans up all the strings.
  398. //
  399. //***************************************************************************
  400. CWStringArray::~CWStringArray()
  401. {
  402. Empty();
  403. }
  404. //***************************************************************************
  405. //
  406. // CWStringArray::DeleteStr
  407. //
  408. // Frees the string at the specified index and sets the element to NULL.
  409. // Does not compress array.
  410. //
  411. // Does not currently do a range check.
  412. //
  413. // Parameters:
  414. // <nIndex> The 0-origin index of the string to remove.
  415. //
  416. // Return values:
  417. // no_error
  418. //
  419. //***************************************************************************
  420. int CWStringArray::DeleteStr(int nIndex)
  421. {
  422. CWin32DefaultArena::WbemMemFree(m_Array[nIndex]);
  423. m_Array[nIndex] = 0;
  424. return no_error;
  425. }
  426. //***************************************************************************
  427. //
  428. // CWStringArray::FindStr
  429. //
  430. // Finds the specified string and returns its location.
  431. //
  432. // Parameters:
  433. // <pTarget> The string to find.
  434. // <nFlags> <no_case> or <with_case>
  435. //
  436. // Return value:
  437. // The 0-origin location of the string, or -1 if not found.
  438. //
  439. //***************************************************************************
  440. int CWStringArray::FindStr(const wchar_t *pTarget, int nFlags)
  441. {
  442. if (nFlags == no_case) {
  443. for (int i = 0; i < m_Array.Size(); i++)
  444. if (wbem_wcsicmp((wchar_t *) m_Array[i], pTarget) == 0)
  445. return i;
  446. }
  447. else {
  448. for (int i = 0; i < m_Array.Size(); i++)
  449. if (wcscmp((wchar_t *) m_Array[i], pTarget) == 0)
  450. return i;
  451. }
  452. return not_found;
  453. }
  454. //***************************************************************************
  455. //
  456. // operator =
  457. //
  458. //***************************************************************************
  459. // Heap handle & allocation functions are not copied. This allows
  460. // transfer of arrays between heaps.
  461. CWStringArray& CWStringArray::operator =(CWStringArray &Src)
  462. {
  463. Empty();
  464. for (int i = 0; i < Src.Size(); i++)
  465. {
  466. wchar_t *pSrc = (wchar_t *) Src.m_Array[i];
  467. wchar_t *pCopy = (wchar_t *) CWin32DefaultArena::WbemMemAlloc((wcslen(pSrc) + 1) * 2);
  468. // Check for allocation failures
  469. if ( NULL == pCopy )
  470. {
  471. throw CX_MemoryException();
  472. }
  473. wcscpy(pCopy, pSrc);
  474. if ( m_Array.Add(pCopy) != CFlexArray::no_error )
  475. {
  476. throw CX_MemoryException();
  477. }
  478. }
  479. return *this;
  480. }
  481. //***************************************************************************
  482. //
  483. // CWStringArray::Add
  484. //
  485. // Appends a new string to the end of the array.
  486. //
  487. // Parameters:
  488. // <pSrc> The string to copy.
  489. //
  490. // Return value:
  491. // The return values of CFlexArray::Add.
  492. //
  493. //***************************************************************************
  494. int CWStringArray::Add(const wchar_t *pSrc)
  495. {
  496. wchar_t *pNewStr = (wchar_t *) CWin32DefaultArena::WbemMemAlloc((wcslen(pSrc) + 1) * 2);
  497. // Check for allocation failures
  498. if ( NULL == pNewStr )
  499. {
  500. return out_of_memory;
  501. }
  502. wcscpy(pNewStr, pSrc);
  503. return m_Array.Add(pNewStr);
  504. }
  505. //***************************************************************************
  506. //
  507. // CWStringArray::InsertAt
  508. //
  509. // Inserts a copy of a string in the array.
  510. //
  511. // Parameters:
  512. // <nIndex> The 0-origin location at which to insert the string.
  513. // <pSrc> The string to copy.
  514. //
  515. // Return values:
  516. // The return values of CFlexArray::InsertAt
  517. //
  518. //***************************************************************************
  519. int CWStringArray::InsertAt(int nIndex, const wchar_t *pSrc)
  520. {
  521. wchar_t *pNewStr = (wchar_t *) CWin32DefaultArena::WbemMemAlloc((wcslen(pSrc) + 1) * 2);
  522. // Check for allocation failures
  523. if ( NULL == pNewStr )
  524. {
  525. return out_of_memory;
  526. }
  527. wcscpy(pNewStr, pSrc);
  528. int iRet = m_Array.InsertAt(nIndex, pNewStr);
  529. if (iRet == array_full)
  530. CWin32DefaultArena::WbemMemFree(pNewStr);
  531. return iRet;
  532. }
  533. //***************************************************************************
  534. //
  535. // CWStringArray::RemoveAt
  536. //
  537. // Removes and deallocates the string at the specified location.
  538. // Shrinks the array.
  539. //
  540. // Parameters:
  541. // <nIndex> The 0-origin index of the 'doomed' string.
  542. //
  543. // Return value:
  544. // Same as CFlexArray::RemoveAt.
  545. //
  546. //***************************************************************************
  547. int CWStringArray::RemoveAt(int nIndex)
  548. {
  549. wchar_t *pDoomedString = (wchar_t *) m_Array[nIndex];
  550. CWin32DefaultArena::WbemMemFree(pDoomedString);
  551. return m_Array.RemoveAt(nIndex);
  552. }
  553. //***************************************************************************
  554. //
  555. // CWStringArray::SetAt
  556. //
  557. // Replaces the string at the targeted location with the new one.
  558. // The old string at the location is cleaned up.
  559. //
  560. // No range checking or out-of-memory checks at present.
  561. //
  562. // Parameters:
  563. // <nIndex> The 0-origin location at which to replace the string.
  564. // <pSrc> The string to copy.
  565. //
  566. // Return value:
  567. // no_error
  568. //
  569. //***************************************************************************
  570. int CWStringArray::SetAt(int nIndex, const wchar_t *pSrc)
  571. {
  572. wchar_t *pNewStr = (wchar_t *) CWin32DefaultArena::WbemMemAlloc((wcslen(pSrc) + 1) * 2);
  573. // Check for allocation failures
  574. if ( NULL == pNewStr )
  575. {
  576. return out_of_memory;
  577. }
  578. wchar_t *pDoomedString = (wchar_t *) m_Array[nIndex];
  579. if (pDoomedString)
  580. CWin32DefaultArena::WbemMemFree(pDoomedString);
  581. wcscpy(pNewStr, pSrc);
  582. m_Array[nIndex] = pNewStr;
  583. return no_error;
  584. }
  585. //***************************************************************************
  586. //
  587. // CWStringArray::ReplaceAt
  588. //
  589. // Directly replaces the pointer at the specified location with the
  590. // one in the parameter. No copy or cleanup.
  591. //
  592. // Parameters:
  593. // <nIndex> The 0-origin location at which to replace.
  594. // <pSrc> The new pointer to copy over the old one.
  595. //
  596. // Return value:
  597. // no_error (No checking done at present).
  598. //
  599. //***************************************************************************
  600. int CWStringArray::ReplaceAt(int nIndex, wchar_t *pSrc)
  601. {
  602. m_Array[nIndex] = pSrc;
  603. return no_error;
  604. }
  605. //***************************************************************************
  606. //
  607. // CWStringArray::Empty
  608. //
  609. // Empties the array, deallocates all strings, and sets the apparent
  610. // array size to zero.
  611. //
  612. //***************************************************************************
  613. void CWStringArray::Empty()
  614. {
  615. for (int i = 0; i < m_Array.Size(); i++)
  616. CWin32DefaultArena::WbemMemFree(m_Array[i]);
  617. m_Array.Empty();
  618. }
  619. //***************************************************************************
  620. //
  621. // CWStringArray::Sort
  622. //
  623. // Sorts the array according to UNICODE order.
  624. // (Shell sort).
  625. //
  626. //***************************************************************************
  627. void CWStringArray::Sort()
  628. {
  629. for (int nInterval = 1; nInterval < m_Array.Size() / 9; nInterval = nInterval * 3 + 1);
  630. while (nInterval)
  631. {
  632. for (int iCursor = nInterval; iCursor < m_Array.Size(); iCursor++)
  633. {
  634. int iBackscan = iCursor;
  635. while (iBackscan - nInterval >= 0 &&
  636. wbem_wcsicmp((const wchar_t *) m_Array[iBackscan],
  637. (const wchar_t *) m_Array[iBackscan-nInterval]) < 0)
  638. {
  639. wchar_t *pTemp = (wchar_t *) m_Array[iBackscan - nInterval];
  640. m_Array[iBackscan - nInterval] = m_Array[iBackscan];
  641. m_Array[iBackscan] = pTemp;
  642. iBackscan -= nInterval;
  643. }
  644. }
  645. nInterval /= 3;
  646. }
  647. }
  648. //***************************************************************************
  649. //
  650. // CWStringArray::Difference
  651. //
  652. // Set-theoretic difference operation on the arrays.
  653. //
  654. // Parameters:
  655. // <Src1> First array (not modified).
  656. // <Src2> Second array which is 'subtracted' from first (not modified).
  657. // <Diff> Receives the difference. Should be an empty array on entry.
  658. //
  659. //***************************************************************************
  660. void CWStringArray::Difference(
  661. CWStringArray &Src1,
  662. CWStringArray &Src2,
  663. CWStringArray &Diff
  664. )
  665. {
  666. for (int i = 0; i < Src1.Size(); i++)
  667. {
  668. if (Src2.FindStr(Src1[i], no_case) == -1)
  669. {
  670. if ( Diff.Add(Src1[i]) != no_error )
  671. {
  672. throw CX_MemoryException();
  673. }
  674. }
  675. }
  676. }
  677. //***************************************************************************
  678. //
  679. // CWStringArray::Intersection
  680. //
  681. // Set-theoretic intersection operation on the arrays.
  682. //
  683. // Parameters:
  684. // <Src1> First array (not modified).
  685. // <Src2> Second array (not modified).
  686. // <Diff> Receives the intersection. Should be an empty array on entry.
  687. //***************************************************************************
  688. void CWStringArray::Intersection(
  689. CWStringArray &Src1,
  690. CWStringArray &Src2,
  691. CWStringArray &Output
  692. )
  693. {
  694. for (int i = 0; i < Src1.Size(); i++)
  695. {
  696. if (Src2.FindStr(Src1[i], no_case) != -1)
  697. {
  698. if ( Output.Add(Src1[i]) != no_error )
  699. {
  700. throw CX_MemoryException();
  701. }
  702. }
  703. }
  704. }
  705. //***************************************************************************
  706. //
  707. // CWStringArray::Union
  708. //
  709. // Set-theoretic union operation on the arrays.
  710. //
  711. // Parameters:
  712. // <Src1> First array (not modified).
  713. // <Src2> Second array (not modified).
  714. // <Diff> Receives the union. Should be an empty array on entry.
  715. //
  716. //***************************************************************************
  717. void CWStringArray::Union(
  718. CWStringArray &Src1,
  719. CWStringArray &Src2,
  720. CWStringArray &Output
  721. )
  722. {
  723. Output = Src1;
  724. for (int i = 0; i < Src2.Size(); i++)
  725. {
  726. if (Output.FindStr(Src2[i], no_case) == not_found)
  727. {
  728. if ( Output.Add(Src2[i]) != no_error )
  729. {
  730. throw CX_MemoryException();
  731. }
  732. }
  733. }
  734. }