View Javadoc

1   ////////////////////////////////////////////////////////////////////////////////
2   // MillScript-Util: an Open Spice interpreter and batch website creation tool
3   // Copyright (C) 2005 Kevin Rogers
4   //
5   // This file is part of MillScript-Util.
6   //
7   // MillScript-Util is free software; you can redistribute it and/or modify it under
8   // the terms of the GNU General Public License as published by the Free
9   // Software Foundation; either version 2 of the License, or (at your option)
10  // any later version.
11  //
12  // MillScript-Util is distributed in the hope that it will be useful, but WITHOUT
13  // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14  // FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
15  // more details.
16  //
17  // You should have received a copy of the GNU General Public License along with
18  // MillScript-Util; if not, write to the Free Software Foundation, Inc., 59 Temple
19  // Place, Suite 330, Boston, MA  02111-1307  USA
20  ////////////////////////////////////////////////////////////////////////////////
21  package org.millscript.commons.util.list;
22  
23  import org.millscript.commons.alert.alerts.Fault;
24  import org.millscript.commons.alert.alerts.IllegalArgumentAlert;
25  import org.millscript.commons.util.IList;
26  import org.millscript.commons.util.ListIterator;
27  import org.millscript.commons.util.alerts.ListIndexOutOfBoundsAlert;
28  import org.millscript.commons.util.iterator.AbstractListIterator;
29  import org.millscript.commons.util.iterator.ArrayListIterator;
30  import org.millscript.commons.util.iterator.NullListIterator;
31  
32  import java.io.Serializable;
33  import java.lang.reflect.InvocationTargetException;
34  import java.lang.reflect.Method;
35  import java.util.ArrayList;
36  import java.util.Collection;
37  import java.util.Collections;
38  import java.util.Iterator;
39  import java.util.List;
40  import java.util.NoSuchElementException;
41  
42  /**
43   * This class provides an immutable <code>List</code> implementation which is
44   * backed by a java.util.List instance.
45   */
46  public class IJavaUtilList< V > extends AbstractIList< V > implements Cloneable, Serializable {
47  
48      /**
49       * This is the ID from the release 0.1.0 for future compatibility.
50       */
51      private static final long serialVersionUID = 4423628895238982551L;
52  
53      /**
54       * This class provides a map interator implementation which iterates over
55       * the values in a {@link java.util.List}.
56       */
57      public static class JavaUtilCollectionListIterator< V > extends AbstractListIterator< V > {
58  
59          /**
60           * The current object in the iteration.
61           */
62          private V currentObject;
63  
64          /**
65           * The {@link Iterator} we get out values from.
66           */
67          private final Iterator< V > javaUtilIterator;
68  
69          /**
70           * The number of iterations before we should stop.
71           */
72          private final int numberOfIterations;
73  
74          /**
75           * Constructs a new java util collection map iterator to iterate over the
76           * values in the specified {@link Collection}.
77           *
78           * @param col   the java.util.Collection whose values to iterate over
79           */
80          public JavaUtilCollectionListIterator( final Collection< V > col ) {
81              this.javaUtilIterator = col.iterator();
82              this.numberOfIterations = col.size();
83          }
84  
85          /**
86           * Constructs a new java util list map iterator to iterate over the
87           * values in the specified {@link java.util.List}.
88           *
89           * @param list  the java.util.List whose values to iterate over
90           * @param start the index(one based, inclusive) to start iterating from
91           * @param end   the index(one based, inclusive) to stop iterating at
92           */
93          public JavaUtilCollectionListIterator( final java.util.List< V > list, final int first, final int last ) {
94              if ( first > last ) {
95                  this.javaUtilIterator = null;
96                  this.numberOfIterations = 0;
97              } else if ( first < 1 || first > list.size() ) {
98                  throw new ListIndexOutOfBoundsAlert(
99                      "First index in slice must be between 1 and the size of the list"
100                 ).culprit(
101                     "index",
102                     first
103                 ).decorate( list ).mishap();
104             } else if ( last > list.size() ) {
105                 throw new ListIndexOutOfBoundsAlert(
106                     "Last index in slice must not be greater than the size of the list"
107                 ).culprit(
108                     "index",
109                     last
110                 ).decorate( list ).mishap();
111             } else {
112                 this.javaUtilIterator = list.listIterator( first - 1 );
113                 this.numberOfIterations = last - first + 1;
114             }
115         }
116 
117         /**
118          * @see org.millscript.commons.util.iterator.AbstractMapIterator#advance()
119          */
120         @Override
121         protected void advance() {
122             // Increment the position
123             super.advance();
124             // Get the next value from the upstream java.util.Iterator but
125             // check for any errors and reset the position if an error occurs
126             try {
127                 this.currentObject = this.javaUtilIterator.next();
128             } catch ( NoSuchElementException ex ) {
129                 super.position = 0;
130             } catch ( NullPointerException ex ) {
131                 super.position = 0;
132             }
133         }
134         
135         /**
136          * @see org.millscript.commons.util.iterator.AbstractMapIterator#getValue()
137          */
138         @Override
139         protected V getValue() {
140             return this.currentObject;
141         }
142 
143         /**
144          * @see org.millscript.commons.util.MapIterator#hasNext()
145          */
146         public boolean hasNext() {
147             return this.javaUtilIterator.hasNext() && super.position < this.numberOfIterations;
148         }
149 
150         /**
151          * @see org.millscript.commons.util.iterator.AbstractMapIterator#outOfBounds()
152          */
153         @Override
154         protected boolean outOfBounds() {
155             return super.position == 0 || super.position > this.numberOfIterations;
156         }
157 
158     }
159 
160     /**
161      * This class provides an immutable <code>List</code> implementation which is
162      * backed by a slice of an Object array. This class allows you to conveniently
163      * share store between different lists.
164      */
165     public static class ImmutableSharedJavaUtilList< V > extends AbstractIList< V > {
166 
167         /**
168          * The index of the first element of this list in the backing store.
169          */
170         private final int firstIndex;
171 
172         /**
173          * The index of the last element of this list in the backing store.
174          */
175         private final int lastIndex;
176         
177         /**
178          * The backing store containing a fixed array of objects.
179          */
180         private final java.util.List< V > store;
181 
182         /**
183          * Constructs a new immutable array list with a slice of the specified
184          * backing java.util.List.
185          *
186          * @param list  the backing java.util.List
187          * @param start the index(one based, inclusive) of the first element in the
188          * slice
189          * @param end   the index(one based, inclusive) of the last element in the
190          * slice. If end &lt; start, the new list will be empty
191          */
192         public ImmutableSharedJavaUtilList( final java.util.List< V > list, final int start, final int end ) {
193             if ( start > end ) {
194                 this.store = null;
195             } else if ( start < 1 || start > list.size() ) {
196                 throw new ListIndexOutOfBoundsAlert(
197                     "First index in slice must be between 1 and the size of the list"
198                 ).culprit(
199                     "index",
200                     start
201                 ).decorate( list ).mishap();
202             } else if ( end > list.size() ) {
203                 throw new ListIndexOutOfBoundsAlert(
204                     "Last index in slice must not be greater than the size of the list"
205                 ).culprit(
206                     "index",
207                     end
208                 ).decorate( list ).mishap();
209             } else {
210                 this.store = list;
211             }
212             this.firstIndex = start;
213             this.lastIndex = end;
214         }
215 
216         /**
217          * @see org.millscript.commons.util.list.AbstractIList#doGet(int)
218          */
219         @Override
220         protected V doGet( final int pos ) {
221             // NOTE - We subtract two here as both firstElementIndex and pos use
222             // one based indexing, so we have to subtract one from both
223             return this.store.get( this.firstIndex + pos - 2 );
224         }
225 
226         /**
227          * @see org.millscript.commons.util.list.AbstractIList#doSlice(int, int, boolean)
228          */
229         @Override
230         @SuppressWarnings( "unchecked" )
231         protected IList< V > doSlice( final int first, final int last, final boolean share ) {
232             // The off by one adjustment is required as the indicies are inclusive,
233             // so if first is one we must use the value of firstIndex.
234             if ( share ) {
235                 return new ImmutableSharedJavaUtilList< V >(
236                     this.store,
237                     this.firstIndex + first - 1,
238                     this.firstIndex + last - 1
239                 );
240             } else {
241                 // Return a copy of the relevant slice of the list
242                 // NOTE - we only subtract one from the first index as Java uses
243                 // crazy inclusive/exclusive rules for each argument
244                 return new IArrayList< V >(
245                     (V[]) this.store.subList( first - 1, last ).toArray(),
246                     true
247                 );
248             }
249         }
250 
251         /**
252          * @see org.millscript.commons.util.IList#indexOf(java.lang.Object)
253          */
254         public int indexOf( final V value ) {
255             final int storeIndex = this.store.indexOf( value ) + 1;
256             if ( storeIndex < 1 || storeIndex > this.size() ) {
257                 return 0;
258             } else {
259                 return storeIndex;
260             }
261         }
262 
263         /**
264          * @see org.millscript.commons.util.IMap#iterator(boolean)
265          */
266         @SuppressWarnings( "unchecked" )
267         public ListIterator< V > iterator( final boolean share ) {
268             if ( share ) {
269                 return new JavaUtilCollectionListIterator< V >(
270                     this.store,
271                     this.firstIndex,
272                     this.lastIndex
273                 );
274             } else if ( this.size() == 0 ) {
275                 return new NullListIterator< V >();
276             } else {
277                 final V[] objects = (V[]) new Object[ this.size() ];
278                 System.arraycopy( this.store, this.firstIndex - 1, objects, 0, objects.length );
279                 return new ArrayListIterator< V >( objects, true );
280             }
281         }
282 
283         /**
284          * @see org.millscript.commons.util.IMap#size()
285          */
286         public int size() {
287             return this.lastIndex < this.firstIndex ? 0
288                                                     : this.lastIndex - this.firstIndex + 1;
289         }
290 
291     }
292 
293     /**
294      * The backing store java.util.List.
295      */
296     private final List< V > store;
297 
298     /**
299      * Constructs a new empty immutable java util list. 
300      */
301     @SuppressWarnings( "unchecked" )
302     public IJavaUtilList() {
303         this.store = Collections.EMPTY_LIST;
304     }
305 
306     /**
307      * Constructs a new immutable array list with a copy of the objects in the
308      * specified collection as its backing store. The lists order will be the
309      * same as the order the objects are returned by the objects iterator.
310      *
311      * @param col    the backing java.util.Collection to copy as a list
312      */
313     public IJavaUtilList( final Collection< V > col ) {
314         this.store = new ArrayList< V >( col.size() );
315         try {
316             for ( Iterator< V > it = col.iterator(); it.hasNext(); ) {
317                 this.store.add( it.next() );
318             }
319         } catch ( Exception ex ) {
320             throw new Fault(
321                 "Failed to take a copy of the specified collection"
322             ).setParentThrowable( ex ).mishap();
323         } 
324     }
325 
326     /**
327      * Constructs a new immutable array list with a copy of the specified
328      * backing object array, starting and ending at the specified indices.
329      *
330      * @param objects   the backing object array to copy
331      * @param start the index of the first element in the slice
332      * @param end   the index of the last element in the slice. If end &lt;
333      * start, the new list will be empty
334      */
335     @SuppressWarnings( "unchecked" )
336     public IJavaUtilList( final Collection< V > col, final int start, final int end ) {
337         if ( start > end ) {
338             this.store = Collections.EMPTY_LIST;
339         } else if ( start < 1 || start > col.size() ) {
340             throw new ListIndexOutOfBoundsAlert(
341                 "First index in slice must be between 1 and the length of the collection"
342             ).culprit(
343                 "index",
344                 start
345             ).decorate( col ).mishap();
346         } else if ( end > col.size() ) {
347             throw new ListIndexOutOfBoundsAlert(
348                 "Last index in slice must not be greater than the length of the collection"
349             ).culprit(
350                 "index",
351                 end
352             ).decorate( col ).mishap();
353         } else { 
354             int n = 1;
355             this.store = new ArrayList< V >( start - end + 1 );
356             try {
357                 for ( Iterator< V > it = col.iterator(); n <= end && it.hasNext(); n++ ) {
358                     if ( n >= start ) {
359                         this.store.add( it.next() );
360                     }
361                 }
362             } catch ( Exception ex ) {
363                 throw new Fault(
364                     "Failed to take a copy of the specified collection slice"
365                 ).setParentThrowable( ex ).mishap();
366             } 
367         }
368     }
369 
370     /**
371      * Constructs a new immutable array list with a the specified list as its
372      * backing store.
373      *
374      * @param list  the backing java.util.List
375      * @param share if <code>true</code> the specified object array will be
376      * shared otherwise the array will be copied.
377      */
378     @SuppressWarnings( "unchecked" )
379     public IJavaUtilList( final List< V > list, final boolean share ) {
380         if ( share ) {
381             this.store = list;
382         } else if ( list instanceof Cloneable ) {
383             try {
384                 Class c = list.getClass();
385                 Method m = c.getMethod( "clone" );
386                 this.store = (List< V >) m.invoke( list );
387             } catch ( NoSuchMethodException ex ) {
388                 throw new IllegalArgumentAlert(
389                     "Trying to make an immutable list from a non-cloneable java.util.List instance"
390                 ).culprit( "list type", list.getClass() ).culprit( "list", list ).mishap();
391             } catch ( IllegalAccessException ex ) {
392                 throw new IllegalArgumentAlert(
393                     "Trying to make an immutable list from a non-cloneable java.util.List instance"
394                 ).culprit( "list type", list.getClass() ).culprit( "list", list ).mishap();
395             } catch ( InvocationTargetException ex ) {
396                 throw new IllegalArgumentAlert(
397                     "Trying to make an immutable list from a non-cloneable java.util.List instance"
398                 ).culprit( "list type", list.getClass() ).culprit( "list", list ).mishap();
399             }
400         } else {
401             this.store = new ArrayList< V >( list.size() );
402             try {
403                 for ( Iterator< V > it = list.iterator(); it.hasNext(); ) {
404                     this.store.add( it.next() );
405                 }
406             } catch ( Exception ex ) {
407                 throw new Fault(
408                     "Failed to take a copy of the specified collection"
409                 ).setParentThrowable( ex ).mishap();
410             } 
411         }
412     }
413 
414     /**
415      * @see java.lang.Object#clone()
416      */
417     @Override
418     public Object clone() throws CloneNotSupportedException {
419         // Nothing special required for this clone
420         return super.clone();
421     }
422 
423     /**
424      * @see org.millscript.commons.util.list.AbstractIList#doGet(int)
425      */
426     @Override
427     protected V doGet( final int pos ) {
428         return this.store.get( pos - 1 );
429     }
430 
431     /**
432      * @see org.millscript.commons.util.list.AbstractIList#doSlice(int, int, boolean)
433      */
434     @Override
435     @SuppressWarnings( "unchecked" )
436     protected IList< V > doSlice( final int first, final int last, final boolean share ) {
437         if ( share ) {
438             return new ImmutableSharedJavaUtilList< V >( this.store, first, last );
439         } else {
440             // Return a copy of the relevant slice of the list
441             // NOTE - we only subtract one from the first index as Java uses
442             // crazy inclusive/exclusive rules for each argument
443             return new IArrayList< V >(
444                 (V[]) this.store.subList( first - 1, last ).toArray(),
445                 true
446             );
447         }
448     }
449 
450     /**
451      * @see org.millscript.commons.util.IList#indexOf(java.lang.Object)
452      */
453     public int indexOf( final V value ) {
454         return this.store.indexOf( value ) + 1;
455     }
456 
457     /**
458      * @see org.millscript.commons.util.IMap#iterator(boolean)
459      */
460     @SuppressWarnings( "unchecked" )
461     public ListIterator< V > iterator( final boolean share ) {
462         if ( this.size() == 0 ) {
463             return new NullListIterator< V >();
464         } else if ( share ) {
465             return new JavaUtilCollectionListIterator< V >( this.store );
466         } else {
467             return new ArrayListIterator< V >( (V[]) this.store.toArray(), true );
468         }
469     }
470 
471     /**
472      * @see org.millscript.commons.util.IMap#size()
473      */
474     public int size() {
475         return this.store.size();
476     }
477 
478 }