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.IOException;
24  import java.io.ObjectInputStream;
25  import java.io.ObjectOutputStream;
26  import java.io.Serializable;
27  
28  import org.millscript.commons.alert.alerts.IllegalArgumentAlert;
29  import org.millscript.commons.util.IList;
30  import org.millscript.commons.util.IMap;
31  import org.millscript.commons.util.MapIterator;
32  import org.millscript.commons.util.Maplet;
33  import org.millscript.commons.util.alerts.NoSuchKeyInMapAlert;
34  import org.millscript.commons.util.iterator.AbstractMapIterator;
35  
36  /**
37   * This class provides a mutable <code>Map</code> implementation which is
38   * backed by an Object array containing alternate name-value objects.
39   */
40  public class EArrayMap< K, V > extends AbstractEMap< K, V > implements Cloneable, Serializable {
41  
42      // TODO - Complete the concurrency work, i.e. get the synchroised bits in
43  
44      /**
45       * This is the ID from the release 0.1.0 for future compatibility.
46       */
47      private static final long serialVersionUID = -4589591352463632844L;
48  
49      /**
50       * This class implements a map iterator which iterates over an array
51       * containing alternating key-value objects, sharing the backing store with
52       * it's enclosing mutable map.
53       * <p>
54       * As expected, the iterator uses a copy-on-write contract with its
55       * enclosing class during the iteration. Simple updates will not cause a
56       * copy, but structural(i.e. inserts/removes) will cause a copy for the
57       * duration of the iteration.
58       * </p>
59       */
60      public static class ArrayMapMapIterator< K, V > extends AbstractMapIterator< K, V > {
61  
62          /**
63           * The next free element in the backing store at the time the iterator
64           * was created. We have to copy the value into this iterator to avoid
65           * problems with updates to the map during an interation.
66           */
67          private final int nextFree;
68  
69          /**
70           * The current position within the backing store array.
71           */
72          private int pos = -2;
73  
74          /**
75           * The backing store containing a variable array of alternate name-value
76           * objects. We copy the reference into this iterator to avoid problems
77           * with updated to the map during an iteration.
78           */
79          private final Object[] store;
80  
81          /**
82           * Contructs a new array map iterator for the specified mutable array
83           * map.
84           *
85           * @param map   the mutable array map to iterate over
86           */
87          public ArrayMapMapIterator( final EArrayMap< K, V > map ) {
88              this.nextFree = map.nextFree;
89              this.store = map.store;
90          }
91  
92          /**
93           * @see org.millscript.commons.util.iterator.AbstractMapIterator#advance()
94           */
95          @Override
96          protected void advance() {
97              this.pos += 2;
98          }
99  
100         /**
101          * @see org.millscript.commons.util.iterator.AbstractMapIterator#getKey()
102          */
103         @Override
104         @SuppressWarnings("unchecked")
105         protected K getKey() {
106             return (K) store[ this.pos ];
107         }
108 
109         /**
110          * @see org.millscript.commons.util.iterator.AbstractMapIterator#getValue()
111          */
112         @Override
113         @SuppressWarnings("unchecked")
114         protected V getValue() {
115             return (V) this.store[ this.pos + 1 ];
116         }
117 
118         /**
119          * @see org.millscript.commons.util.MapIterator#hasNext()
120          */
121         public boolean hasNext() {
122             return this.pos + 2 < this.nextFree;
123         }
124 
125         /**
126          * @see org.millscript.commons.util.iterator.AbstractMapIterator#outOfBounds()
127          */
128         @Override
129         protected boolean outOfBounds() {
130             return this.pos < 0 || this.pos >= this.nextFree;
131         }
132 
133     }
134 
135     /**
136      * The default amount of free space available in the map is enough for 16
137      * name-value pairs. This value must be a multiple of two otherwise array
138      * index out of bounds errors may occur.
139      */
140     static final byte DEFAULT_FREE_SPACE = 32;
141 
142     // FIXME - The should ideally be private
143     /**
144      * The position of the next free element in the backing store and also the
145      * twice the number of name-value pairs in this map.
146      */
147     transient int nextFree = 0;
148 
149     // FIXME - The store should ideally be private
150     /**
151      * The backing store containing a variable array of alternate name-value
152      * objects.
153      */
154     transient Object[] store;
155 
156     /**
157      * Constructs a new, emtpy mutable array map.
158      */
159     public EArrayMap() {
160         this.store = new Object[ DEFAULT_FREE_SPACE ];
161     }
162 
163     /**
164      * Constructs a new mutable array map with a copy of the specified backing
165      * array containing alternate name-value objects.
166      *
167      * @param objects   the backing array of alternate name-value objects to
168      * copy
169      */
170     public EArrayMap( final Object[] objects ) {
171         if ( objects.length % 2 == 1 ) {
172             throw new IllegalArgumentAlert(
173                 "Backing store must be a name, value sequence"
174             ).culprit(
175                 "object array",
176                 objects
177             ).culprit(
178                 "object array length",
179                 objects.length
180             ).mishap();
181         }
182         this.nextFree = objects.length;
183         this.store = new Object[ objects.length + DEFAULT_FREE_SPACE ];
184         System.arraycopy( objects, 0, this.store, 0, objects.length );
185     }
186 
187     /**
188      * @see java.lang.Object#clone()
189      */
190     @Override
191     @SuppressWarnings( "unchecked" )
192     public Object clone() throws CloneNotSupportedException {
193         final EArrayMap< K, V > map = (EArrayMap< K, V >) super.clone();
194         map.store = (V[]) new Object[ this.store.length ];
195         System.arraycopy( this.store, 0, map.store, 0, this.store.length );
196         return map;
197     }
198 
199     /**
200      * @see org.millscript.commons.util.IMap#contains(java.lang.Object, java.lang.Object)
201      */
202     public boolean contains( final K key, final V value ) {
203         int i = 0;
204         while ( i < this.nextFree - 1 ) {
205             final Object sKey = this.store[ i++ ];
206             final Object sValue = this.store[ i++ ];
207             if ( sKey == null ? key == null : sKey.equals( key ) ) {
208                 // The keys are equal, so return true if the values are both
209                 // null or equal and false otherwise
210                 return sValue == null ? value == null
211                                       : sValue.equals( value );
212             }
213         }
214         return false;
215     }
216 
217     /**
218      * @see org.millscript.commons.util.IMap#containsKey(java.lang.Object)
219      */
220     public boolean containsKey( final K key ) {
221         for ( int i = 0; i < this.nextFree; i += 2 ) {
222             if ( key == null ? this.store[ i ] == null : key.equals( this.store[ i ] ) ) {
223                 return true;
224             }
225         }
226         return false;
227     }
228 
229     /**
230      * @see org.millscript.commons.util.IMap#containsValue(java.lang.Object)
231      */
232     public boolean containsValue( final V value ) {
233         for ( int i = 1; i < this.nextFree; i += 2 ) {
234             final Object sValue = this.store[ i ];
235             if ( sValue == null ? value == null : sValue.equals( value ) ) {
236                 return true;
237             }
238         }
239         return false;
240     }
241 
242     /**
243      * @see org.millscript.commons.util.IMap#get(java.lang.Object)
244      */
245     @SuppressWarnings( "unchecked" )
246     public V get( final K key ) {
247         for ( int i = 0; i < this.nextFree - 1; i += 2 ) {
248             if ( key == null ? this.store[ i ] == null : key.equals( this.store[ i ] ) ) {
249                 return (V) this.store[ ++i ];
250             }
251         }
252         return this.getDefault().get( this, key );
253     }
254 
255     /**
256      * @see org.millscript.commons.util.EMap#insert(java.lang.Object, java.lang.Object)
257      */
258     public void insert( final K key, final V value ) {
259         // Look to see if the key is already present
260         for ( int i = 0; i < this.nextFree - 1; i += 2 ) {
261             if ( key == null ? this.store[ i ] == null : key.equals( this.store[ i ] ) ) {
262                 this.store[ ++i ] = value;
263                 return;
264             }
265         }
266         // Nope, the key isn't already here, we have to add it
267         // Is there enough space in the backing store?
268         if ( this.nextFree == this.store.length ) {
269             // No space left, we need to resize
270             final Object[] newStore = new Object[ this.store.length + DEFAULT_FREE_SPACE ];
271             System.arraycopy( this.store, 0, newStore, 0, this.store.length );
272             this.store = newStore;
273         }
274         // Now append the new mapping
275         this.store[ this.nextFree++ ] = key;
276         this.store[ this.nextFree++ ] = value;
277     }
278 
279     /**
280      * @see org.millscript.commons.util.EMap#insertAll(org.millscript.commons.util.IMap)
281      */
282     @Override
283     public void insertAll( final IMap< ? extends K, ? extends V > map ) {
284         // For speed, check if there is enough free space for all the mappings.
285         // NOTE - we perform a cursory check on the number of mappings in the
286         // supplied map and no more. We can't be sure how many keys in the
287         // supplied map are already present in this one and hence if we are
288         // going to start wasting space.
289         if ( map.size() > this.store.length ) {
290             // Not enough space left, we need to resize. Make the new store a
291             // little bit bigger than the supplied map.
292             final Object[] newStore = new Object[ map.size() + DEFAULT_FREE_SPACE ];
293             System.arraycopy( this.store, 0, newStore, 0, this.nextFree );
294             this.store = newStore;
295         }
296         // Now insert all the mappings.
297         super.insertAll( map );
298     }
299 
300     /**
301      * @see org.millscript.commons.util.IMap#iterator(boolean)
302      */
303     public MapIterator< K, V > iterator( final boolean share ) {
304         if ( share ) {
305             return new ArrayMapMapIterator< K, V >( this );
306         } else {
307             // FIXME - This bit probably needs to be synchronised to avoid
308             // structural updates during the array creation and copy, not the
309             // iterator construction though
310             final Object[] objects = new Object[ this.nextFree ];
311             System.arraycopy( this.store, 0, objects, 0, objects.length );
312             return new IArrayMap.ArrayMapMapIterator< K, V >( objects, true );
313         }
314     }
315 
316     /**
317      * Reads this object from the specified {@link ObjectInputStream}.
318      *
319      * @param stream    the {@link ObjectInputStream} to read the objects data
320      * from
321      * @serialData      Reads the default object, followed by the integer
322      * number of mappings and then an alternating sequence of key-value pairs
323      * @throws ClassNotFoundException   if a required class cannot be found
324      * @throws IOException      if something goes wrong during the
325      * deserialization process
326      */
327     private void readObject( final ObjectInputStream stream ) throws ClassNotFoundException, IOException {
328         // Write the default stuff
329         stream.defaultReadObject();
330         // Retrieve the number of mappings in the array.
331         final int size = stream.readInt();
332         // Allocate the required amount of storage.
333         this.store = new Object[ ( size * 2 ) + DEFAULT_FREE_SPACE ];
334         // Retrieve the mappings
335         for ( this.nextFree = 0; this.nextFree < size * 2; this.nextFree++ ) {
336             this.store[ this.nextFree ] = stream.readObject();
337         }
338     }
339 
340     /**
341      * @see org.millscript.commons.util.EMap#remove(java.lang.Object, java.lang.Object)
342      */
343     public void remove( final K key, final V value ) {
344         // Find the key-value pair
345         for ( int i = 0; i < this.nextFree - 1; i += 2 ) {
346             if ( key == null ? this.store[ i ] == null : key.equals( this.store[ i ] ) ) {
347                 // The keys are equal, are the values?
348                 if ( value == null ? this.store[ i + 1 ] == null : value.equals( this.store[ i + 1 ] ) ) {
349                     // The keys are equal and so are the values. We should
350                     // remove this pair
351                     // Whatever we do, we have to reduce the used portion of
352                     // the backing store by two.
353                     this.nextFree -= 2;
354                     // Now, if the new used size is the same as the counter we
355                     // don't have to do anything else, otherwise we have to
356                     // shift the end of the backing store by two places.
357                     if ( i != this.nextFree ) {
358                         System.arraycopy( this.store, i + 2, this.store, i, this.nextFree - i );
359                     }
360                 }
361             }
362         }
363     }
364 
365     /**
366      * @see org.millscript.commons.util.EMap#removeAll()
367      */
368     public void removeAll() {
369         this.nextFree = 0;
370         this.store = new Object[ DEFAULT_FREE_SPACE ];
371     }
372 
373     /**
374      * @see org.millscript.commons.util.EMap#removeKey(java.lang.Object)
375      */
376     public void removeKey( final K key ) {
377         // Find the key
378         for ( int i = 0; i < this.nextFree - 1; i += 2 ) {
379             if ( key == null ? this.store[ i ] == null : key.equals( this.store[ i ] ) ) {
380                 // The keys are equal, we should remove this pair
381                 // Whatever we do, we have to reduce the used portion of
382                 // the backing store by two.
383                 this.nextFree -= 2;
384                 // Now, if the new used size is the same as the counter we
385                 // don't have to do anything else, otherwise we have to
386                 // shift the end of the backing store by two places.
387                 if ( i != this.nextFree ) {
388                     System.arraycopy( this.store, i + 2, this.store, i, this.nextFree - i );
389                     // Nothing more to do now as there are duplicate keys are
390                     // not allowed
391                     return;
392                 }
393             }
394         }
395     }
396 
397     /**
398      * @see org.millscript.commons.util.EMap#removeValue(java.lang.Object)
399      */
400     public void removeValue( final V value ) {
401         // Find the value
402         int i = 1;
403         while ( i < this.nextFree ) {
404             if ( value == null ? this.store[ i ] == null : value.equals( this.store[ i ] ) ) {
405                 // The keys are equal, we should remove this pair
406                 // Now, if the new used size is the same as the key position we
407                 // don't have to do anything else, otherwise we have to
408                 // shift the end of the backing store by two places.
409                 if ( i + 1 != this.nextFree ) {
410                     System.arraycopy( this.store, i + 1, this.store, i - 1, this.nextFree - i - 1 );
411                 }
412                 // Reduce the used portion of the backing store by two.
413                 this.nextFree -= 2;
414             } else {
415                 // We only increment the position if we didn't match values,
416                 // otherwise we will have shifted the array values left two
417                 // places which achieves the same result as incrementing the
418                 // position
419                 i += 2;
420             }
421         }
422     }
423 
424     /**
425      * @see org.millscript.commons.util.map.AbstractIMap#sharedKeyList()
426      */
427     @Override
428     protected IList< K > sharedKeyList() {
429         // NOTE - We use the same type of list as for the shared value list but
430         // ofset it by one in the array, so we get a list of the keys!
431         return new IArrayMap.ImmutableXList< K >( this.store, 1, this.size() );
432     }
433 
434     /**
435      * @see org.millscript.commons.util.map.AbstractIMap#sharedMapletList()
436      */
437     @Override
438     protected IList< Maplet< K, V > > sharedMapletList() {
439         return new IArrayMap.ImmutableSharedMapletList< K, V >( this.store, 1, this.size() );
440     }
441 
442     /**
443      * @see org.millscript.commons.util.map.AbstractIMap#sharedValueList()
444      */
445     @Override
446     protected IList< V > sharedValueList() {
447         // NOTE - We use the same type of list as for the shared key list but
448         // ofset it by one in the array, so we get a list of the values!
449         return new IArrayMap.ImmutableXList< V >( this.store, 2, this.size() );
450     }
451 
452     /**
453      * @see org.millscript.commons.util.IMap#size()
454      */
455     public int size() {
456         return this.nextFree / 2;
457     }
458 
459     /**
460      * @see org.millscript.commons.util.UMap#update(java.lang.Object, java.lang.Object)
461      */
462     public void update( final K key, final V value ) {
463         // Look to see if the key is already present
464         for ( int i = 0; i < this.nextFree - 1; i += 2 ) {
465             if ( key == null ? this.store[ i ] == null : key.equals( this.store[ i ] ) ) {
466                 this.store[ ++i ] = value;
467                 return;
468             }
469         }
470         // Error
471         throw new NoSuchKeyInMapAlert(
472             "Only pre-existing keys can have their values updated"
473         ).culpritKey( key ).decorate( this ).mishap();
474     }
475 
476     /**
477      * Writes this object to the specified {@link ObjectOutputStream}.
478      *
479      * @param stream    the {@link ObjectOutputStream} to write this object to
480      * @serialData      Writes the default object, followed by the integer
481      * number of mappings and then an alternating sequence of key-value pairs
482      * @throws IOException      if something goes wrong during the
483      * serialization process
484      */
485     private void writeObject( final ObjectOutputStream stream ) throws IOException {
486         // Write the default stuff
487         stream.defaultWriteObject();
488         // Save the number of mappings in the array.
489         stream.writeInt( this.size() );
490         // Save the mappings in the array
491         for ( int i = 0; i < this.nextFree; i++ ) {
492             stream.writeObject( this.store[ i ] );
493         }
494     }
495 
496 }