1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 * This file incorporates work covered by the following license notice:
11 * Licensed to the Apache Software Foundation (ASF) under one or more
12 * contributor license agreements. See the NOTICE file distributed
13 * with this work for additional information regarding copyright
14 * ownership. The ASF licenses this file to you under the Apache
15 * License, Version 2.0 (the "License"); you may not use this file
16 * except in compliance with the License. You may obtain a copy of
17 * the License at http://www.apache.org/licenses/LICENSE-2.0 .
21 #include <tools/long.hxx>
25 /** Resize block management by this constant.
26 As a result there are approx. 20 * MAXENTRY == 20000 entries available */
27 const sal_uInt16 nBlockGrowSize
= 20;
29 #if OSL_DEBUG_LEVEL > 2
30 #define CHECKIDX( p, n, i, c ) CheckIdx( p, n, i, c );
31 void CheckIdx( BlockInfo
** ppInf
, sal_uInt16 nBlock
, sal_Int32 nSize
, sal_uInt16 nCur
)
33 assert( !nSize
|| nCur
< nBlock
); // BigPtrArray: CurIndex invalid
36 for( sal_uInt16 nCnt
= 0; nCnt
< nBlock
; ++nCnt
, ++ppInf
)
38 nIdx
+= (*ppInf
)->nElem
;
39 // Array with holes is not allowed
40 assert( !nCnt
|| (*(ppInf
-1))->nEnd
+ 1 == (*ppInf
)->nStart
);
42 assert(nIdx
== nSize
); // invalid count in nSize
45 #define CHECKIDX( p, n, i, c )
48 BigPtrArray::BigPtrArray()
50 m_nBlock
= m_nCur
= 0;
52 m_nMaxBlock
= nBlockGrowSize
;
53 m_ppInf
.reset( new BlockInfo
* [ m_nMaxBlock
] );
56 BigPtrArray::~BigPtrArray()
60 BlockInfo
** pp
= m_ppInf
.get();
61 for( sal_uInt16 n
= 0; n
< m_nBlock
; ++n
, ++pp
)
68 // Also moving is done simply here. Optimization is useless because of the
69 // division of this field into multiple parts.
70 void BigPtrArray::Move( sal_Int32 from
, sal_Int32 to
)
74 sal_uInt16 cur
= Index2Block( from
);
75 BlockInfo
* p
= m_ppInf
[ cur
];
76 BigPtrEntry
* pElem
= p
->mvData
[ from
- p
->nStart
];
77 Insert( pElem
, to
); // insert first, then delete!
78 ImplRemove( ( to
< from
) ? ( from
+ 1 ) : from
, 1, /*bClearElement*/false );
82 BigPtrEntry
* BigPtrArray::operator[]( sal_Int32 idx
) const
84 assert(idx
< m_nSize
); // operator[]: Index out of bounds
85 m_nCur
= Index2Block( idx
);
86 BlockInfo
* p
= m_ppInf
[ m_nCur
];
87 return p
->mvData
[ idx
- p
->nStart
];
90 /** Search a block at a given position */
91 sal_uInt16
BigPtrArray::Index2Block( sal_Int32 pos
) const
94 BlockInfo
* p
= m_ppInf
[ m_nCur
];
95 if( p
->nStart
<= pos
&& p
->nEnd
>= pos
)
102 if( m_nCur
< ( m_nBlock
- 1 ) )
104 p
= m_ppInf
[ m_nCur
+1 ];
105 if( p
->nStart
<= pos
&& p
->nEnd
>= pos
)
109 else if( pos
< p
->nStart
&& m_nCur
> 0 )
111 p
= m_ppInf
[ m_nCur
-1 ];
112 if( p
->nStart
<= pos
&& p
->nEnd
>= pos
)
116 // binary search: always successful
117 sal_uInt16 lower
= 0, upper
= m_nBlock
- 1;
121 sal_uInt16 n
= lower
+ ( upper
- lower
) / 2;
122 cur
= ( n
== cur
) ? n
+1 : n
;
124 if( p
->nStart
<= pos
&& p
->nEnd
>= pos
)
127 if( p
->nStart
> pos
)
134 /** Update all index areas
136 @param pos last correct block (starting point)
138 void BigPtrArray::UpdIndex( sal_uInt16 pos
)
140 BlockInfo
** pp
= m_ppInf
.get() + pos
;
141 sal_Int32 idx
= (*pp
)->nEnd
+ 1;
142 while( ++pos
< m_nBlock
)
144 BlockInfo
* p
= *++pp
;
151 /** Create and insert new block
153 Existing blocks will be moved rearward.
155 @param pos Position at which the new block should be created.
157 BlockInfo
* BigPtrArray::InsBlock( sal_uInt16 pos
)
159 if( m_nBlock
== m_nMaxBlock
)
161 // than extend the array first
162 BlockInfo
** ppNew
= new BlockInfo
* [ m_nMaxBlock
+ nBlockGrowSize
];
163 memcpy( ppNew
, m_ppInf
.get(), m_nMaxBlock
* sizeof( BlockInfo
* ));
164 m_nMaxBlock
+= nBlockGrowSize
;
165 m_ppInf
.reset( ppNew
);
167 if( pos
!= m_nBlock
)
169 memmove( m_ppInf
.get() + pos
+1, m_ppInf
.get() + pos
,
170 ( m_nBlock
- pos
) * sizeof( BlockInfo
* ));
173 BlockInfo
* p
= new BlockInfo
;
177 p
->nStart
= p
->nEnd
= m_ppInf
[ pos
-1 ]->nEnd
+ 1;
179 p
->nStart
= p
->nEnd
= 0;
181 p
->nEnd
--; // no elements
187 void BigPtrArray::BlockDel( sal_uInt16 nDel
)
189 m_nBlock
= m_nBlock
- nDel
;
190 if( m_nMaxBlock
- m_nBlock
> nBlockGrowSize
)
193 nDel
= (( m_nBlock
/ nBlockGrowSize
) + 1 ) * nBlockGrowSize
;
194 BlockInfo
** ppNew
= new BlockInfo
* [ nDel
];
195 memcpy( ppNew
, m_ppInf
.get(), m_nBlock
* sizeof( BlockInfo
* ));
196 m_ppInf
.reset( ppNew
);
201 void BigPtrArray::Insert( BigPtrEntry
* pElem
, sal_Int32 pos
)
203 CHECKIDX( m_ppInf
.get(), m_nBlock
, m_nSize
, m_nCur
);
209 // special case: insert first element
213 else if( pos
== m_nSize
)
215 // special case: insert at end
218 if( p
->nElem
== MAXENTRY
)
219 // the last block is full, create a new one
220 p
= InsBlock( ++cur
);
225 cur
= Index2Block( pos
);
229 if( p
->nElem
== MAXENTRY
)
231 // does the last entry fit into the next block?
233 if( cur
< ( m_nBlock
- 1 ) && m_ppInf
[ cur
+1 ]->nElem
< MAXENTRY
)
235 q
= m_ppInf
[ cur
+1 ];
238 int nCount
= q
->nElem
;
239 auto pFrom
= q
->mvData
.begin() + nCount
;
240 auto pTo
= pFrom
+ 1;
244 ++((*pTo
)->m_nOffset
);
252 // If it does not fit, then insert a new block. But if there is more
253 // than 50% space in the array then compress first.
254 if( /*nBlock == nMaxBlock &&*/
255 m_nBlock
> ( m_nSize
/ ( MAXENTRY
/ 2 ) ) &&
258 // Something was moved before the current position and all
259 // pointer might be invalid. Thus restart Insert.
260 Insert( pElem
, pos
);
264 q
= InsBlock( cur
+1 );
267 // entry does not fit anymore - clear space
268 BigPtrEntry
* pLast
= p
->mvData
[ MAXENTRY
-1 ];
269 pLast
->m_nOffset
= 0;
272 q
->mvData
[ 0 ] = pLast
;
279 // now we have free space - insert
281 assert(pos
< MAXENTRY
);
282 if( pos
!= p
->nElem
)
284 int nCount
= p
->nElem
- sal_uInt16(pos
);
285 auto pFrom
= p
->mvData
.begin() + p
->nElem
;
286 auto pTo
= pFrom
+ 1;
290 ++( *pTo
)->m_nOffset
;
293 // insert element and update indices
294 pElem
->m_nOffset
= sal_uInt16(pos
);
296 p
->mvData
[ pos
] = pElem
;
300 if( cur
!= ( m_nBlock
- 1 ) ) UpdIndex( cur
);
303 CHECKIDX( m_ppInf
.get(), m_nBlock
, m_nSize
, m_nCur
);
306 void BigPtrArray::Remove( sal_Int32 pos
, sal_Int32 n
)
308 ImplRemove(pos
, n
, true);
311 void BigPtrArray::ImplRemove( sal_Int32 pos
, sal_Int32 n
, bool bClearElement
)
313 CHECKIDX( m_ppInf
.get(), m_nBlock
, m_nSize
, m_nCur
);
315 sal_uInt16 nBlkdel
= 0; // deleted blocks
316 sal_uInt16 cur
= Index2Block( pos
); // current block number
317 sal_uInt16 nBlk1
= cur
; // 1st treated block
318 sal_uInt16 nBlk1del
= USHRT_MAX
; // 1st deleted block
319 BlockInfo
* p
= m_ppInf
[ cur
];
325 sal_uInt16 nel
= p
->nElem
- sal_uInt16(pos
);
326 if( sal_Int32(nel
) > nElem
)
327 nel
= sal_uInt16(nElem
);
328 // clear the back-pointers from the node back to node array. helps to flush out stale accesses.
330 for(sal_uInt16 i
=0; i
< nel
; ++i
)
332 p
->mvData
[pos
+i
]->m_pBlock
= nullptr;
333 p
->mvData
[pos
+i
]->m_nOffset
= 0;
335 // move elements if needed
336 if( ( pos
+ nel
) < sal_Int32(p
->nElem
) )
338 auto pTo
= p
->mvData
.begin() + pos
;
339 auto pFrom
= pTo
+ nel
;
340 int nCount
= p
->nElem
- nel
- sal_uInt16(pos
);
344 (*pTo
)->m_nOffset
= (*pTo
)->m_nOffset
- nel
;
349 p
->nElem
= p
->nElem
- nel
;
350 // possibly delete block completely
354 if( USHRT_MAX
== nBlk1del
)
360 p
= m_ppInf
[ ++cur
];
364 // update table if blocks were removed
367 for( sal_uInt16 i
= nBlk1del
; i
< ( nBlk1del
+ nBlkdel
); i
++ )
370 if( ( nBlk1del
+ nBlkdel
) < m_nBlock
)
372 memmove( m_ppInf
.get() + nBlk1del
, m_ppInf
.get() + nBlk1del
+ nBlkdel
,
373 ( m_nBlock
- nBlkdel
- nBlk1del
) * sizeof( BlockInfo
* ) );
375 // UpdateIdx updates the successor thus start before first elem
380 p
->nEnd
= p
->nElem
-1;
387 BlockDel( nBlkdel
); // blocks were deleted
391 if( nBlk1
!= ( m_nBlock
- 1 ) && m_nSize
)
395 // call Compress() if there is more than 50% space in the array
396 if( m_nBlock
> ( m_nSize
/ ( MAXENTRY
/ 2 ) ) )
399 CHECKIDX( m_ppInf
.get(), m_nBlock
, m_nSize
, m_nCur
);
402 void BigPtrArray::Replace( sal_Int32 idx
, BigPtrEntry
* pElem
)
404 ImplReplace(idx
, pElem
, /*bClearElement*/ true);
407 void BigPtrArray::ImplReplace( sal_Int32 idx
, BigPtrEntry
* pElem
, bool bClearElement
)
409 assert(idx
< m_nSize
); // Index out of bounds
410 m_nCur
= Index2Block( idx
);
411 BlockInfo
* p
= m_ppInf
[ m_nCur
];
412 pElem
->m_nOffset
= sal_uInt16(idx
- p
->nStart
);
415 // clear the back-pointers from the old element back to element array. helps to flush out stale accesses.
418 p
->mvData
[idx
- p
->nStart
]->m_pBlock
= nullptr;
419 p
->mvData
[idx
- p
->nStart
]->m_nOffset
= 0;
422 // update with new element
423 p
->mvData
[ idx
- p
->nStart
] = pElem
;
426 /** Speed up the complicated removal logic in SwNodes::RemoveNode.
427 Replaces the node AFTER pNotTheOne.
428 Returns the entry BEFORE pNotTheOne.
430 BigPtrEntry
* BigPtrArray::ReplaceTheOneAfter( BigPtrEntry
* pNotTheOne
, BigPtrEntry
* pNewEntry
)
432 assert(pNotTheOne
->m_pBlock
->pBigArr
== this);
433 BlockInfo
* p
= pNotTheOne
->m_pBlock
;
434 sal_uInt16 nOffset
= pNotTheOne
->m_nOffset
;
436 // if the next node is inside the current block
437 if (nOffset
< p
->nElem
- 1)
440 p
->mvData
[nOffset
] = pNewEntry
;
441 pNewEntry
->m_nOffset
= nOffset
;
442 pNewEntry
->m_pBlock
= p
;
448 BigPtrArray::ImplReplace( pNotTheOne
->GetPos()+1, pNewEntry
, /*bClearElement*/false );
451 // if the previous node is inside the current block
455 return p
->mvData
[nOffset
];
460 sal_Int32 nPrevPos
= pNotTheOne
->GetPos();
463 return BigPtrArray::operator[]( nPrevPos
- 1 );
467 /** Compress the array */
468 sal_uInt16
BigPtrArray::Compress()
470 CHECKIDX( m_ppInf
.get(), m_nBlock
, m_nSize
, m_nCur
);
472 // Iterate over InfoBlock array from beginning to end. If there is a deleted
473 // block in between so move all following ones accordingly. The pointer <pp>
474 // represents the "old" and <qq> the "new" array.
475 BlockInfo
** pp
= m_ppInf
.get(), **qq
= pp
;
477 BlockInfo
* pLast(nullptr); // last empty block
478 sal_uInt16 nLast
= 0; // missing elements
479 sal_uInt16 nBlkdel
= 0; // number of deleted blocks
480 sal_uInt16 nFirstChgPos
= USHRT_MAX
; // at which position was the 1st change?
482 // convert fill percentage into number of remaining elements
483 short const nMax
= MAXENTRY
- tools::Long(MAXENTRY
) * COMPRESSLVL
/ 100;
485 for( sal_uInt16 cur
= 0; cur
< m_nBlock
; ++cur
)
488 sal_uInt16 n
= p
->nElem
;
489 // Check if a not completely full block will be ignored. This happens if
490 // the current block would have to be split but the filling of the
491 // inspected block is already over its threshold value. In this case we
492 // do not fill more (it's expensive because of a double memmove() call)
493 if( nLast
&& ( n
> nLast
) && ( nLast
< nMax
) )
497 if( USHRT_MAX
== nFirstChgPos
)
500 // Not full yet? Then fill up.
504 // move elements from current to last block
505 auto pElem
= pLast
->mvData
.begin() + pLast
->nElem
;
506 auto pFrom
= p
->mvData
.begin();
507 for( sal_uInt16 nCount
= n
, nOff
= pLast
->nElem
;
508 nCount
; --nCount
, ++pElem
)
511 (*pElem
)->m_pBlock
= pLast
;
512 (*pElem
)->m_nOffset
= nOff
++;
516 pLast
->nElem
= pLast
->nElem
+ n
;
518 p
->nElem
= p
->nElem
- n
;
520 // Is the current block now empty as a result?
530 pElem
= p
->mvData
.begin();
532 int nCount
= p
->nElem
;
536 (*pElem
)->m_nOffset
= (*pElem
)->m_nOffset
- n
;
542 if( p
) // BlockInfo was not deleted
544 *qq
++ = p
; // adjust to correct position
546 // keep the potentially existing last half-full block
547 if( !nLast
&& p
->nElem
< MAXENTRY
)
550 nLast
= MAXENTRY
- p
->nElem
;
555 // if blocks were deleted shrink BlockInfo array if needed
561 p
->nEnd
= p
->nElem
- 1;
564 if( m_nCur
>= nFirstChgPos
)
567 CHECKIDX( m_ppInf
.get(), m_nBlock
, m_nSize
, m_nCur
);
572 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */