org.apache.jcs.utils.struct.SortedPreferentialArray.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.jcs.utils.struct.SortedPreferentialArray.java

Source

package org.apache.jcs.utils.struct;

/*
 * 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.
 */

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

/**
 * This maintains a sorted array with a preferential replacement policy when full.
 * <p>
 * Insertion time is n, search is log(n)
 * <p>
 * Clients must manage thread safety on previous version. I synchronized the public methods to add
 * easy thread safety. I synchronized all public methods that make modifications.
 */
public class SortedPreferentialArray {
    /** The logger */
    private static final Log log = LogFactory.getLog(SortedPreferentialArray.class);

    /** prefer large means that the smallest will be removed when full. */
    private boolean preferLarge = true;

    /** maximum number allowed */
    private int maxSize = 0;

    /** The currency number */
    private int curSize = 0;

    /** The primary array */
    private Comparable[] array;

    /** the number that have been inserted. */
    private int insertCnt = 0;

    /**
     * Consruct the array with the maximum size.
     * <p>
     * @param maxSize int
     */
    public SortedPreferentialArray(int maxSize) {
        this.maxSize = maxSize;
        array = new Comparable[maxSize];
    }

    /**
     * If the array is full this will remove the smallest if preferLarge==true and if obj is bigger,
     * or the largest if preferLarge=false and obj is smaller than the largest.
     * <p>
     * @param obj Object
     */
    public synchronized void add(Comparable obj) {
        if (obj == null) {
            return;
        }

        if (curSize < maxSize) {
            insert(obj);
            return;
        }
        if (preferLarge) {
            // insert if obj is larger than the smallest
            Comparable sma = getSmallest();
            if (obj.compareTo(sma) > 0) {
                insert(obj);
                return;
            }
            // obj is less than or equal to the smallest.
            if (log.isDebugEnabled()) {
                log.debug("New object is smaller than or equal to the smallest");
            }
            return;
        }
        // Not preferLarge
        Comparable lar = getLargest();
        // insert if obj is smaller than the largest
        int diff = obj.compareTo(lar);
        if (diff > 0 || diff == 0) {
            if (log.isDebugEnabled()) {
                log.debug("New object is larger than or equal to the largest");
            }
            return;
        }
        // obj is less than the largest.
        insert(obj);
        return;
    }

    /**
     * Returns the largest without removing it from the array.
     * <p>
     * @return Comparable
     */
    public synchronized Comparable getLargest() {
        return array[curSize - 1];
    }

    /**
     * Returns the smallest element without removing it from the array.
     * <p>
     * @return Comparable
     */
    public synchronized Comparable getSmallest() {
        return array[0];
    }

    /**
     * Insert looks for the nearest largest. It then determines which way to shuffle depending on
     * the preference.
     * <p>
     * @param obj Comparable
     */
    private void insert(Comparable obj) {
        try {
            int nLar = findNearestLargerEqualOrLastPosition(obj);
            if (log.isDebugEnabled()) {
                log.debug("nLar = " + nLar + " obj = " + obj);
            }

            if (nLar == curSize) {
                // this next check should be unnecessary
                // findNearestLargerPosition should only return the curSize if
                // there is
                // room left. Check to be safe
                if (curSize < maxSize) {
                    array[nLar] = obj;
                    curSize++;
                    if (log.isDebugEnabled()) {
                        log.debug(this.dumpArray());
                    }
                    if (log.isDebugEnabled()) {
                        log.debug("Inserted object at the end of the array");
                    }
                    return;
                } // end if not full
            }

            boolean isFull = false;
            if (curSize == maxSize) {
                isFull = true;
            }

            // The array is full, we must replace
            // remove smallest or largest to determine whether to
            // shuffle left or right to insert
            if (preferLarge) {
                if (isFull) {
                    // is full, prefer larger, remove smallest by shifting left
                    int pnt = nLar - 1; // set iteration stop point
                    for (int i = 0; i < pnt; i++) {
                        array[i] = array[i + 1];
                    }
                    // use nLar-1 for insertion point
                    array[nLar - 1] = obj;
                    if (log.isDebugEnabled()) {
                        log.debug("Inserted object at " + (nLar - 1));
                    }
                } else {
                    // not full, shift right from spot
                    int pnt = nLar; // set iteration stop point
                    for (int i = curSize; i > pnt; i--) {
                        array[i] = array[i - 1];
                    }
                    // use nLar-1 for insertion point
                    array[nLar] = obj;
                    curSize++;
                    if (log.isDebugEnabled()) {
                        log.debug("Inserted object at " + (nLar));
                    }
                }
            } else {
                // prefer smaller, remove largest by shifting right
                // use nLar for insertion point
                int pnt = nLar + 1;
                if (!isFull) {
                    pnt = nLar;
                }
                for (int i = curSize; i > pnt; i--) {
                    array[i] = array[i - 1];
                }
                array[nLar] = obj;
                if (log.isDebugEnabled()) {
                    log.debug("Inserted object at " + nLar);
                }
            }

            if (log.isDebugEnabled()) {
                log.debug(this.dumpArray());
            }
        } catch (Exception e) {
            log.error("Insertion problem" + this.dumpArray(), e);
        }

        insertCnt++;
        if (insertCnt % 100 == 0) {
            if (log.isDebugEnabled()) {
                log.debug(this.dumpArray());
            }
        }
    }

