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.map;
22  
23  import java.io.Serializable;
24  
25  import org.millscript.commons.alert.alerts.IllegalArgumentAlert;
26  import org.millscript.commons.util.IList;
27  import org.millscript.commons.util.ListIterator;
28  import org.millscript.commons.util.MapIterator;
29  import org.millscript.commons.util.Maplet;
30  import org.millscript.commons.util.alerts.ListIndexOutOfBoundsAlert;
31  import org.millscript.commons.util.iterator.AbstractListIterator;
32  import org.millscript.commons.util.iterator.AbstractMapIterator;
33  import org.millscript.commons.util.iterator.ArrayListIterator;
34  import org.millscript.commons.util.iterator.SkippingSharedArrayListIterator;
35  import org.millscript.commons.util.list.AbstractIList;
36  import org.millscript.commons.util.list.IArrayList;
37  import org.millscript.commons.util.maplet.IMaplet;
38  
39  /**
40   * This class provides an immutable <code>Map</code> implementation which is
41   * backed by an Object array containing alternate name-value objects.
42   */
43  public class IArrayMap< K, V > extends AbstractIMap< K, V > implements Cloneable, Serializable {
44  
45      /**
46       * This is the ID from the release 0.1.0 for future compatibility.
47       */
48      private static final long serialVersionUID = -5390055346082586713L;
49  
50      /**
51       * This class implements a map iterator which iterates over an array
52       * containing alternating key-value objects.
53       */
54      public static class ArrayMapMapIterator< K, V > extends AbstractMapIterator< K, V > {
55  
56          /**
57           * The current position of the iterator within the array.
58           */
59          private int pos = -2;
60  
61          /**
62           * The backing store containing a fixed array of alternate name-value
63           * objects.
64           */
65          private final Object[] store;
66  
67          /**
68           * Constructs a new array iterator to iterate over the alternating
69           * key-value objects in the specified array.
70           *
71           * @param objects   the array containing alternate key-value objects
72           * @param share if <code>true</code> the specified object array will be
73           * shared otherwise the array will be copied.
74           */
75          public ArrayMapMapIterator( final Object[] objects, final boolean share ) {
76              if ( objects == null || share ) {
77                  this.store = objects;
78              } else {
79                  this.store = new Object[ objects.length ];
80                  System.arraycopy( objects, 0, this.store, 0, objects.length );
81              }
82          }
83  
84          /**
85           * @see org.millscript.commons.util.iterator.AbstractMapIterator#advance()
86           */
87          @Override
88          protected void advance() {
89              this.pos += 2;
90          }
91  
92          /**
93           * @see org.millscript.commons.util.iterator.AbstractMapIterator#getKey()
94           */
95          @Override
96          @SuppressWarnings( "unchecked" )
97          protected K getKey() {
98              return (K) this.store[ this.pos ];
99          }
100 
101         /**
102          * @see org.millscript.commons.util.iterator.AbstractMapIterator#getValue()
103          */
104         @Override
105         @SuppressWarnings( "unchecked" )
106         protected V getValue() {
107             return (V) this.store[ this.pos + 1 ];
108         }
109 
110         /**
111          * @see org.millscript.commons.util.MapIterator#hasNext()
112          */
113         public boolean hasNext() {
114             return this.store != null && this.pos + 2 < this.store.length;
115         }
116 
117         /**
118          * @see org.millscript.commons.util.iterator.AbstractMapIterator#outOfBounds()
119          */
120         @Override
121         protected boolean outOfBounds() {
122             return this.store == null || this.pos < 0 || this.pos >= this.store.length;
123         }
124 
125     }
126 
127     public static class SharedMapletListMapIterator< K, V >  extends AbstractListIterator< Maplet< K, V > > {
128 
129         /**
130          * The index of the first element in the list.
131          */
132         private final int firstIndex;
133 
134         /**
135          * The number of elements in the list.
136          */
137         private final int size;
138 
139         /**
140          * The backing store containing a fixed array of alternate name-value
141          * objects.
142          */
143         private final Object[] store;
144 
145         /**
146          * Constructs a new array iterator to iterate over the alternating
147          * key-value objects in the specified array.
148          *
149          * @param objects   the array containing alternate key-value objects
150          * @param first the index(one based, inclusive) of the first list
151          * element(the maplets key) within the array
152          * @param len   the number of elements in this list
153          */
154         public SharedMapletListMapIterator( final Object[] objects, final int first, final int len ) {
155             if ( len <= 0 ) {
156                 // We keep this test here to avoid problems with the array
157                 // length check below
158                 this.firstIndex = first - 1;
159                 this.size = len;
160                 this.store = null;
161             } else if ( first < 0 || first > objects.length ) {
162                 throw new ListIndexOutOfBoundsAlert(
163                     "First index in slice must be between 1 and the length of the array"
164                 ).culprit(
165                     "index",
166                     first
167                 ).decorate( objects ).mishap();
168             } else if ( first + 2 * ( len - 1 ) > objects.length ) {
169                 throw new ListIndexOutOfBoundsAlert(
170                     "Requested list exceeds bounds of specified array"
171                 ).culprit(
172                     "first element index",
173                     first
174                 ).culprit(
175                     "requested size",
176                     len
177                 ).decorate( objects ).mishap();
178 //            } else if ( ( first - len + 2 ) % 2 != 0 ) {
179 //                throw new IllegalArgumentAlert(
180 //                    "Backing store key-value array length must be a multiple of 2"
181 //                ).culprit(
182 //                    "array",
183 //                    objects
184 //                ).culprit(
185 //                    "array length",
186 //                    objects.length
187 //                ).culprit(
188 //                    "first name index",
189 //                    first
190 //                ).culprit(
191 //                    "last name index",
192 //                    len
193 //                ).mishap();
194             } else {
195                 this.firstIndex = first - 1;
196                 this.size = len;
197                 this.store = objects;
198             }
199         }
200 
201         /**
202          * @see org.millscript.commons.util.iterator.AbstractMapIterator#getValue()
203          */
204         @Override
205         @SuppressWarnings( "unchecked" )
206         protected Maplet< K, V > getValue() {
207             return new IMaplet< K, V >(
208                 (K) this.store[ this.firstIndex + 2 * super.position - 2 ],
209                 (V) this.store[ this.firstIndex + 2 * super.position - 1 ]
210             );
211         }
212 
213         /**
214          * @see org.millscript.commons.util.MapIterator#hasNext()
215          */
216         public boolean hasNext() {
217             return super.position < this.size;
218         }
219 
220         /**
221          * @see org.millscript.commons.util.iterator.AbstractMapIterator#outOfBounds()
222          */
223         @Override
224         protected boolean outOfBounds() {
225             return super.position == 0 || super.position > this.size;
226         }
227 
228     } 
229 
230     /**
231      * This class implements a maplet list whose individual maplets are stored
232      * in a shared array containing alternating key-value objects.
233      */
234     public static class ImmutableSharedMapletList< K, V > extends AbstractIList< Maplet< K, V > > {
235 
236         /**
237          * The index of the first element in the list.
238          */
239         private final int firstIndex;
240 
241         /**
242          * The number of elements in the list.
243          */
244         private final int size;
245 
246         /**
247          * The backing store containing a fixed array of alternate name-value
248          * objects.
249          */
250         private final Object[] store;
251 
252         /**
253          * Constructs a new array iterator to iterate over the alternating
254          * key-value objects in the specified array.
255          *
256          * @param objects   the array containing alternate key-value objects
257          * @param first the index(one based, inclusive) of the first list
258          * element(the maplets key) within the array
259          * @param len   the number of elements in this list
260          */
261         public ImmutableSharedMapletList( final Object[] objects, final int first, final int len ) {
262             if ( len <= 0 ) {
263                 // We keep this test here to avoid problems with the array
264                 // length check below
265                 this.firstIndex = first - 1;
266                 this.size = len;
267                 this.store = null;
268             } else if ( first < 0 || first > objects.length ) {
269                 throw new ListIndexOutOfBoundsAlert(
270                     "First index in slice must be between 1 and the length of the array"
271                 ).culprit(
272                     "index",
273                     first
274                 ).decorate( objects ).mishap();
275             } else if ( first + 2 * ( len - 1 ) > objects.length ) {
276                 throw new ListIndexOutOfBoundsAlert(
277                     "Requested list exceeds bounds of specified array"
278                 ).culprit(
279                     "first element index",
280                     first
281                 ).culprit(
282                     "requested size",
283                     len
284                 ).decorate( objects ).mishap();
285 //            } else if ( ( first - len + 2 ) % 2 != 0 ) {
286 //                throw new IllegalArgumentAlert(
287 //                    "Backing store key-value array length must be a multiple of 2"
288 //                ).culprit(
289 //                    "array",
290 //                    objects
291 //                ).culprit(
292 //                    "array length",
293 //                    objects.length
294 //                ).culprit(
295 //                    "first name index",
296 //                    first
297 //                ).culprit(
298 //                    "last name index",
299 //                    len
300 //                ).mishap();
301             } else {
302                 this.firstIndex = first - 1;
303                 this.size = len;
304                 this.store = objects;
305             }
306         }
307 
308         /**
309          * @see org.millscript.commons.util.list.AbstractIList#doGet(int)
310          */
311         @Override
312         @SuppressWarnings( "unchecked" )
313         protected Maplet< K, V > doGet( final int pos ) {
314             return new IMaplet< K, V >(
315                 (K) this.store[ this.firstIndex + 2 * ( pos - 1 ) ],
316                 (V) this.store[ this.firstIndex + 2 * pos ]
317             );
318         }
319 
320         /**
321          * @see org.millscript.commons.util.list.AbstractIList#doSlice(int, int, boolean)
322          */
323         @Override
324         @SuppressWarnings( "unchecked" )
325         protected IList< Maplet< K, V > > doSlice( int first, int last, boolean share ) {
326             if ( share ) {
327                 return new ImmutableSharedMapletList< K, V >(
328                     this.store,
329                     this.firstIndex + first - 1,
330                     last - first + 1
331                 );
332             } else {
333                 // Return a copy of the relevant slice of the list
334                 final Maplet< K, V >[] maplets = (Maplet< K, V >[]) new Maplet[ this.size() ];
335                 for ( int i = this.firstIndex, j = 0; j < this.size(); i += 2, j++ ) {
336                     maplets[ j ] = new IMaplet< K, V >(
337                         (K) this.store[ i ],
338                         (V) this.store[ i + 1 ]
339                     );
340                 }
341                 return new IArrayList< Maplet< K, V > >( maplets, true );
342             }
343         }
344 
345         /**
346          * @see org.millscript.commons.util.IList#indexOf(java.lang.Object)
347          */
348         public int indexOf( final Maplet< K, V > value ) {
349             try {
350                 for ( int i = this.firstIndex, j = 1; j <= this.size(); i += 2, j++ ) {
351                     if ( value.getKey() == null ? this.store[ i ] == null : value.getKey().equals( this.store[ i ] ) ) {
352                         if ( value.getValue() == null ? this.store[ i + 1 ] == null : value.getValue().equals( this.store[ i + 1 ] ) ) {
353                             return j;
354                         }
355                     }
356                 }
357                 return 0;
358             } catch ( ClassCastException ex ) {
359                 return 0;
360             } catch ( NullPointerException ex ) {
361                 return 0;
362             }
363         }
364 
365         /**
366          * @see org.millscript.commons.util.IMap#iterator(boolean)
367          */
368         @SuppressWarnings( "unchecked" )
369         public ListIterator< Maplet< K, V > > iterator( boolean share ) {
370             if ( share ) {
371                 return new SharedMapletListMapIterator< K, V >(
372                     this.store,
373                     this.firstIndex + 1,
374                     this.size
375                 );
376             } else {
377                 final Maplet< K, V >[] maplets = (Maplet< K, V >[]) new Maplet[ this.size() ];
378                 for ( int i = this.firstIndex, j = 0; j < this.size(); i += 2, j++ ) {
379                     maplets[ j ] = new IMaplet< K, V >(
380                         (K) this.store[ i ],
381                         (V) this.store[ i + 1 ]
382                     );
383                 }
384                 return new ArrayListIterator< Maplet< K, V > >( maplets, true );
385             }
386         }
387 
388         /**
389          * @see org.millscript.commons.util.IMap#size()
390          */
391         public int size() {
392             return this.size;
393         }
394         
395     }
396 
397     /**
398      * This class implements an immutable list of the keys or values in an
399      * immutable array map.
400      */
401     public static class ImmutableXList< V > extends AbstractIList< V > {
402 
403         /**
404          * The index of the first element in the array.
405          */
406         private final int firstIndex;
407 
408         /**
409          * The number of elements in the list.
410          */
411         private final int size;
412 
413         /**
414          * The backing store containing a fixed array of alternate name-value
415          * objects.
416          */
417         private final Object[] store;
418 
419         /**
420          * Constructs a new immutable list of the keys or values in an
421          * immutable array maps backing store.
422          *
423          * @param objects   the array containing alternate key-value objects
424          * @param first the index(one based, inclusive) of the first list
425          * element within the array
426          * @param len   the number of elements in this list
427          */
428         public ImmutableXList( final Object[] objects, final int first, final int len ) {
429             if ( len <= 0 ) {
430                 // We keep this test here to avoid problems with the array
431                 // length check below
432                 this.firstIndex = first - 1;
433                 this.size = 0;
434                 this.store = null;
435             } else if ( first < 0 || first > objects.length ) {
436                 throw new ListIndexOutOfBoundsAlert(
437                     "First index in slice must be greater than 0 and less than the length of the array"
438                 ).culprit(
439                     "index",
440                     first
441                 ).decorate( objects ).mishap();
442             } else if ( first + 2 * ( len - 1 ) > objects.length ) {
443                 throw new ListIndexOutOfBoundsAlert(
444                     "Requested list exceeds bounds of specified array"
445                 ).culprit(
446                     "index",
447                     first
448                 ).culprit(
449                     "size",
450                     len
451                 ).decorate( objects ).mishap();
452 //            } else if ( ( first - last ) % 2 != 0 ) {
453 //                throw new IllegalArgumentAlert(
454 //                    "Backing store key-value array length must be a multiple of 2"
455 //                ).culprit(
456 //                    "array",
457 //                    objects
458 //                ).culprit(
459 //                    "array length",
460 //                    objects.length
461 //                ).culprit(
462 //                    "first name index",
463 //                    first
464 //                ).culprit(
465 //                    "last name index",
466 //                    last
467 //                ).mishap();
468             } else {
469                 this.firstIndex = first - 1;
470                 this.size = len;
471                 this.store = objects;
472             }
473         }
474 
475         /**
476          * @see org.millscript.commons.util.list.AbstractIList#doGet(int)
477          */
478         @Override
479         @SuppressWarnings( "unchecked" )
480         protected V doGet( final int pos ) {
481             // NOTE - We subtract two here as pos uses one based indexing, so
482             // we have to subtract one from each occurance. We have to add pos
483             // twice as the array is alternate key-value objects hence for a
484             // key access get( 2 ) means this.store[ 2 ] if firstIndex is zero.
485             return (V) this.store[ this.firstIndex - 2 + pos + pos ];
486         }
487 
488         /**
489          * @see org.millscript.commons.util.list.AbstractIList#doSlice(int, int, boolean)
490          */
491         @Override
492         @SuppressWarnings( "unchecked" )
493         protected IList< V > doSlice( final int first, final int last, final boolean share ) {
494             if ( share ) {
495                 return new ImmutableXList< V >(
496                     this.store,
497                     this.firstIndex + first - 1,
498                     last - first + 1
499                 );
500             } else {
501                 // Return a copy of the relevant slice of the list
502                 final V[] objects = (V[]) new Object[ last - first + 1 ];
503                 for ( int i = this.firstIndex, j = 0; j < last - first + 1; i += 2, j++ ) {
504                     objects[ j ] = (V) this.store[ i ];
505                 }
506                 return new IArrayList< V >( objects, true );
507             }
508         }
509 
510         /**
511          * @see org.millscript.commons.util.IList#indexOf(java.lang.Object)
512          */
513         public int indexOf( final V value ) {
514             for ( int i = this.firstIndex, j = 1; j <= this.size(); i += 2, j++ ) {
515                 if ( value == null ? this.store[ i ] == null : value.equals( this.store[ i ] ) ) {
516                     return j;
517                 }
518             }
519             return 0;
520         }
521 
522         /**
523          * @see org.millscript.commons.util.IMap#iterator(boolean)
524          */
525         @SuppressWarnings( "unchecked" )
526         public ListIterator< V > iterator( boolean share ) {
527             if ( share ) {
528                 // NOTE - The following cast is dubious, but required to allow
529                 // us to call the constructor. Remember, the backing store is
530                 // an array of K, V, K, V, K, V, ...
531                 return new SkippingSharedArrayListIterator< V >(
532                     (V[]) this.store,
533                     this.firstIndex,
534                     this.size,
535                     2
536                 );
537             } else {
538                 final V[] objects = (V[]) new Object[ this.size() ];
539                 for ( int i = this.firstIndex, j = 0; j < this.size; i += 2, j++ ) {
540                     objects[ j ] = (V) this.store[ i ];
541                 }
542                 return new ArrayListIterator< V >( objects, true );
543             }
544         }
545 
546         /**
547          * @see org.millscript.commons.util.IMap#size()
548          */
549         public int size() {
550             return this.size;
551         }
552         
553     }
554 
555     /**
556      * The backing store containing a fixed array of alternate name-value
557      * objects.
558      */
559     private final Object[] store;
560 
561     /**
562      * Constructs a new empty immutable array map. 
563      */
564     public IArrayMap() {
565         this.store = new Object[ 0 ];
566     }
567 
568     /**
569      * Constructs a new immutable array map with a copy of the specified
570      * backing array containing alternate name-value objects.
571      *
572      * @param objects   the backing array of alternate name-value objects to
573      * copy
574      * @param share if <code>true</code> the specified object array will be
575      * shared otherwise the array will be copied.
576      */
577     public IArrayMap( final Object[] objects, final boolean share ) {
578         if ( objects.length % 2 == 1 ) {
579             throw new IllegalArgumentAlert(
580                 "Backing store must be a name, value sequence"
581             ).culprit(
582                 "object array",
583                 objects
584             ).culprit(
585                 "object array length",
586                 objects.length
587             ).mishap();
588         }
589         if ( share ) {
590             this.store = objects;
591         } else {
592             this.store = new Object[ objects.length ];
593             System.arraycopy( objects, 0, this.store, 0, objects.length );
594         }
595     }
596 
597     /**
598      * @see java.lang.Object#clone()
599      */
600     @Override
601     public Object clone() throws CloneNotSupportedException {
602         // Nothing special required for this clone
603         return super.clone();
604     }
605 
606     /**
607      * @see org.millscript.commons.util.IMap#contains(java.lang.Object, java.lang.Object)
608      */
609     public boolean contains( final K key, final V value ) {
610         int i = 0;
611         while ( i < this.store.length - 1 ) {
612             final Object sKey = this.store[ i++ ];
613             final Object sValue = this.store[ i++ ];
614             if ( sKey == null ? key == null : sKey.equals( key ) ) {
615                 // The keys are equal, so return true if the values are both
616                 // null or equal and false otherwise
617                 return sValue == null ? value == null
618                                       : sValue.equals( value );
619             }
620         }
621         return false;
622     }
623 
624     /**
625      * @see org.millscript.commons.util.IMap#containsKey(java.lang.Object)
626      */
627     public boolean containsKey( final K key ) {
628         for ( int i = 0; i < this.store.length; i += 2 ) {
629             if ( key == null ? this.store[ i ] == null : key.equals( this.store[ i ] ) ) {
630                 return true;
631             }
632         }
633         return false;
634     }
635 
636     /**
637      * @see org.millscript.commons.util.IMap#containsValue(java.lang.Object)
638      */
639     public boolean containsValue( final V value ) {
640         for ( int i = 1; i < this.store.length; i += 2 ) {
641             final Object sValue = this.store[ i ];
642             if ( sValue == null ? value == null : sValue.equals( value ) ) {
643                 return true;
644             }
645         }
646         return false;
647     }
648 
649     /**
650      * @see org.millscript.commons.util.IMap#get(java.lang.Object)
651      */
652     @SuppressWarnings( "unchecked" )
653     public V get( final K key ) {
654         for ( int i = 0; i < this.store.length - 1; i += 2 ) {
655             if ( key == null ? this.store[ i ] == null : key.equals( this.store[ i ] ) ) {
656                 return (V) this.store[ ++i ];
657             }
658         }
659         return this.getDefault().get( this, key );
660     }
661 
662     /**
663      * @see org.millscript.commons.util.IMap#iterator(boolean)
664      */
665     public MapIterator< K, V > iterator( boolean share ) {
666         return new ArrayMapMapIterator< K, V >( this.store, share );
667     }
668 
669     /**
670      * @see org.millscript.commons.util.map.AbstractIMap#sharedKeyList()
671      */
672     @Override
673     protected IList< K > sharedKeyList() {
674         return new ImmutableXList< K >( this.store, 1, this.size() );
675     }
676 
677     /**
678      * @see org.millscript.commons.util.map.AbstractIMap#sharedMapletList()
679      */
680     @Override
681     protected IList< Maplet< K, V > > sharedMapletList() {
682         return new ImmutableSharedMapletList< K, V >( this.store, 1, this.size() );
683     }
684 
685     /**
686      * @see org.millscript.commons.util.map.AbstractIMap#sharedValueList()
687      */
688     @Override
689     protected IList< V > sharedValueList() {
690         return new ImmutableXList< V >( this.store, 2, this.size() );
691     }
692 
693     /**
694      * @see org.millscript.commons.util.IMap#size()
695      */
696     public int size() {
697         return this.store.length / 2;
698     }
699 
700 }