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 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.MapIterator;
27  import org.millscript.commons.util.Maplet;
28  import org.millscript.commons.util.iterator.AbstractMapIterator;
29  import org.millscript.commons.util.list.IArrayList;
30  import org.millscript.commons.util.list.IJavaUtilList;
31  import org.millscript.commons.util.maplet.IMaplet;
32  
33  import java.io.Serializable;
34  import java.lang.reflect.InvocationTargetException;
35  import java.lang.reflect.Method;
36  import java.util.Collections;
37  import java.util.HashMap;
38  import java.util.Iterator;
39  import java.util.Map;
40  import java.util.NoSuchElementException;
41  import java.util.Map.Entry;
42  
43  /**
44   * This class provides an immutable <code>Map</code> implementation which is
45   * backed by a java.util.Map instance.
46   */
47  public class IJavaUtilMap< K, V > extends AbstractIMap< K, V > implements Cloneable, Serializable {
48  
49      // TODO - We need to find a good way to avoid running into Java's "if the
50      // map is modified while an iteration is in progress the results are
51      // undefined" yuckiness.
52  
53      /**
54       * This is the ID from the release 0.1.0 for future compatibility.
55       */
56      private static final long serialVersionUID = -8344051250886359924L;
57  
58      /**
59       * This class implements a map iterator which iterates over the mappings in
60       * a {@link Map}.
61       */
62      public static class JavaUtilMapIterator< K, V > extends AbstractMapIterator< K, V > {
63  
64          /**
65           * The current Map.Entry for the current iterator position.
66           */
67          private Entry< K, V > currentMapEntry;
68  
69          /**
70           * The java.util.Iterator we are fetching values from.
71           */
72          private final Iterator< Entry< K, V > > it;
73  
74          /**
75           * Constructs a new array iterator to iterate over the mappings in a
76           * {@link Map}.
77           *
78           * @param map   the {@link Map} whose mappings to iterate over 
79           */
80          public JavaUtilMapIterator( final Map< K, V > map ) {
81              this.it = map.entrySet().iterator();
82          }
83  
84          /**
85           * @see org.millscript.commons.util.iterator.AbstractMapIterator#advance()
86           */
87          @Override
88          protected void advance() {
89              try {
90                  this.currentMapEntry = this.it.next();
91              } catch ( NoSuchElementException ex ) {
92                  // Clear out the previous map entry, in case someone tries to
93                  // continue using the current* methods after the exception
94                  this.currentMapEntry = null;
95              }
96          }
97  
98          /**
99           * @see org.millscript.commons.util.iterator.AbstractMapIterator#getKey()
100          */
101         @Override
102         protected K getKey() {
103             return this.currentMapEntry.getKey();
104         }
105 
106         /**
107          * @see org.millscript.commons.util.iterator.AbstractMapIterator#getValue()
108          */
109         @Override
110         protected V getValue() {
111             return this.currentMapEntry.getValue();
112         }
113 
114         /**
115          * @see org.millscript.commons.util.MapIterator#hasNext()
116          */
117         public boolean hasNext() {
118             return this.it.hasNext();
119         }
120 
121         /**
122          * @see org.millscript.commons.util.iterator.AbstractMapIterator#outOfBounds()
123          */
124         @Override
125         protected boolean outOfBounds() {
126             return this.currentMapEntry == null;
127         }
128 
129     }
130 
131     /**
132      * The backing store java.util.Map.
133      */
134     private final Map< K, V > store;
135 
136     /**
137      * Constructs a new empty immutable java util map. 
138      */
139     @SuppressWarnings( "unchecked" )
140     public IJavaUtilMap() {
141         this.store = Collections.EMPTY_MAP;
142     }
143 
144     /**
145      * Constructs a new immutable java util map with a copy of the specified
146      * backing array containing alternate name-value objects.
147      *
148      * @param objects   the backing array of alternate name-value objects to
149      * copy
150      */
151     @SuppressWarnings( "unchecked" )
152     public IJavaUtilMap( final Map< K, V > map ) {
153         if ( map instanceof Cloneable ) {
154             try {
155                 Class c = map.getClass();
156                 Method m = c.getMethod( "clone" );
157                 this.store = (Map< K, V >) m.invoke( map );
158             } catch ( NoSuchMethodException ex ) {
159                 throw new IllegalArgumentAlert(
160                     "Trying to make an immutable map from a non-cloneable java.util.Map instance"
161                 ).culprit( "list type", map.getClass() ).culprit( "list", map ).mishap();
162             } catch ( IllegalAccessException ex ) {
163                 throw new IllegalArgumentAlert(
164                     "Trying to make an immutable map from a non-cloneable java.util.Map instance"
165                 ).culprit( "list type", map.getClass() ).culprit( "list", map ).mishap();
166             } catch ( InvocationTargetException ex ) {
167                 throw new IllegalArgumentAlert(
168                     "Trying to make an immutable map from a non-cloneable java.util.Map instance"
169                 ).culprit( "list type", map.getClass() ).culprit( "list", map ).mishap();
170             }
171         } else {
172             this.store = new HashMap< K, V >( map.size() );
173             try {
174                 for ( Iterator< Entry< K, V > > it = map.entrySet().iterator(); it.hasNext(); ) {
175                     final Entry< K, V > entry = it.next();
176                     this.store.put( entry.getKey(), entry.getValue() );
177                 }
178             } catch ( Exception ex ) {
179                 throw new Fault(
180                     "Failed to take a copy of the specified list slice"
181                 ).setParentThrowable( ex ).mishap();
182             } 
183         }
184     }
185 
186     /**
187      * @see java.lang.Object#clone()
188      */
189     @Override
190     public Object clone() throws CloneNotSupportedException {
191         // Nothing special required for this clone
192         return super.clone();
193     }
194 
195     /**
196      * @see org.millscript.commons.util.IMap#contains(java.lang.Object, java.lang.Object)
197      */
198     public boolean contains( final K key, final V value ) {
199         if ( this.store.containsKey( key ) ) {
200             // Ok, the backing map contains the key, so does the key's value
201             // match the specified one?
202             return value == null ? this.store.get( key ) == null
203                                  : value.equals( this.store.get( key ) );
204         }
205         return false;
206     }
207 
208     /**
209      * @see org.millscript.commons.util.IMap#containsKey(java.lang.Object)
210      */
211     public boolean containsKey( final K key ) {
212         return this.store.containsKey( key );
213     }
214 
215     /**
216      * @see org.millscript.commons.util.IMap#containsValue(java.lang.Object)
217      */
218     public boolean containsValue( final V value ) {
219         return this.store.containsValue( value );
220     }
221 
222     /**
223      * @see org.millscript.commons.util.IMap#get(java.lang.Object)
224      */
225     public V get( final K key ) {
226         // If the map contains the specified key, return the corresponding
227         // value, otherwise return the default value
228         return this.store.containsKey( key ) ? this.store.get( key )
229                                              : this.getDefault().get( this, key );
230     }
231 
232     /**
233      * @see org.millscript.commons.util.IMap#iterator()
234      */
235     public MapIterator< K, V > iterator() {
236         return new JavaUtilMapIterator< K, V >( this.store );
237     }
238 
239     /**
240      * @see org.millscript.commons.util.IMap#iterator(boolean)
241      */
242     @SuppressWarnings( "unchecked" )
243     public MapIterator< K, V > iterator( final boolean share ) {
244         if ( share ) {
245             return new JavaUtilMapIterator< K, V >( this.store );
246         } else {
247             // I'm not entirely sure how efficient this really is, but it is
248             // definitely more compact than trying to clone the backing map or
249             // copying the maplets into a new hash map...
250             final Maplet< K, V >[] maplets = new Maplet[ this.size() ];
251             final Iterator< Entry< K, V > > it = this.store.entrySet().iterator();
252             for ( int i = 0; it.hasNext(); i++ ) {
253                 Entry< K, V > entry = it.next();
254                 maplets[ i ] = new IMaplet< K, V >(
255                     entry.getKey(),
256                     entry.getValue()
257                 );
258             }
259             return new IMapletArrayMap.MapletArrayMapIterator< K, V >( maplets, true );
260         }
261     }
262 
263     /**
264      * @see org.millscript.commons.util.map.AbstractIMap#sharedKeyList()
265      */
266     @Override
267     protected IList< K > sharedKeyList() {
268         // FIXME - Implement a better version of this?
269         return new IJavaUtilList< K >( this.store.keySet() );
270     }
271 
272     /**
273      * @see org.millscript.commons.util.map.AbstractIMap#sharedMapletList()
274      */
275     @Override
276     @SuppressWarnings( "unchecked" )
277     protected IList< Maplet< K, V > > sharedMapletList() {
278         // FIXME - Implement a better version of this?
279         final Iterator< Entry< K, V > > it = this.store.entrySet().iterator();
280         final Maplet< K, V >[] maplets = new Maplet[ this.size() ];
281         for ( int i = 0; it.hasNext(); i++ ) {
282             Entry< K, V > entry = it.next();
283             maplets[ i ] = new IMaplet< K, V >( entry );
284         }
285         return new IArrayList< Maplet< K, V > >( maplets, true );
286     }
287 
288     /**
289      * @see org.millscript.commons.util.map.AbstractIMap#sharedValueList()
290      */
291     @Override
292     protected IList< V > sharedValueList() {
293         // FIXME - Implement a better version of this?
294         return new IJavaUtilList< V >( this.store.values() );
295     }
296 
297     /**
298      * @see org.millscript.commons.util.IMap#size()
299      */
300     public int size() {
301         return this.store.size();
302     }
303 
304 }