    /**
     * Determines whether the preference is for large or small.
     * <p>
     * @param pref boolean
     */
    public synchronized void setPreferLarge(boolean pref) {
        preferLarge = pref;
    }

    /**
     * Returns and removes the nearer larger or equal object from the aray.
     * <p>
     * @param obj Comparable
     * @return Comparable, null if arg is null or none was found.
     */
    public synchronized Comparable takeNearestLargerOrEqual(Comparable obj) {
        if (obj == null) {
            return null;
        }

        Comparable retVal = null;
        try {
            int pos = findNearestOccupiedLargerOrEqualPosition(obj);
            if (pos == -1) {
                return null;
            }

            try {
                retVal = array[pos];
                remove(pos);
            } catch (Exception e) {
                log.error("Problem removing from array. pos [" + pos + "] " + obj, e);
            }

            if (log.isDebugEnabled()) {
                log.debug("obj = " + obj + " || retVal = " + retVal);
            }
        } catch (Exception e) {
            log.error("Take problem" + this.dumpArray(), e);
        }
        return retVal;
    }

    /**
     * Returns the current size of the array.
     * <p>
     * @return int
     */
    public synchronized int size() {
        return this.curSize;
    }

    /**
     * This determines the position in the array that is occupied by an object that is larger or
     * equal to the argument. If none exists, -1 is returned.
     * <p>
     * @param obj Object
     * @return Object
     */
    private int findNearestOccupiedLargerOrEqualPosition(Comparable obj) {
        if (curSize == 0) {
            // nothing in the array
            return -1;
        }

        // this gives us an insert position.
        int pos = findNearestLargerEqualOrLastPosition(obj);

        // see if the previous will do to handle the empty insert spot position
        if (pos == curSize) { // && curSize < maxSize ) {
                              // pos will be > 0 if it equals curSize, we check for this above.
            if (obj.compareTo(array[pos - 1]) <= 0) {
                pos = pos - 1;
            } else {
                pos = -1;
            }
        } else {
            // the find nearest, returns the last, since it is used by insertion.
            if (obj.compareTo(array[pos]) > 0) {
                return -1;
            }
        }

        return pos;
    }

