View Javadoc

1   package org.apache.jcs.utils.struct;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import org.apache.commons.logging.Log;
23  import org.apache.commons.logging.LogFactory;
24  
25  /**
26   * This maintains a sorted array with a preferential replacement policy when full.
27   * <p>
28   * Insertion time is n, search is log(n)
29   * <p>
30   * Clients must manage thread safety on previous version. I synchronized the public methods to add
31   * easy thread safety. I synchronized all public methods that make modifications.
32   */
33  public class SortedPreferentialArray<T extends Comparable<? super T>>
34  {
35      /** The logger */
36      private static final Log log = LogFactory.getLog( SortedPreferentialArray.class );
37  
38      /** prefer large means that the smallest will be removed when full. */
39      private boolean preferLarge = true;
40  
41      /** maximum number allowed */
42      private int maxSize = 0;
43  
44      /** The currency number */
45      private int curSize = 0;
46  
47      /** The primary array */
48      private final T[] array;
49  
50      /** the number that have been inserted. */
51      private int insertCnt = 0;
52  
53      /**
54       * Construct the array with the maximum size.
55       * <p>
56       * @param maxSize int
57       */
58      public SortedPreferentialArray( int maxSize )
59      {
60          this.maxSize = maxSize;
61          @SuppressWarnings("unchecked") // No generic arrays in java
62          T[] ts = (T[]) new Comparable<?>[maxSize];
63          array = ts;
64      }
65  
66      /**
67       * If the array is full this will remove the smallest if preferLarge==true and if obj is bigger,
68       * or the largest if preferLarge=false and obj is smaller than the largest.
69       * <p>
70       * @param obj Object
71       */
72      public synchronized void add(T obj)
73      {
74          if ( obj == null )
75          {
76              return;
77          }
78  
79          if ( curSize < maxSize )
80          {
81              insert( obj );
82              return;
83          }
84          if ( preferLarge )
85          {
86              // insert if obj is larger than the smallest
87              T sma = getSmallest();
88              if ( obj.compareTo( sma ) > 0 )
89              {
90                  insert( obj );
91                  return;
92              }
93              // obj is less than or equal to the smallest.
94              if ( log.isDebugEnabled() )
95              {
96                  log.debug( "New object is smaller than or equal to the smallest" );
97              }
98              return;
99          }
100         // Not preferLarge
101         T lar = getLargest();
102         // insert if obj is smaller than the largest
103         int diff = obj.compareTo( lar );
104         if ( diff > 0 || diff == 0 )
105         {
106             if ( log.isDebugEnabled() )
107             {
108                 log.debug( "New object is larger than or equal to the largest" );
109             }
110             return;
111         }
112         // obj is less than the largest.
113         insert( obj );
114     }
115 
116     /**
117      * Returns the largest without removing it from the array.
118      * <p>
119      * @return Comparable
120      */
121     public synchronized T getLargest()
122     {
123         return array[curSize - 1];
124     }
125 
126     /**
127      * Returns the smallest element without removing it from the array.
128      * <p>
129      * @return Comparable
130      */
131     public synchronized T getSmallest()
132     {
133         return array[0];
134     }
135 
136     /**
137      * Insert looks for the nearest largest. It then determines which way to shuffle depending on
138      * the preference.
139      * <p>
140      * @param obj Comparable
141      */
142     private void insert(T obj)
143     {
144         try
145         {
146             int nLar = findNearestLargerEqualOrLastPosition( obj );
147             if ( log.isDebugEnabled() )
148             {
149                 log.debug( "nLar = " + nLar + " obj = " + obj );
150             }
151 
152             if ( nLar == curSize )
153             {
154                 // this next check should be unnecessary
155                 // findNearestLargerPosition should only return the curSize if
156                 // there is
157                 // room left. Check to be safe
158                 if ( curSize < maxSize )
159                 {
160                     array[nLar] = obj;
161                     curSize++;
162                     if ( log.isDebugEnabled() )
163                     {
164                         log.debug( this.dumpArray() );
165                     }
166                     if ( log.isDebugEnabled() )
167                     {
168                         log.debug( "Inserted object at the end of the array" );
169                     }
170                     return;
171                 } // end if not full
172             }
173 
174             boolean isFull = false;
175             if ( curSize == maxSize )
176             {
177                 isFull = true;
178             }
179 
180             // The array is full, we must replace
181             // remove smallest or largest to determine whether to
182             // shuffle left or right to insert
183             if ( preferLarge )
184             {
185                 if ( isFull )
186                 {
187                     // is full, prefer larger, remove smallest by shifting left
188                     int pnt = nLar - 1; // set iteration stop point
189                     for ( int i = 0; i < pnt; i++ )
190                     {
191                         array[i] = array[i + 1];
192                     }
193                     // use nLar-1 for insertion point
194                     array[nLar - 1] = obj;
195                     if ( log.isDebugEnabled() )
196                     {
197                         log.debug( "Inserted object at " + ( nLar - 1 ) );
198                     }
199                 }
200                 else
201                 {
202                     // not full, shift right from spot
203                     int pnt = nLar; // set iteration stop point
204                     for ( int i = curSize; i > pnt; i-- )
205                     {
206                         array[i] = array[i - 1];
207                     }
208                     // use nLar-1 for insertion point
209                     array[nLar] = obj;
210                     curSize++;
211                     if ( log.isDebugEnabled() )
212                     {
213                         log.debug( "Inserted object at " + ( nLar ) );
214                     }
215                 }
216             }
217             else
218             {
219                 // prefer smaller, remove largest by shifting right
220                 // use nLar for insertion point
221                 int pnt = nLar + 1;
222                 if ( !isFull )
223                 {
224                     pnt = nLar;
225                 }
226                 for ( int i = curSize; i > pnt; i-- )
227                 {
228                     array[i] = array[i - 1];
229                 }
230                 array[nLar] = obj;
231                 if ( log.isDebugEnabled() )
232                 {
233                     log.debug( "Inserted object at " + nLar );
234                 }
235             }
236 
237             if ( log.isDebugEnabled() )
238             {
239                 log.debug( this.dumpArray() );
240             }
241         }
242         catch ( Exception e )
243         {
244             log.error( "Insertion problem" + this.dumpArray(), e );
245         }
246 
247         insertCnt++;
248         if ( insertCnt % 100 == 0 )
249         {
250             if ( log.isDebugEnabled() )
251             {
252                 log.debug( this.dumpArray() );
253             }
254         }
255     }
256 
257     /**
258      * Determines whether the preference is for large or small.
259      * <p>
260      * @param pref boolean
261      */
262     public synchronized void setPreferLarge( boolean pref )
263     {
264         preferLarge = pref;
265     }
266 
267     /**
268      * Returns and removes the nearer larger or equal object from the aray.
269      * <p>
270      * @param obj Comparable
271      * @return Comparable, null if arg is null or none was found.
272      */
273     public synchronized T takeNearestLargerOrEqual( T obj )
274     {
275         if ( obj == null )
276         {
277             return null;
278         }
279 
280         T retVal = null;
281         try
282         {
283             int pos = findNearestOccupiedLargerOrEqualPosition( obj );
284             if ( pos == -1 )
285             {
286                 return null;
287             }
288 
289             try
290             {
291                 retVal = array[pos];
292                 remove( pos );
293             }
294             catch ( Exception e )
295             {
296                 log.error( "Problem removing from array. pos [" + pos + "] " + obj, e );
297             }
298 
299             if ( log.isDebugEnabled() )
300             {
301                 log.debug( "obj = " + obj + " || retVal = " + retVal );
302             }
303         }
304         catch ( Exception e )
305         {
306             log.error( "Take problem" + this.dumpArray(), e );
307         }
308         return retVal;
309     }
310 
311     /**
312      * Returns the current size of the array.
313      * <p>
314      * @return int
315      */
316     public synchronized int size()
317     {
318         return this.curSize;
319     }
320 
321     /**
322      * This determines the position in the array that is occupied by an object that is larger or
323      * equal to the argument. If none exists, -1 is returned.
324      * <p>
325      * @param obj Object
326      * @return Object
327      */
328     private int findNearestOccupiedLargerOrEqualPosition(T obj)
329     {
330         if ( curSize == 0 )
331         {
332             // nothing in the array
333             return -1;
334         }
335 
336         // this gives us an insert position.
337         int pos = findNearestLargerEqualOrLastPosition( obj );
338 
339         // see if the previous will do to handle the empty insert spot position
340         if ( pos == curSize )
341         { // && curSize < maxSize ) {
342             // pos will be > 0 if it equals curSize, we check for this above.
343             if ( obj.compareTo(array[pos - 1] ) <= 0 )
344             {
345                 pos = pos - 1;
346             }
347             else
348             {
349                 pos = -1;
350             }
351         }
352         else
353         {
354             // the find nearest, returns the last, since it is used by insertion.
355             if ( obj.compareTo(array[pos] ) > 0 )
356             {
357                 return -1;
358             }
359         }
360 
361         return pos;
362     }
363 
364     /**
365      * This method determines the position where an insert should take place for a given object.
366      * With some additional checking, this can also be used to find an object matching or greater
367      * than the argument.
368      * <p>
369      * If the array is not full and the current object is larger than all the rest the first open
370      * slot at the end will be returned.
371      * <p>
372      * NOTE: If the object is larger than the largest and it is full, it will return the last position.
373      * <p>
374      * If the array is empty, the first spot is returned.
375      * <p>
376      * If the object is smaller than all the rests, the first position is returned. The caller must
377      * decide what to do given the preference.
378      * <p>
379      * Returns the position of the object nearest to or equal to the larger object.
380      * <p>
381      * If you want to find the takePosition, you have to calculate it.
382      * findNearestOccupiedLargerOrEqualPosition will calculate this for you.
383      * <p>
384      * @param obj Comparable
385      * @return int
386      */
387     private int findNearestLargerEqualOrLastPosition(T obj)
388     {
389         // do nothing if a null was passed in
390         if ( obj == null )
391         {
392             return -1;
393         }
394 
395         // return the first spot if the array is empty
396         if ( curSize <= 0 )
397         {
398             return 0;
399         }
400 
401         // mark the numer to be returned, the greaterPos as unset
402         int greaterPos = -1;
403         // prepare for a binary search
404         int curPos = ( curSize - 1 ) / 2;
405         int prevPos = -1;
406 
407         try
408         {
409             // set the loop exit flag to false
410             boolean done = false;
411 
412             // check the ends
413             // return insert position 0 if obj is smaller
414             // than the smallest. the caller can determine what to
415             // do with this, depending on the preference setting
416             if ( obj.compareTo( getSmallest() ) <= 0 )
417             {
418                 // LESS THAN OR EQUAL TO SMALLEST
419                 if ( log.isDebugEnabled() )
420                 {
421                     log.debug( obj + " is smaller than or equal to " + getSmallest() );
422                 }
423                 greaterPos = 0;
424                 done = true;
425                 // return greaterPos;
426             }
427             else
428             {
429                 // GREATER THAN SMALLEST
430                 if ( log.isDebugEnabled() )
431                 {
432                     log.debug( obj + " is bigger than " + getSmallest() );
433                 }
434 
435                 // return the largest position if obj is larger
436                 // than the largest. the caller can determine what to
437                 // do with this, depending on the preference setting
438                 if ( obj.compareTo( getLargest() ) >= 0 )
439                 {
440                     if ( curSize == maxSize )
441                     {
442                         // there is no room left in the array, return the last
443                         // spot
444                         greaterPos = curSize - 1;
445                         done = true;
446                     }
447                     else
448                     {
449                         // there is room left in the array
450                         greaterPos = curSize;
451                         done = true;
452                     }
453                 }
454                 else
455                 {
456                     // the obj is less than or equal to the largest, so we know that the
457                     // last item is larger or equal
458                     greaterPos = curSize - 1;
459                 }
460             }
461 
462             // /////////////////////////////////////////////////////////////////////
463             // begin binary search for insertion spot
464             while ( !done )
465             {
466                 if ( log.isDebugEnabled() )
467                 {
468                     log.debug( "\n curPos = " + curPos + "; greaterPos = " + greaterPos + "; prevpos = " + prevPos );
469                 }
470 
471                 // get out of loop if we have come to the end or passed it
472                 if ( curPos == prevPos || curPos >= curSize )
473                 {
474                     done = true;
475                     break;
476                 }
477                 else
478 
479                 // EQUAL TO
480                 // object at current position is equal to the obj, use this,
481                 // TODO could avoid some shuffling if I found a lower pos.
482                 if ((array[curPos]).compareTo( obj ) == 0 )
483                 {
484                     if ( log.isDebugEnabled() )
485                     {
486                         log.debug( array[curPos] + " is equal to " + obj );
487                     }
488                     greaterPos = curPos;
489                     done = true;
490                     break;
491                 }
492                 else
493 
494                 // GREATER THAN
495                 // array object at current position is greater than the obj, go
496                 // left
497                 if ((array[curPos]).compareTo( obj ) > 0 )
498                 {
499                     if ( log.isDebugEnabled() )
500                     {
501                         log.debug( array[curPos] + " is greater than " + obj );
502                     }
503                     // set the smallest greater equal to the current position
504                     greaterPos = curPos;
505                     // set the current position to
506                     // set the previous position to the current position
507                     // We could have an integer overflow, but this array could
508                     // never get that large.
509                     int newPos = Math.min( curPos, ( curPos + prevPos ) / 2 );
510                     prevPos = curPos;
511                     curPos = newPos;
512                 }
513                 else
514 
515                 // LESS THAN
516                 // the object at the current position is smaller, go right
517                 if ((array[curPos]).compareTo( obj ) < 0 )
518                 {
519                     if ( log.isDebugEnabled() )
520                     {
521                         log.debug( array[curPos] + " is less than " + obj );
522                     }
523                     if ( ( greaterPos != -1 ) && greaterPos - curPos < 0 )
524                     {
525                         done = true;
526                         break; // return greaterPos;
527                     }
528                     else
529                     {
530                         int newPos = 0;
531                         if ( prevPos > curPos )
532                         {
533                             newPos = Math.min( ( curPos + prevPos ) / 2, curSize );
534                         }
535                         else if ( prevPos == -1 )
536                         {
537                             newPos = Math.min( ( curSize + curPos ) / 2, curSize );
538                         }
539                         prevPos = curPos;
540                         curPos = newPos;
541                     }
542                 }
543             } // end while
544             // /////////////////////////////////////////////////////////////////////
545 
546             if ( log.isDebugEnabled() )
547             {
548                 log.debug( "Greater Position is [" + greaterPos + "]" + " array[greaterPos] [" + array[greaterPos]
549                     + "]" );
550             }
551         }
552         catch ( Exception e )
553         {
554             log.error( "\n curPos = " + curPos + "; greaterPos = " + greaterPos + "; prevpos = " + prevPos + " "
555                 + this.dumpArray(), e );
556         }
557 
558         return greaterPos;
559     }
560 
561     /**
562      * Removes the item from the array at the specified position. The remaining items to the right
563      * are shifted left.
564      * <p>
565      * @param position int
566      * @throw IndexOutOfBoundsException if position is out of range.
567      */
568     private void remove( int position )
569     {
570         if ( position >= curSize || position < 0 )
571         {
572             throw new IndexOutOfBoundsException( "position=" + position + " must be less than curSize=" + curSize );
573         }
574         curSize--;
575 
576         if ( position < curSize )
577         {
578             try
579             {
580                 System.arraycopy( array, position + 1, array, position, ( curSize - position ) );
581             }
582             catch ( IndexOutOfBoundsException ibe )
583             {
584                 // throw this, log details for debugging. This shouldn't happen.
585                 log.warn( "Caught index out of bounds exception. "
586                     + "called 'System.arraycopy( array, position + 1, array, position, (curSize - position) );'  "
587                     + "array.lengh [" + array.length + "] position [" + position + "] curSize [" + curSize + "]" );
588                 throw ibe;
589             }
590         }
591     }
592 
593     /**
594      * Debugging method to return a human readable display of array data.
595      * <p>
596      * @return String representation of the contents.
597      */
598     protected synchronized String dumpArray()
599     {
600         StringBuffer buf = new StringBuffer();
601         buf.append( "\n ---------------------------" );
602         buf.append( "\n curSize = " + curSize );
603         buf.append( "\n array.length = " + array.length );
604         buf.append( "\n ---------------------------" );
605         buf.append( "\n Dump:" );
606         for ( int i = 0; i < curSize; i++ )
607         {
608             buf.append( "\n " + i + "=" + array[i] );
609         }
610         return buf.toString();
611     }
612 }