/*
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License, version 2.1 as published by the Free Software
* Foundation.
*
* You should have received a copy of the GNU Lesser General Public License along with this
* program; if not, you can obtain a copy at http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html
* or from the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
* 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 Lesser General Public License for more details.
*
* Copyright (c) 2001 - 2013 Object Refinery Ltd, Pentaho Corporation and Contributors.. All rights reserved.
*/
package org.pentaho.reporting.engine.classic.core.modules.output.table.base;
import java.util.Arrays;
public class TableCutList implements Cloneable {
private static final Boolean[] EMPTY_ENTRIES = new Boolean[0];
private static final long[] EMPTY_KEYS = new long[0];
private static final int LIN_VS_BIN = 16;
private Boolean[] entries;
private long[] keys;
private int size;
private int increment;
private boolean enableQuickLookup;
private long scaleFactor;
public TableCutList( final int increment, final boolean enableQuickLookup ) {
if ( increment < 1 ) {
throw new IllegalArgumentException();
}
this.increment = increment;
entries = TableCutList.EMPTY_ENTRIES;
keys = TableCutList.EMPTY_KEYS;
this.enableQuickLookup = enableQuickLookup;
}
public TableCutList clone() {
try {
TableCutList clone = (TableCutList) super.clone();
clone.entries = clone.entries.clone();
return clone;
} catch ( CloneNotSupportedException e ) {
throw new IllegalStateException();
}
}
public void clear() {
this.size = 0;
this.scaleFactor = 0;
}
public boolean isEnableQuickLookup() {
return enableQuickLookup;
}
public void setEnableQuickLookup( final boolean enableQuickLookup ) {
this.enableQuickLookup = enableQuickLookup;
}
public int size() {
return size;
}
/**
* Ensures, that the list backend can store at least <code>c</code> elements. This method does nothing, if the new
* capacity is less than the current capacity.
*
* @param c
* the new capacity of the list.
*/
private void ensureCapacity( final int c ) {
if ( keys.length <= c ) {
final int newIncrement = Math.min( 25000, Math.max( keys.length / 2, increment ) );
final long[] newKeys = new long[Math.max( keys.length + newIncrement, c + 1 )];
System.arraycopy( keys, 0, newKeys, 0, size );
keys = newKeys;
final Boolean[] newCuts = new Boolean[Math.max( entries.length + newIncrement, c + 1 )];
System.arraycopy( entries, 0, newCuts, 0, size );
entries = newCuts;
}
}
public static long bin;
public static long lin;
public boolean put( final long key, final Boolean entry ) {
if ( entry == null ) {
throw new NullPointerException();
}
// accessLogWriter.println("put - " + System.currentTimeMillis() + " - " + key);
if ( size > 0 ) {
// try a short-cut, which is usefull for y-coordinates (which are almost always sorted).
if ( key > keys[size - 1] ) {
ensureCapacity( size + 1 );
keys[size] = key;
entries[size] = entry;
size += 1;
scaleFactor = ( key - keys[0] ) / size;
return true;
}
}
int start = 0;
int end = size;
if ( enableQuickLookup && size > 0 && scaleFactor != 0 ) {
if ( key < keys[0] ) {
end = 1;
} else {
// assume a relatively uniform layout, all rows have roughly the same size ..
// this means, we can guess-jump close to the target-position ..
final int maxIdx = size - 1;
final long lastVal = keys[maxIdx];
if ( lastVal > 0 ) {
final int targetIdx = (int) ( ( key - keys[0] ) / scaleFactor );
final int minTgtIdx = Math.max( 0, Math.min( maxIdx, targetIdx - 7 ) );
final int maxTgtIdx = Math.min( maxIdx, targetIdx + 7 );
final long minKey = keys[minTgtIdx];
final long maxKey = keys[maxTgtIdx];
final boolean minLessPos = key >= minKey;
final boolean maxMorePos = key <= maxKey;
if ( minLessPos ) {
start = minTgtIdx;
}
if ( maxMorePos ) {
end = maxTgtIdx + 1;
}
}
}
}
// ok, check, whether this is a new key ..
final int position;
if ( ( end - start ) < LIN_VS_BIN ) {
lin += 1;
position = linearSearch( keys, key, start, end );
} else {
bin += 1;
position = binarySearch( keys, key, start, end );
}
if ( position >= 0 ) {
final Boolean entryFromList = entries[position];
if ( entryFromList == null ) {
throw new IllegalStateException( "Must not happen" );
}
if ( Boolean.TRUE.equals( entryFromList ) ) {
entries[position] = entry;
}
return false;
}
ensureCapacity( size + 1 );
final int insertPoint = -( position + 1 );
if ( insertPoint < size ) {
// shift the contents ..
System.arraycopy( keys, insertPoint, keys, insertPoint + 1, size - insertPoint );
System.arraycopy( entries, insertPoint, entries, insertPoint + 1, size - insertPoint );
}
keys[insertPoint] = key;
entries[insertPoint] = entry;
size += 1;
if ( insertPoint == ( size - 1 ) ) {
// we modified the last entry, so update the lookup-scale-factor ..
scaleFactor = ( key - keys[0] ) / size;
} else {
// we modified a inner entry, so update the lookup-scale-factor ..
scaleFactor = ( keys[size - 1] - keys[0] ) / size;
}
return true;
}
/**
* Performs a binary-search, but includes some optimizations in case we search for the same key all the time.
*
* @param pos
* the starting position of the box.
* @return the position as positive integer or a negative integer indicating the insert-point.
*/
private int findKeyInternal( final long pos ) {
return findKeyInternal( pos, -1 );
}
private int findKeyInternal( final long pos, final int lastFoundPos ) {
int start = 0;
int end = size;
if ( lastFoundPos != -1 && lastFoundPos < size ) {
if ( keys[lastFoundPos] == pos ) {
return lastFoundPos;
}
}
if ( enableQuickLookup && size > 0 && scaleFactor != 0 ) {
if ( pos < keys[0] ) {
return -1;
}
// assume a relatively uniform layout, all rows have roughly the same size ..
// this means, we can guess-jump close to the target-position ..
final int maxIdx = size - 1;
final long lastVal = keys[maxIdx];
if ( lastVal > 0 ) {
final int targetIdx = (int) ( ( pos - keys[0] ) / scaleFactor );
final int minTgtIdx = Math.max( 0, Math.min( maxIdx, targetIdx - 7 ) );
final int maxTgtIdx = Math.min( maxIdx, targetIdx + 7 );
final long minKey = keys[minTgtIdx];
final long maxKey = keys[maxTgtIdx];
final boolean minLessPos = pos >= minKey;
final boolean maxMorePos = pos <= maxKey;
if ( minLessPos ) {
start = minTgtIdx;
}
if ( maxMorePos ) {
end = maxTgtIdx + 1;
}
}
}
final int i;
if ( end - start < LIN_VS_BIN ) {
lin += 1;
i = linearSearch( keys, pos, start, end );
} else {
bin += 1;
i = binarySearch( keys, pos, start, end );
}
if ( i > -1 ) {
return i;
}
if ( i == -1 ) {
return -1;
}
return i;
}
public boolean remove( final long key ) {
final int position = findKeyInternal( key );
// binarySearch(keys, key, 0, size);
if ( position < 0 ) {
return false;
}
final int shiftElements = size - position - 1;
if ( shiftElements == 0 ) {
keys[position] = 0;
entries[position] = null;
size -= 1;
if ( size == 0 ) {
scaleFactor = 0;
} else {
scaleFactor = ( keys[size - 1] - keys[0] ) / size;
}
return true;
}
size -= 1;
System.arraycopy( keys, position + 1, keys, position, shiftElements );
System.arraycopy( entries, position + 1, entries, position, shiftElements );
keys[size] = 0;
entries[size] = null;
if ( size == 0 ) {
scaleFactor = 0;
} else {
scaleFactor = ( keys[size - 1] - keys[0] ) / size;
}
return true;
}
public Boolean get( final long key ) {
if ( size == 0 ) {
return null;
}
if ( key > keys[size - 1] ) {
return null;
}
final int position = findKeyInternal( key );
// binarySearch(keys, key, 0, size);
if ( position < 0 ) {
return null;
}
return entries[position];
}
public Boolean getPrevious( final long key ) {
if ( size == 0 ) {
return null;
}
if ( key > keys[size - 1] ) {
return entries[size - 1];
}
final int position = findKeyInternal( key );
// binarySearch(keys, key, 0, size);
if ( position == 0 ) {
return null;
}
if ( position > 0 ) {
return entries[position - 1];
}
final int insertPoint = -( position + 2 );
return entries[insertPoint];
}
public boolean containsKey( final long key ) {
if ( size > 0 ) {
// try a short-cut, which is usefull for y-coordinates (which are almost always sorted).
if ( key > keys[size - 1] ) {
return false;
}
}
return findKeyInternal( key ) >= 0;
}
private static int linearSearch( final long[] array, final long key, final int start, final int end ) {
for ( int i = start; i < end; i++ ) {
final long value = array[i];
if ( value == key ) {
return i;
}
if ( key < value ) {
return -( i + 1 );
}
}
return -( end + 1 );
}
private static int binarySearch( final long[] array, final long key, final int start, final int end ) {
// int itCount = 0;
int low = start;
int high = end - 1;
while ( low <= high ) {
// itCount += 1;
final int mid = ( low + high ) >>> 1;
final long midVal = array[mid];
if ( midVal < key ) {
low = mid + 1;
} else if ( midVal > key ) {
high = mid - 1;
} else {
return mid; // key found
}
}
return -( low + 1 ); // key not found.
}
public Boolean[] getRawEntries() {
return entries;
}
/**
* Copys the list contents into a new array.
*
* @return the list contents as array.
* @deprecated Always provide a buffer for performance reasons.
*/
public long[] getKeys() {
if ( size == 0 ) {
return TableCutList.EMPTY_KEYS;
}
if ( size == keys.length ) {
return (long[]) keys.clone();
}
final long[] retval = new long[size];
System.arraycopy( keys, 0, retval, 0, size );
return retval;
}
public long[] getKeys( long[] retval ) {
if ( retval == null || retval.length < size ) {
retval = new long[size];
}
System.arraycopy( keys, 0, retval, 0, size );
return retval;
}
/**
* Tries to locate the key that matches the given key-parameter as closely as possible. If greater is set to true,
* then - if the coordinate is not contained in the list - the next coordinate is given, else the previous one is
* returned.
*
* @param coordinate
* @param greater
* @return
*/
public int findKeyPosition( final long coordinate, final boolean greater ) {
return findKeyPosition( coordinate, greater, -1 );
}
public int findKeyPosition( final long coordinate, final boolean greater, final int lastFoundPos ) {
final int pos = findKeyInternal( coordinate, lastFoundPos );
if ( pos == size ) {
// warning: This might be stupid
return size - 1;
}
if ( pos >= 0 ) {
return pos;
}
// the coordinate is greater than the largest key in this list ..
if ( pos == -( size + 1 ) ) {
return size - 1;
}
// the coordinate is not a key, but smaller than the largest key in this list..
if ( greater ) {
return ( -pos - 1 );
} else {
return ( -pos - 2 );
}
}
public long getKeyAt( final int indexPosition ) {
if ( indexPosition >= size || indexPosition < 0 ) {
throw new IndexOutOfBoundsException();
}
return keys[indexPosition];
}
public Boolean getValueAt( final int indexPosition ) {
if ( indexPosition >= size || indexPosition < 0 ) {
throw new IndexOutOfBoundsException();
}
return entries[indexPosition];
}
public long findKey( final long key, final boolean upperBounds ) {
final int pos = findKeyPosition( key, upperBounds );
return keys[pos];
}
/**
* Expects a sorted (ascending) list of cut-entries that should be removed. You will run into troubles if the list is
* not sorted.
*
* @param cutArray
*/
public void removeAll( final long[] cutArray, final long cutSize ) {
if ( cutSize == 0 ) {
return;
}
int cutIndex = 0;
long currentCut = cutArray[0];
int targetPosition = 0;
int sourcePosition = 0;
for ( ; sourcePosition < size; sourcePosition++ ) {
final long key = keys[sourcePosition];
if ( key == currentCut ) {
// do nothing ..
cutIndex += 1;
if ( cutIndex == cutSize ) {
System.arraycopy( keys, sourcePosition + 1, keys, targetPosition, size - sourcePosition - 1 );
System.arraycopy( entries, sourcePosition + 1, entries, targetPosition, size - sourcePosition - 1 );
targetPosition = size - cutIndex;
break;
}
currentCut = cutArray[cutIndex];
} else {
keys[targetPosition] = key;
entries[targetPosition] = entries[sourcePosition];
targetPosition += 1;
}
}
Arrays.fill( keys, targetPosition, size, 0 );
Arrays.fill( entries, targetPosition, size, null );
size = targetPosition;
if ( size != 0 ) {
scaleFactor = ( keys[size - 1] - keys[0] ) / size;
} else {
scaleFactor = 0;
}
}
}