/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ /* * This file is part of the LibreOffice project. * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. * * This file incorporates work covered by the following license notice: * * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed * with this work for additional information regarding copyright * ownership. The ASF licenses this file to you under the Apache * License, Version 2.0 (the "License"); you may not use this file * except in compliance with the License. You may obtain a copy of * the License at http://www.apache.org/licenses/LICENSE-2.0 . */ #include #include #include #include /** Resize block management by this constant. As a result there are approx. 20 * MAXENTRY == 20000 entries available */ const sal_uInt16 nBlockGrowSize = 20; #if OSL_DEBUG_LEVEL > 2 #define CHECKIDX( p, n, i, c ) CheckIdx( p, n, i, c ); void CheckIdx( BlockInfo** ppInf, sal_uInt16 nBlock, sal_uLong nSize, sal_uInt16 nCur ) { assert( !nSize || nCur < nBlock ); // BigPtrArray: CurIndex invalid sal_uLong nIdx = 0; for( sal_uInt16 nCnt = 0; nCnt < nBlock; ++nCnt, ++ppInf ) { nIdx += (*ppInf)->nElem; // Array with holes is not allowed assert( !nCnt || (*(ppInf-1))->nEnd + 1 == (*ppInf)->nStart ); } assert(nIdx == nSize); // invalid count in nSize } #else #define CHECKIDX( p, n, i, c ) #endif BigPtrArray::BigPtrArray() { m_nBlock = m_nCur = 0; m_nSize = 0; m_nMaxBlock = nBlockGrowSize; m_ppInf.reset( new BlockInfo* [ m_nMaxBlock ] ); } BigPtrArray::~BigPtrArray() { if( m_nBlock ) { BlockInfo** pp = m_ppInf.get(); for( sal_uInt16 n = 0; n < m_nBlock; ++n, ++pp ) { delete *pp; } } } // Also moving is done simply here. Optimization is useless because of the // division of this field into multiple parts. void BigPtrArray::Move( sal_uLong from, sal_uLong to ) { if (from != to) { sal_uInt16 cur = Index2Block( from ); BlockInfo* p = m_ppInf[ cur ]; BigPtrEntry* pElem = p->mvData[ from - p->nStart ]; Insert( pElem, to ); // insert first, then delete! Remove( ( to < from ) ? ( from + 1 ) : from ); } } BigPtrEntry* BigPtrArray::operator[]( sal_uLong idx ) const { assert(idx < m_nSize); // operator[]: Index out of bounds m_nCur = Index2Block( idx ); BlockInfo* p = m_ppInf[ m_nCur ]; return p->mvData[ idx - p->nStart ]; } /** Search a block at a given position */ sal_uInt16 BigPtrArray::Index2Block( sal_uLong pos ) const { // last used block? BlockInfo* p = m_ppInf[ m_nCur ]; if( p->nStart <= pos && p->nEnd >= pos ) return m_nCur; // Index = 0? if( !pos ) return 0; // following one? if( m_nCur < ( m_nBlock - 1 ) ) { p = m_ppInf[ m_nCur+1 ]; if( p->nStart <= pos && p->nEnd >= pos ) return m_nCur+1; } // previous one? else if( pos < p->nStart && m_nCur > 0 ) { p = m_ppInf[ m_nCur-1 ]; if( p->nStart <= pos && p->nEnd >= pos ) return m_nCur-1; } // binary search: always successful sal_uInt16 lower = 0, upper = m_nBlock - 1; sal_uInt16 cur = 0; for(;;) { sal_uInt16 n = lower + ( upper - lower ) / 2; cur = ( n == cur ) ? n+1 : n; p = m_ppInf[ cur ]; if( p->nStart <= pos && p->nEnd >= pos ) return cur; if( p->nStart > pos ) upper = cur; else lower = cur; } } /** Update all index areas @param pos last correct block (starting point) */ void BigPtrArray::UpdIndex( sal_uInt16 pos ) { BlockInfo** pp = m_ppInf.get() + pos; sal_uLong idx = (*pp)->nEnd + 1; while( ++pos < m_nBlock ) { BlockInfo* p = *++pp; p->nStart = idx; idx += p->nElem; p->nEnd = idx - 1; } } /** Create and insert new block Existing blocks will be moved rearward. @param pos Position at which the new block should be created. */ BlockInfo* BigPtrArray::InsBlock( sal_uInt16 pos ) { if( m_nBlock == m_nMaxBlock ) { // than extend the array first BlockInfo** ppNew = new BlockInfo* [ m_nMaxBlock + nBlockGrowSize ]; memcpy( ppNew, m_ppInf.get(), m_nMaxBlock * sizeof( BlockInfo* )); m_nMaxBlock += nBlockGrowSize; m_ppInf.reset( ppNew ); } if( pos != m_nBlock ) { memmove( m_ppInf.get() + pos+1, m_ppInf.get() + pos, ( m_nBlock - pos ) * sizeof( BlockInfo* )); } ++m_nBlock; BlockInfo* p = new BlockInfo; m_ppInf[ pos ] = p; if( pos ) p->nStart = p->nEnd = m_ppInf[ pos-1 ]->nEnd + 1; else p->nStart = p->nEnd = 0; p->nEnd--; // no elements p->nElem = 0; p->pBigArr = this; return p; } void BigPtrArray::BlockDel( sal_uInt16 nDel ) { m_nBlock = m_nBlock - nDel; if( m_nMaxBlock - m_nBlock > nBlockGrowSize ) { // than shrink array nDel = (( m_nBlock / nBlockGrowSize ) + 1 ) * nBlockGrowSize; BlockInfo** ppNew = new BlockInfo* [ nDel ]; memcpy( ppNew, m_ppInf.get(), m_nBlock * sizeof( BlockInfo* )); m_ppInf.reset( ppNew ); m_nMaxBlock = nDel; } } void BigPtrArray::Insert( BigPtrEntry* pElem, sal_uLong pos ) { CHECKIDX( m_ppInf.get(), m_nBlock, m_nSize, m_nCur ); BlockInfo* p; sal_uInt16 cur; if( !m_nSize ) { // special case: insert first element cur = 0; p = InsBlock( cur ); } else if( pos == m_nSize ) { // special case: insert at end cur = m_nBlock - 1; p = m_ppInf[ cur ]; if( p->nElem == MAXENTRY ) // the last block is full, create a new one p = InsBlock( ++cur ); } else { // standard case: cur = Index2Block( pos ); p = m_ppInf[ cur ]; } if( p->nElem == MAXENTRY ) { // does the last entry fit into the next block? BlockInfo* q; if( cur < ( m_nBlock - 1 ) && m_ppInf[ cur+1 ]->nElem < MAXENTRY ) { q = m_ppInf[ cur+1 ]; if( q->nElem ) { int nCount = q->nElem; auto pFrom = q->mvData.begin() + nCount; auto pTo = pFrom + 1; while( nCount-- ) { *--pTo = *--pFrom; ++((*pTo)->m_nOffset); } } q->nStart--; q->nEnd--; } else { // If it does not fit, then insert a new block. But if there is more // than 50% space in the array then compress first. if( /*nBlock == nMaxBlock &&*/ m_nBlock > ( m_nSize / ( MAXENTRY / 2 ) ) && cur >= Compress() ) { // Something was moved before the current position and all // pointer might be invalid. Thus restart Insert. Insert( pElem, pos ); return ; } q = InsBlock( cur+1 ); } // entry does not fit anymore - clear space BigPtrEntry* pLast = p->mvData[ MAXENTRY-1 ]; pLast->m_nOffset = 0; pLast->m_pBlock = q; q->mvData[ 0 ] = pLast; q->nElem++; q->nEnd++; p->nEnd--; p->nElem--; } // now we have free space - insert pos -= p->nStart; assert(pos < MAXENTRY); if( pos != p->nElem ) { int nCount = p->nElem - sal_uInt16(pos); auto pFrom = p->mvData.begin() + p->nElem; auto pTo = pFrom + 1; while( nCount-- ) { *--pTo = *--pFrom; ++( *pTo )->m_nOffset; } } // insert element and update indices pElem->m_nOffset = sal_uInt16(pos); pElem->m_pBlock = p; p->mvData[ pos ] = pElem; p->nEnd++; p->nElem++; m_nSize++; if( cur != ( m_nBlock - 1 ) ) UpdIndex( cur ); m_nCur = cur; CHECKIDX( m_ppInf.get(), m_nBlock, m_nSize, m_nCur ); } void BigPtrArray::Remove( sal_uLong pos, sal_uLong n ) { CHECKIDX( m_ppInf.get(), m_nBlock, m_nSize, m_nCur ); sal_uInt16 nBlkdel = 0; // deleted blocks sal_uInt16 cur = Index2Block( pos ); // current block number sal_uInt16 nBlk1 = cur; // 1st treated block sal_uInt16 nBlk1del = USHRT_MAX; // 1st deleted block BlockInfo* p = m_ppInf[ cur ]; pos -= p->nStart; sal_uLong nElem = n; while( nElem ) { sal_uInt16 nel = p->nElem - sal_uInt16(pos); if( sal_uLong(nel) > nElem ) nel = sal_uInt16(nElem); // move elements if needed if( ( pos + nel ) < sal_uLong(p->nElem) ) { auto pTo = p->mvData.begin() + pos; auto pFrom = pTo + nel; int nCount = p->nElem - nel - sal_uInt16(pos); while( nCount-- ) { *pTo = *pFrom++; (*pTo)->m_nOffset = (*pTo)->m_nOffset - nel; ++pTo; } } p->nEnd -= nel; p->nElem = p->nElem - nel; // possibly delete block completely if( !p->nElem ) { nBlkdel++; if( USHRT_MAX == nBlk1del ) nBlk1del = cur; } nElem -= nel; if( !nElem ) break; p = m_ppInf[ ++cur ]; pos = 0; } // update table if blocks were removed if( nBlkdel ) { for( sal_uInt16 i = nBlk1del; i < ( nBlk1del + nBlkdel ); i++ ) delete m_ppInf[ i ]; if( ( nBlk1del + nBlkdel ) < m_nBlock ) { memmove( m_ppInf.get() + nBlk1del, m_ppInf.get() + nBlk1del + nBlkdel, ( m_nBlock - nBlkdel - nBlk1del ) * sizeof( BlockInfo* ) ); // UpdateIdx updates the successor thus start before first elem if( !nBlk1 ) { p = m_ppInf[ 0 ]; p->nStart = 0; p->nEnd = p->nElem-1; } else { --nBlk1; } } BlockDel( nBlkdel ); // blocks were deleted } m_nSize -= n; if( nBlk1 != ( m_nBlock - 1 ) && m_nSize ) UpdIndex( nBlk1 ); m_nCur = nBlk1; // call Compress() if there is more than 50% space in the array if( m_nBlock > ( m_nSize / ( MAXENTRY / 2 ) ) ) Compress(); CHECKIDX( m_ppInf.get(), m_nBlock, m_nSize, m_nCur ); } void BigPtrArray::Replace( sal_uLong idx, BigPtrEntry* pElem) { assert(idx < m_nSize); // Index out of bounds m_nCur = Index2Block( idx ); BlockInfo* p = m_ppInf[ m_nCur ]; pElem->m_nOffset = sal_uInt16(idx - p->nStart); pElem->m_pBlock = p; p->mvData[ idx - p->nStart ] = pElem; } /** Compress the array */ sal_uInt16 BigPtrArray::Compress() { CHECKIDX( m_ppInf.get(), m_nBlock, m_nSize, m_nCur ); // Iterate over InfoBlock array from beginning to end. If there is a deleted // block in between so move all following ones accordingly. The pointer // represents the "old" and the "new" array. BlockInfo** pp = m_ppInf.get(), **qq = pp; BlockInfo* p; BlockInfo* pLast(nullptr); // last empty block sal_uInt16 nLast = 0; // missing elements sal_uInt16 nBlkdel = 0; // number of deleted blocks sal_uInt16 nFirstChgPos = USHRT_MAX; // at which position was the 1st change? // convert fill percentage into number of remaining elements short const nMax = MAXENTRY - tools::Long(MAXENTRY) * COMPRESSLVL / 100; for( sal_uInt16 cur = 0; cur < m_nBlock; ++cur ) { p = *pp++; sal_uInt16 n = p->nElem; // Check if a not completely full block will be ignored. This happens if // the current block would have to be split but the filling of the // inspected block is already over its threshold value. In this case we // do not fill more (it's expensive because of a double memmove() call) if( nLast && ( n > nLast ) && ( nLast < nMax ) ) nLast = 0; if( nLast ) { if( USHRT_MAX == nFirstChgPos ) nFirstChgPos = cur; // Not full yet? Then fill up. if( n > nLast ) n = nLast; // move elements from current to last block auto pElem = pLast->mvData.begin() + pLast->nElem; auto pFrom = p->mvData.begin(); for( sal_uInt16 nCount = n, nOff = pLast->nElem; nCount; --nCount, ++pElem ) { *pElem = *pFrom++; (*pElem)->m_pBlock = pLast; (*pElem)->m_nOffset = nOff++; } // adjustment pLast->nElem = pLast->nElem + n; nLast = nLast - n; p->nElem = p->nElem - n; // Is the current block now empty as a result? if( !p->nElem ) { // then remove delete p; p = nullptr; ++nBlkdel; } else { pElem = p->mvData.begin(); pFrom = pElem + n; int nCount = p->nElem; while( nCount-- ) { *pElem = *pFrom++; (*pElem)->m_nOffset = (*pElem)->m_nOffset - n; ++pElem; } } } if( p ) // BlockInfo was not deleted { *qq++ = p; // adjust to correct position // keep the potentially existing last half-full block if( !nLast && p->nElem < MAXENTRY ) { pLast = p; nLast = MAXENTRY - p->nElem; } } } // if blocks were deleted shrink BlockInfo array if needed if( nBlkdel ) BlockDel( nBlkdel ); // and re-index p = m_ppInf[ 0 ]; p->nEnd = p->nElem - 1; UpdIndex( 0 ); if( m_nCur >= nFirstChgPos ) m_nCur = 0; CHECKIDX( m_ppInf.get(), m_nBlock, m_nSize, m_nCur ); return nFirstChgPos; } /* vim:set shiftwidth=4 softtabstop=4 expandtab: */