blob: ef0e69368163eb9d76df377ac9861dbad319dfe9 [file] [log] [blame]
/**************************************************************
*
* 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
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*
*************************************************************/
// MARKER(update_precomp.py): autogen include statement, do not remove
#include "precompiled_sw.hxx"
#include <string.h>
#include <limits.h>
#include "bparr.hxx"
// die Blockverwaltung waechst/schrumpft immer um 20 Bloecke, das sind dann
// immer ~ 20 * MAXENTRY == 20000 Eintraege
const sal_uInt16 nBlockGrowSize = 20;
#ifndef DBG_UTIL
#define CHECKIDX( p, n, i, c )
#else
#define CHECKIDX( p, n, i, c ) CheckIdx( p, n, i, c );
void CheckIdx( BlockInfo** ppInf, sal_uInt16 nBlock, sal_uLong nSize, sal_uInt16 nCur )
{
DBG_ASSERT( !nSize || nCur < nBlock, "BigPtrArray: CurIndex steht falsch" );
sal_uLong nIdx = 0;
for( sal_uInt16 nCnt = 0; nCnt < nBlock; ++nCnt, ++ppInf )
{
nIdx += (*ppInf)->nElem;
// Array mit Luecken darf es nicht geben
DBG_ASSERT( !nCnt || (*(ppInf-1))->nEnd + 1 == (*ppInf)->nStart,
"BigPtrArray: Luecke in der Verwaltung!" );
}
DBG_ASSERT( nIdx == nSize, "BigPtrArray: Anzahl ungueltig" );
}
#endif
BigPtrArray::BigPtrArray()
{
nBlock = nCur = 0;
nSize = 0;
nMaxBlock = nBlockGrowSize; // == 20 * 1000 Eintraege
ppInf = new BlockInfo* [ nMaxBlock ];
}
BigPtrArray::~BigPtrArray()
{
if( nBlock )
{
BlockInfo** pp = ppInf;
for( sal_uInt16 n = 0; n < nBlock; ++n, ++pp )
{
delete[] (*pp)->pData;
delete *pp;
}
}
delete[] ppInf;
}
// Auch der Move ist schlicht. Optimieren ist hier wg. der
// Stueckelung des Feldes zwecklos!
void BigPtrArray::Move( sal_uLong from, sal_uLong to )
{
sal_uInt16 cur = Index2Block( from );
BlockInfo* p = ppInf[ cur ];
ElementPtr pElem = p->pData[ from - p->nStart ];
Insert( pElem, to ); // erst einfuegen, dann loeschen !!!!
Remove( ( to < from ) ? ( from + 1 ) : from );
}
// das Ende ist EXCLUSIV
void BigPtrArray::ForEach( sal_uLong nStart, sal_uLong nEnd,
FnForEach fn, void* pArgs )
{
if( nEnd > nSize )
nEnd = nSize;
if( nStart < nEnd )
{
sal_uInt16 cur = Index2Block( nStart );
BlockInfo** pp = ppInf + cur;
BlockInfo* p = *pp;
sal_uInt16 nElem = sal_uInt16( nStart - p->nStart );
ElementPtr* pElem = p->pData + nElem;
nElem = p->nElem - nElem;
for(;;)
{
if( !(*fn)( *pElem++, pArgs ) || ++nStart >= nEnd )
break;
// naechstes Element
if( !--nElem )
{
// neuer Block
p = *++pp;
pElem = p->pData;
nElem = p->nElem;
}
}
}
}
ElementPtr BigPtrArray::operator[]( sal_uLong idx ) const
{
// weil die Funktion eben doch nicht const ist:
DBG_ASSERT( idx < nSize, "operator[]: Index aussserhalb" );
BigPtrArray* pThis = (BigPtrArray*) this;
sal_uInt16 cur = Index2Block( idx );
BlockInfo* p = ppInf[ cur ];
pThis->nCur = cur;
return p->pData[ idx - p->nStart ];
}
///////////////////////////////////////////////////////////////////////////
// private Methoden
// Suchen des Blocks einer bestimmten Position
// Algorithmus:
// 1. Test, ob der letzte Block der gesuchte Block ist
// 2. Sonderfall: Index = 0?
// 3. Test der Nachbarbloecke
// 4. Binaere Suche
sal_uInt16 BigPtrArray::Index2Block( sal_uLong pos ) const
{
// zuletzt verwendeter Block?
BlockInfo* p = ppInf[ nCur ];
if( p->nStart <= pos && p->nEnd >= pos )
return nCur;
// Index = 0?
if( !pos )
return 0;
// Folgeblock?
if( nCur < ( nBlock - 1 ) )
{
p = ppInf[ nCur+1 ];
if( p->nStart <= pos && p->nEnd >= pos )
return nCur+1;
}
// vorangehender Block?
else if( pos < p->nStart && nCur > 0 )
{
p = ppInf[ nCur-1 ];
if( p->nStart <= pos && p->nEnd >= pos )
return nCur-1;
}
// Binaere Suche:
// Diese fuehrt immer zum Erfolg
sal_uInt16 lower = 0, upper = nBlock - 1;
sal_uInt16 cur = 0;
for(;;)
{
sal_uInt16 n = lower + ( upper - lower ) / 2;
cur = ( n == cur ) ? n+1 : n;
p = ppInf[ cur ];
if( p->nStart <= pos && p->nEnd >= pos )
return cur;
if( p->nStart > pos )
upper = cur;
else
lower = cur;
}
}
// Update aller Indexbereiche ab einer bestimmten Position
// pos bezeichnet den letzten korrekten Block
void BigPtrArray::UpdIndex( sal_uInt16 pos )
{
BlockInfo** pp = ppInf + pos;
sal_uLong idx = (*pp)->nEnd + 1;
BlockInfo* p;
while( ++pos < nBlock )
{
p = *++pp;
p->nStart = idx;
idx += p->nElem;
p->nEnd = idx - 1;
}
}
// Einrichten eines neuen Blocks an einer bestimmten Position
// Vorhandene Blocks werden nach hinten verschoben
BlockInfo* BigPtrArray::InsBlock( sal_uInt16 pos )
{
if( nBlock == nMaxBlock )
{
// dann sollte wir mal das Array erweitern
BlockInfo** ppNew = new BlockInfo* [ nMaxBlock + nBlockGrowSize ];
memcpy( ppNew, ppInf, nMaxBlock * sizeof( BlockInfo* ));
delete[] ppInf;
nMaxBlock += nBlockGrowSize;
ppInf = ppNew;
}
if( pos != nBlock )
memmove( ppInf + pos+1, ppInf + pos ,
( nBlock - pos ) * sizeof (BlockInfo*) );
++nBlock;
BlockInfo* p = new BlockInfo;
ppInf[ pos ] = p;
if( pos )
p->nStart = p->nEnd = ppInf[ pos-1 ]->nEnd + 1;
else
p->nStart = p->nEnd = 0;
p->nEnd--; // keine Elemente
p->nElem = 0;
p->pData = new ElementPtr [ MAXENTRY ];
p->pBigArr = this;
return p;
}
void BigPtrArray::BlockDel( sal_uInt16 nDel )
{
nBlock = nBlock - nDel;
if( nMaxBlock - nBlock > nBlockGrowSize )
{
// dann koennen wir wieder schrumpfen
nDel = (( nBlock / nBlockGrowSize ) + 1 ) * nBlockGrowSize;
BlockInfo** ppNew = new BlockInfo* [ nDel ];
memcpy( ppNew, ppInf, nBlock * sizeof( BlockInfo* ));
delete[] ppInf;
ppInf = ppNew;
nMaxBlock = nDel;
}
}
void BigPtrArray::Insert( const ElementPtr& rElem, sal_uLong pos )
{
CHECKIDX( ppInf, nBlock, nSize, nCur );
BlockInfo* p;
sal_uInt16 cur;
if( !nSize )
// Sonderfall: erstes Element einfuegen
p = InsBlock( cur = 0 );
else if( pos == nSize )
{
// Sonderfall: Einfuegen am Ende
cur = nBlock - 1;
p = ppInf[ cur ];
if( p->nElem == MAXENTRY )
// Der letzte Block ist voll, neuen anlegen
p = InsBlock( ++cur );
}
else
{
// Standardfall:
cur = Index2Block( pos );
p = ppInf[ cur ];
}
if( p->nElem == MAXENTRY )
{
// passt der letzte Eintrag in den naechsten Block?
BlockInfo* q;
if( cur < ( nBlock - 1 ) && ppInf[ cur+1 ]->nElem < MAXENTRY )
{
q = ppInf[ cur+1 ];
if( q->nElem )
{
int nCount = q->nElem;
ElementPtr *pFrom = q->pData + nCount,
*pTo = pFrom+1;
while( nCount-- )
++( *--pTo = *--pFrom )->nOffset;
}
q->nStart--;
q->nEnd--;
}
else
{
// Wenn er auch nicht in den Folgeblock passt, muss ein
// neuer Block eingefuegt werden
// erst mal bei Bedarf komprimieren
// wenn mehr als 50% "Luft" im Array ist, dann sollte man mal das
// Compress aufrufen
if( /*nBlock == nMaxBlock &&*/
nBlock > ( nSize / ( MAXENTRY / 2 ) ) &&
cur >= Compress() )
{
// es wurde vor der akt. Pos etwas verschoben und alle
// Pointer koennen ungueltig sein. Also das Insert
// nochmals aufsetzen
Insert( rElem, pos );
return ;
}
q = InsBlock( cur+1 );
}
// Eintrag passt nicht mehr. Dann muss Platz gemacht werden
ElementPtr pLast = p->pData[ MAXENTRY-1 ];
pLast->nOffset = 0;
pLast->pBlock = q;
q->pData[ 0 ] = pLast;
q->nElem++;
q->nEnd++;
p->nEnd--;
p->nElem--;
}
// Nun haben wir einen freien Block am Wickel: eintragen
pos -= p->nStart;
DBG_ASSERT( pos < MAXENTRY, "falsche Pos" );
if( pos != p->nElem )
{
int nCount = p->nElem - sal_uInt16(pos);
ElementPtr *pFrom = p->pData + p->nElem,
*pTo = pFrom + 1;
while( nCount-- )
++( *--pTo = *--pFrom )->nOffset;
}
// Element eintragen und Indexe updaten
((ElementPtr&)rElem)->nOffset = sal_uInt16(pos);
((ElementPtr&)rElem)->pBlock = p;
p->pData[ pos ] = rElem;
p->nEnd++;
p->nElem++;
nSize++;
if( cur != ( nBlock - 1 ) ) UpdIndex( cur );
nCur = cur;
CHECKIDX( ppInf, nBlock, nSize, nCur );
}
void BigPtrArray::Remove( sal_uLong pos, sal_uLong n )
{
CHECKIDX( ppInf, nBlock, nSize, nCur );
sal_uInt16 nBlkdel = 0; // entfernte Bloecke
sal_uInt16 cur = Index2Block( pos ); // aktuelle Blocknr
sal_uInt16 nBlk1 = cur; // 1. behandelter Block
sal_uInt16 nBlk1del = USHRT_MAX; // 1. entfernter Block
BlockInfo* p = 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);
// Eventuell Elemente verschieben
if( ( pos + nel ) < sal_uLong(p->nElem) )
{
ElementPtr *pTo = p->pData + pos,
*pFrom = pTo + nel;
int nCount = p->nElem - nel - sal_uInt16(pos);
while( nCount-- )
{
*pTo = *pFrom++;
(*pTo)->nOffset = (*pTo)->nOffset - nel;
++pTo;
}
}
p->nEnd -= nel;
p->nElem = p->nElem - nel;
if( !p->nElem )
{
// eventuell Block ganz entfernen
delete[] p->pData;
nBlkdel++;
if( USHRT_MAX == nBlk1del )
nBlk1del = cur;
}
nElem -= nel;
if( !nElem )
break;
p = ppInf[ ++cur ];
pos = 0;
}
// Am Ende die Tabelle updaten, falls Bloecke geloescht waren
if( nBlkdel )
{
// loeschen sollte man immer !!
for( sal_uInt16 i = nBlk1del; i < ( nBlk1del + nBlkdel ); i++ )
delete ppInf[ i ];
if( ( nBlk1del + nBlkdel ) < nBlock )
{
memmove( ppInf + nBlk1del, ppInf + nBlk1del + nBlkdel,
( nBlock - nBlkdel - nBlk1del ) * sizeof( BlockInfo* ) );
// JP 19.07.95: nicht den ersten behandelten, sondern den davor!!
// UpdateIdx updatet nur alle Nachfolgende!!
if( !nBlk1 )
{
p = ppInf[ 0 ];
p->nStart = 0;
p->nEnd = p->nElem-1;
}
else
--nBlk1;
}
BlockDel( nBlkdel ); // es wurden Bloecke geloescht
}
nSize -= n;
if( nBlk1 != ( nBlock - 1 ) && nSize )
UpdIndex( nBlk1 );
nCur = nBlk1;
// wenn mehr als 50% "Luft" im Array ist, dann sollte man mal das
// Compress aufrufen
if( nBlock > ( nSize / ( MAXENTRY / 2 ) ) )
Compress();
CHECKIDX( ppInf, nBlock, nSize, nCur );
}
void BigPtrArray::Replace( sal_uLong idx, const ElementPtr& rElem)
{
// weil die Funktion eben doch nicht const ist:
DBG_ASSERT( idx < nSize, "Set: Index aussserhalb" );
BigPtrArray* pThis = (BigPtrArray*) this;
sal_uInt16 cur = Index2Block( idx );
BlockInfo* p = ppInf[ cur ];
pThis->nCur = cur;
((ElementPtr&)rElem)->nOffset = sal_uInt16(idx - p->nStart);
((ElementPtr&)rElem)->pBlock = p;
p->pData[ idx - p->nStart ] = rElem;
}
// Array komprimieren
sal_uInt16 BigPtrArray::Compress( short nMax )
{
CHECKIDX( ppInf, nBlock, nSize, nCur );
// Es wird von vorne nach hinten ueber das InfoBlock Array iteriert.
// Wenn zwischen durch Block gel�scht werden, dann mussen alle
// nachfolgenden verschoben werden. Dazu werden die Pointer pp und qq
// benutzt; wobei pp das "alte" Array, qq das "neue" Array ist.
BlockInfo** pp = ppInf, **qq = pp;
BlockInfo* p;
BlockInfo* pLast(0); // letzter nicht voller Block
sal_uInt16 nLast = 0; // fehlende Elemente
sal_uInt16 nBlkdel = 0; // Anzahl der geloeschte Bloecke
sal_uInt16 nFirstChgPos = USHRT_MAX; // ab welcher Pos gab es die 1. Aenderung?
// von Fuell-Prozenten auf uebrige Eintrage umrechnen
nMax = MAXENTRY - (long) MAXENTRY * nMax / 100;
for( sal_uInt16 cur = 0; cur < nBlock; ++cur )
{
p = *pp++;
sal_uInt16 n = p->nElem;
// Testen, ob der noch nicht volle Block so gelassen wird
// dies ist der Fall, wenn der aktuelle Block gesplittet
// werden muesste, der noch nicht volle Block aber bereits
// ueber dem uebergebenen Break-Wert voll ist. In diesem
// Fall wird von einer weiteren Fuellung (die ja wegen dem
// zweifachen memmove() zeitaufwendig ist) abgesehen.
if( nLast && ( n > nLast ) && ( nLast < nMax ) )
nLast = 0;
if( nLast )
{
if( USHRT_MAX == nFirstChgPos )
nFirstChgPos = cur;
// ein nicht voller Block vorhanden: auffuellen
if( n > nLast )
n = nLast;
// Elemente uebertragen, vom akt. in den letzten
ElementPtr* pElem = pLast->pData + pLast->nElem;
ElementPtr* pFrom = p->pData;
for( sal_uInt16 nCount = n, nOff = pLast->nElem;
nCount; --nCount, ++pElem )
*pElem = *pFrom++,
(*pElem)->pBlock = pLast,
(*pElem)->nOffset = nOff++;
// korrigieren
pLast->nElem = pLast->nElem + n;
nLast = nLast - n;
p->nElem = p->nElem - n;
// Ist der aktuelle Block dadurch leer geworden?
if( !p->nElem )
{
// dann kann der entfernt werden
delete[] p->pData;
delete p, p = 0;
++nBlkdel;
}
else
{
pElem = p->pData, pFrom = pElem + n;
int nCount = p->nElem;
while( nCount-- )
{
*pElem = *pFrom++;
(*pElem)->nOffset = (*pElem)->nOffset - n;
++pElem;
}
}
}
if( p ) // die Blockinfo wurde nicht geloescht
{
*qq++ = p; // dann setze sie an die richtige neue Position
// eventuell den letzten halbvollen Block festhalten
if( !nLast && p->nElem < MAXENTRY )
{
pLast = p;
nLast = MAXENTRY - p->nElem;
}
}
}
// Bloecke geloescht wurden, ggfs. das BlockInfo Array verkuerzen
if( nBlkdel )
BlockDel( nBlkdel );
// Und neu durchindizieren
p = ppInf[ 0 ];
p->nEnd = p->nElem - 1;
UpdIndex( 0 );
if( nCur >= nFirstChgPos )
nCur = 0;
CHECKIDX( ppInf, nBlock, nSize, nCur );
return nFirstChgPos;
}