1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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
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
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
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
209
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
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
267
268 if ( this.nextFree == this.store.length ) {
269
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
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
285
286
287
288
289 if ( map.size() > this.store.length ) {
290
291
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
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
308
309
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
329 stream.defaultReadObject();
330
331 final int size = stream.readInt();
332
333 this.store = new Object[ ( size * 2 ) + DEFAULT_FREE_SPACE ];
334
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
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
348 if ( value == null ? this.store[ i + 1 ] == null : value.equals( this.store[ i + 1 ] ) ) {
349
350
351
352
353 this.nextFree -= 2;
354
355
356
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
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
381
382
383 this.nextFree -= 2;
384
385
386
387 if ( i != this.nextFree ) {
388 System.arraycopy( this.store, i + 2, this.store, i, this.nextFree - i );
389
390
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
402 int i = 1;
403 while ( i < this.nextFree ) {
404 if ( value == null ? this.store[ i ] == null : value.equals( this.store[ i ] ) ) {
405
406
407
408
409 if ( i + 1 != this.nextFree ) {
410 System.arraycopy( this.store, i + 1, this.store, i - 1, this.nextFree - i - 1 );
411 }
412
413 this.nextFree -= 2;
414 } else {
415
416
417
418
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
430
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
448
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
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
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
487 stream.defaultWriteObject();
488
489 stream.writeInt( this.size() );
490
491 for ( int i = 0; i < this.nextFree; i++ ) {
492 stream.writeObject( this.store[ i ] );
493 }
494 }
495
496 }