    /**
     * This method determines the position where an insert should take place for a given object.
     * With some additional checking, this can also be used to find an object matching or greater
     * than the argument.
     * <p>
     * If the array is not full and the current object is larger than all the rest the first open
     * slot at the end will be returned.
     * <p>
     * NOTE: If the object is larger than the largest and it is full, it will return the last position.
     * <p>
     * If the array is empty, the first spot is returned.
     * <p>
     * If the object is smaller than all the rests, the first position is returned. The caller must
     * decide what to do given the preference.
     * <p>
     * Returns the position of the object nearest to or equal to the larger object.
     * <p>
     * If you want to find the takePosition, you have to calculate it.
     * findNearestOccupiedLargerOrEqualPosition will calculate this for you.
     * <p>
     * @param obj Comparable
     * @return int
     */
    private int findNearestLargerEqualOrLastPosition(Comparable obj) {
        // do nothing if a null was passed in
        if (obj == null) {
            return -1;
        }

        // return the first spot if the array is empty
        if (curSize <= 0) {
            return 0;
        }

        // mark the numer to be returned, the greaterPos as unset
        int greaterPos = -1;
        // prepare for a binary search
        int curPos = (curSize - 1) / 2;
        int prevPos = -1;

        try {
            // set the loop exit flag to false
            boolean done = false;

            // check the ends
            // return insert position 0 if obj is smaller
            // than the smallest. the caller can determine what to
            // do with this, depending on the preference setting
            if (obj.compareTo(getSmallest()) <= 0) {
                // LESS THAN OR EQUAL TO SMALLEST
                if (log.isDebugEnabled()) {
                    log.debug(obj + " is smaller than or equal to " + getSmallest());
                }
                greaterPos = 0;
                done = true;
                // return greaterPos;
            } else {
                // GREATER THAN SMALLEST
                if (log.isDebugEnabled()) {
                    log.debug(obj + " is bigger than " + getSmallest());
                }

                // return the largest position if obj is larger
                // than the largest. the caller can determine what to
                // do with this, depending on the preference setting
                if (obj.compareTo(getLargest()) >= 0) {
                    if (curSize == maxSize) {
                        // there is no room left in the array, return the last
                        // spot
                        greaterPos = curSize - 1;
                        done = true;
                    } else {
                        // there is room left in the array
                        greaterPos = curSize;
                        done = true;
                    }
                } else {
                    // the obj is less than or equal to the largest, so we know that the
                    // last item is larger or equal
                    greaterPos = curSize - 1;
                }
            }

            // /////////////////////////////////////////////////////////////////////
            // begin binary search for insertion spot
            while (!done) {
                if (log.isDebugEnabled()) {
                    log.debug("\n curPos = " + curPos + "; greaterPos = " + greaterPos + "; prevpos = " + prevPos);
                }

                // get out of loop if we have come to the end or passed it
                if (curPos == prevPos || curPos >= curSize) {
                    done = true;
                    break;
                } else

                // EQUAL TO
                // object at current position is equal to the obj, use this,
                // TODO could avoid some shuffling if I found a lower pos.
                if (array[curPos].compareTo(obj) == 0) {
                    if (log.isDebugEnabled()) {
                        log.debug(array[curPos] + " is equal to " + obj);
                    }
                    greaterPos = curPos;
                    done = true;
                    break;
                } else

                // GREATER THAN
                // array object at current position is greater than the obj, go
                // left
                if (array[curPos].compareTo(obj) > 0) {
                    if (log.isDebugEnabled()) {
                        log.debug(array[curPos] + " is greater than " + obj);
                    }
                    // set the smallest greater equal to the current position
                    greaterPos = curPos;
                    // set the current position to
                    // set the previous position to the current position
                    // We could have an integer overflow, but this array could
                    // never get that large.
                    int newPos = Math.min(curPos, (curPos + prevPos) / 2);
                    prevPos = curPos;
                    curPos = newPos;
                } else

                // LESS THAN
                // the object at the current position is smaller, go right
                if (array[curPos].compareTo(obj) < 0) {
                    if (log.isDebugEnabled()) {
                        log.debug(array[curPos] + " is less than " + obj);
                    }
                    if ((greaterPos != -1) && greaterPos - curPos < 0) {
                        done = true;
                        break; // return greaterPos;
                    } else {
                        int newPos = 0;
                        if (prevPos > curPos) {
                            newPos = Math.min((curPos + prevPos) / 2, curSize);
                        } else if (prevPos == -1) {
                            newPos = Math.min((curSize + curPos) / 2, curSize);
                        }
                        prevPos = curPos;
                        curPos = newPos;
                    }
                }
            } // end while
              // /////////////////////////////////////////////////////////////////////

            if (log.isDebugEnabled()) {
                log.debug("Greater Position is [" + greaterPos + "]" + " array[greaterPos] [" + array[greaterPos]
                        + "]");
            }
        } catch (Exception e) {
            log.error("\n curPos = " + curPos + "; greaterPos = " + greaterPos + "; prevpos = " + prevPos + " "
                    + this.dumpArray(), e);
        }

        return greaterPos;
    }

    /**
     * Removes the item from the array at the specified position. The remaining items to the right
     * are shifted left.
     * <p>
     * @param position int
     * @throw IndexOutOfBoundsException if position is out of range.
     */
    private void remove(int position) {
        if (position >= curSize || position < 0) {
            throw new IndexOutOfBoundsException("position=" + position + " must be less than curSize=" + curSize);
        }
        curSize--;

        if (position < curSize) {
            try {
                System.arraycopy(array, position + 1, array, position, (curSize - position));
            } catch (IndexOutOfBoundsException ibe) {
                // throw this, log details for debugging. This shouldn't happen.
                log.warn("Caught index out of bounds exception. "
                        + "called 'System.arraycopy( array, position + 1, array, position, (curSize - position) );'  "
                        + "array.lengh [" + array.length + "] position [" + position + "] curSize [" + curSize
                        + "]");
                throw ibe;
            }
        }
        return;
    }

    /**
     * Debugging method to return a human readable display of array data.
     * <p>
     * @return String representation of the contents.
     */
    protected synchronized String dumpArray() {
        StringBuffer buf = new StringBuffer();
        buf.append("\n ---------------------------");
        buf.append("\n curSize = " + curSize);
        buf.append("\n array.length = " + array.length);
        buf.append("\n ---------------------------");
        buf.append("\n Dump:");
        for (int i = 0; i < curSize; i++) {
            buf.append("\n " + i + "=" + array[i]);
        }
        return buf.toString();
    }
}