/*! * 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) 2002-2013 Pentaho Corporation.. All rights reserved. */ package org.pentaho.reporting.designer.core.util; import org.pentaho.reporting.engine.classic.core.util.InstanceID; import java.util.Arrays; /** * Creation-Date: 26.08.2007, 15:06:45 * * @author Thomas Morgner */ public final class BreakPositionsList { private static final long[] EMPTY_KEYS = new long[ 0 ]; private static final InstanceID[] EMPTY_OWNERS = new InstanceID[ 0 ]; private long[] keys; private InstanceID[] owner; private int size; private int increment; private boolean enableQuickLookup; private long scaleFactor; public BreakPositionsList() { this( 25, true ); } public BreakPositionsList( final int increment, final boolean enableQuickLookup ) { if ( increment < 1 ) { throw new IllegalArgumentException(); } this.increment = increment; this.keys = BreakPositionsList.EMPTY_KEYS; this.owner = EMPTY_OWNERS; this.enableQuickLookup = enableQuickLookup; } public void clear() { Arrays.fill( owner, null ); Arrays.fill( keys, 0 ); scaleFactor = 0; size = 0; } 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 long[] newKeys = new long[ Math.max( keys.length + increment, c + 1 ) ]; System.arraycopy( keys, 0, newKeys, 0, size ); keys = newKeys; final InstanceID[] newOwner = new InstanceID[ Math.max( owner.length + increment, c + 1 ) ]; System.arraycopy( owner, 0, newOwner, 0, size ); owner = newOwner; } } public InstanceID getOwner( final long key ) { final int pos = findKeyInternal( key ); if ( pos >= 0 ) { return owner[ pos ]; } return null; } public boolean add( final long key, final InstanceID owner ) { // 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; this.owner[ size ] = owner; size += 1; scaleFactor = key / size; return true; } } int start = 0; int end = size; if ( enableQuickLookup && size > 0 ) { // 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 / 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 = binarySearch( keys, key, start, end ); if ( position >= 0 ) { // not a new key, but a multi-owner thing this.owner[ position ] = null; 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( this.owner, insertPoint, this.owner, insertPoint + 1, size - insertPoint ); } keys[ insertPoint ] = key; this.owner[ insertPoint ] = owner; size += 1; if ( insertPoint == ( size - 1 ) ) { // we modified the last entry, so update the lookup-scale-factor .. scaleFactor = key / size; } else { // we modified a inner entry, so update the lookup-scale-factor .. scaleFactor = keys[ size - 1 ] / size; } return true; } //private int foundDirect; /** * 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 ) { int start = 0; int end = size; if ( enableQuickLookup && size > 0 ) { // 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 / 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 = 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; size -= 1; scaleFactor = keys[ size - 1 ] / size; return true; } size -= 1; System.arraycopy( keys, position + 1, keys, position, shiftElements ); keys[ size ] = 0; scaleFactor = keys[ size - 1 ] / size; return true; } public long getNext( final long key ) { if ( size == 0 ) { return key; } if ( key > keys[ size - 1 ] ) { // param greater than the greatest value in list return keys[ size - 1 ]; } final int position = findKeyInternal( key ); if ( position == -1 ) { // param smaller than the smallest key in list return keys[ 0 ]; } else if ( position < 0 ) { final int insertPoint = -( position + 1 ); return keys[ insertPoint ]; } return keys[ position ]; } /** * Returns the entry that is either equal or less than this key. * * @param key * @return */ public long getPrevious( final long key ) { if ( size == 0 ) { return key; } if ( key > keys[ size - 1 ] ) { return keys[ size - 1 ]; } final int position = findKeyInternal( key ); if ( position >= 0 ) { return keys[ position ]; } if ( position == -1 ) { // param smaller than the smallest key in list return keys[ 0 ]; } final int insertPoint = -( position + 2 ); return keys[ insertPoint ]; } private 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. } /** * Copys the list contents into a new array. * * @return the list contents as array. */ public long[] getKeys() { if ( size == 0 ) { return BreakPositionsList.EMPTY_KEYS; } if ( size == keys.length ) { return 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 ) { final int pos = findKeyInternal( coordinate ); //binarySearch(keys, coordinate, 0, size); if ( pos == size ) { //return xMaxBounds; // 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 ); } } /** * 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, keys.length - sourcePosition - 1 ); targetPosition = size - cutIndex; break; } currentCut = cutArray[ cutIndex ]; } else { keys[ targetPosition ] = key; targetPosition += 1; } } Arrays.fill( keys, targetPosition, size, 0 ); size = targetPosition; scaleFactor = keys[ size - 1 ] / size; } public void offset( final long offset ) { for ( int i = 0; i < keys.length; i++ ) { keys[ i ] -= offset; } } }