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 }