View Javadoc

1   ////////////////////////////////////////////////////////////////////////////////
2   // MillScript: an Open Spice interpreter and batch website creation tool
3   // Copyright (C) 2001-2004 Open World Ltd
4   // Copyright (C) 2005 Kevin Rogers
5   //
6   // This file is part of MillScript.
7   //
8   // MillScript is free software; you can redistribute it and/or modify it under
9   // the terms of the GNU General Public License as published by the Free
10  // Software Foundation; either version 2 of the License, or (at your option)
11  // any later version.
12  //
13  // MillScript is distributed in the hope that it will be useful, but WITHOUT
14  // ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
15  // FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
16  // more details.
17  //
18  // You should have received a copy of the GNU General Public License along with
19  // MillScript; if not, write to the Free Software Foundation, Inc., 59 Temple
20  // Place, Suite 330, Boston, MA  02111-1307  USA
21  ////////////////////////////////////////////////////////////////////////////////
22  package org.millscript.millscript.datatypes;
23  
24  import org.millscript.commons.alert.Alert;
25  import org.millscript.commons.util.EMap;
26  import org.millscript.commons.util.IList;
27  import org.millscript.commons.util.IMap;
28  import org.millscript.commons.util.MapIterator;
29  import org.millscript.commons.util.Maplet;
30  import org.millscript.commons.util.UList;
31  import org.millscript.commons.util.alerts.ImmutableMapAlert;
32  import org.millscript.commons.util.alerts.InvalidIndexForListsAlert;
33  import org.millscript.commons.util.alerts.InvalidKeyForMapAlert;
34  import org.millscript.commons.util.alerts.ListIndexOutOfBoundsAlert;
35  import org.millscript.commons.util.map.IJavaUtilMap;
36  import org.millscript.commons.vfs.VFolder;
37  import org.millscript.commons.vfs.alerts.VEntryNotFoundAlert;
38  import org.millscript.commons.xml.tokenizer.NoNamespacesName;
39  
40  import java.util.Collection;
41  import java.util.List;
42  import java.util.Map;
43  
44  /**
45   * This utility class implements list aware tools for MillScript. List aware
46   * means that these methods unify different datatypes in a list-like way. e.g.
47   * getting a specified item in a list.
48   * <p>
49   * These static methods provide a location to optimize Map-like operations for
50   * particular classes.  Each one provides a general fallback defined in terms
51   * of <code>MapFactory.make</code>.
52   * </p>
53   */
54  public final class MapAwareTools {
55  
56      /**
57       * Returns the value for the specified key from the specified map like
58       * object.
59       *
60       * @param obj   the map like object to index
61       * @param key   the key to get a value for
62       * @return  the value for the specified key
63       */
64      @SuppressWarnings( "unchecked" )
65      public static Object get( final Object obj, final Object key ) {
66          if ( obj instanceof IList || obj instanceof List ) {
67              if ( key instanceof Integer ) {
68                  return ListAwareTools.get( obj, ((Integer) key).intValue() );
69              } else {
70                  throw new InvalidIndexForListsAlert().culprit(
71                      "key",
72                      key
73                  ).culprit(
74                      "list size",
75                      size( obj )
76                  ).culprit(
77                      "list",
78                      obj
79                  ).mishap();
80              }
81          } else if ( obj instanceof IMap ) {
82              // This handles MillScript-Util Maps and Lists(as a list is a map
83              // we can just carry on without having to do anything special)
84              return ((IMap) obj).get( key );
85          } else if ( obj instanceof Map ) {
86              // This handles java.util.Maps for non MillScript compatibility
87              return ((Map)obj).get( key );
88          } else if ( obj instanceof String ) {
89              try {
90                  //  Adjust for negative and 1-indexing.
91                  final int n = ((Integer)key).intValue();
92                  final String string = (String)obj;
93                  if ( n > 0 ) {
94                      return new Character( string.charAt( n - 1 ) );
95                  } else {
96                      return new Character( string.charAt( string.length() + n ) );
97                  }
98              } catch ( IndexOutOfBoundsException ex ) {
99                  throw new Alert(
100                     "String index out of bounds",
101                     "String index must be between 1 and the length of the string"
102                 ).culprit(
103                     "index",
104                     key
105                 ).culprit(
106                     "string length",
107                     ((String)obj).length()
108                 ).culprit(
109                     "string",
110                     obj
111                 ).mishap();
112             } catch ( ClassCastException ex ) {
113                 throw new Alert(
114                     "Invalid index for a string",
115                     "Strings may only be indexed by whole numbers"
116                 ).culprit(
117                     "key",
118                     key
119                 ).culprit(
120                     "string length",
121                     ((String)obj).length()
122                 ).culprit(
123                     "string",
124                     obj
125                 ).mishap();
126             }
127         } else if ( obj instanceof XmlElement ) {
128             if ( key instanceof Integer ) {
129                 try {
130                     //  Adjust for negative and 1-indexing.
131                     final int n = ((Integer)key).intValue();
132                     final XmlElement frag = (XmlElement)obj;
133                     if ( n > 0 ) {
134                         return frag.get( n - 1 );
135                     } else {
136                         return frag.get( frag.size() + n );
137                     }
138                 } catch ( IndexOutOfBoundsException ex ) {
139                     throw new Alert(
140                         "XML element index out of bounds",
141                         "XML element index must be between 1 and the number of its children"
142                     ).culprit(
143                         "index",
144                         key
145                     ).culprit(
146                         "number of children",
147                         ((XmlElement) obj).size()
148                     ).culprit(
149                         "xml element",
150                         obj
151                     ).mishap();
152                 }
153             } else if ( key instanceof String ) {
154                 return ((XmlElement)obj).getAttributes().get( new NoNamespacesName( (String) key ) );
155             } else {
156                 throw new InvalidKeyForMapAlert(
157                     "XML element index must be a number or string"
158                 ).culprit(
159                     "index",
160                     key
161                 ).culprit(
162                     "xml element",
163                     obj
164                 ).mishap();
165             }
166         } else if ( obj instanceof VFolder ) {
167             if ( key instanceof String ) {
168                 final String path = (String) key;
169                 final VFolder parent = (VFolder) obj;
170                 // "Index" the file by the key
171                 try {
172                     return parent.checkVEntry( path );
173                 } catch ( VEntryNotFoundAlert a ) {
174                     // Ok, there is no entry with that name so just return null
175                     return null;
176                 }
177             } else {
178                 throw new Alert(
179                     "Invalid index for files",
180                     "Files may only be indexed by strings"
181                 ).culprit(
182                     "key",
183                     key
184                 ).culprit(
185                     "file",
186                     obj
187                 ).mishap();
188             }
189         } else {
190             return MapFactory.make( obj ).get( key );
191         }
192     }
193 
194     /**
195      * Inserts a mapping for the specified key and value into the given map
196      * like object.
197      *
198      * @param obj   the map like object to insert a mapping into
199      * @param key   the key to insert
200      * @param val   the value to associate with the key
201      */
202     @SuppressWarnings( "unchecked" )
203     public static void insert( final Object obj, final Object key, final Object val ) {
204         if ( obj instanceof EMap ) {
205             ((EMap) obj).insert( key, val );
206         } else if ( obj instanceof Map ) {
207             ((Map)obj).put( key, val );
208         } else if ( obj instanceof UList ) {
209             try {
210                 // Adjust for 1-indexing.
211                 final int n = ((Integer) key).intValue();
212                 final UList list = (UList) obj;
213                 if ( n > 0 ) {
214                     list.update( n, val );
215                 } else {
216                     list.update( list.size() + n + 1, val );
217                 }
218             } catch ( ClassCastException ex ) {
219                 throw new InvalidIndexForListsAlert().culprit(
220                     "key",
221                     key
222                 ).culprit(
223                     "list size",
224                     ((UList)obj).size()
225                 ).culprit(
226                     "list",
227                     obj
228                 ).mishap();
229             }
230         } else if ( obj instanceof List ) {
231             try {
232                 // Adjust for 1-indexing.
233                 final int n = ((Integer)key).intValue();
234                 final List list = (List)obj;
235                 if ( n > 0 ) {
236                     list.set( n - 1, val );
237                 } else {
238                     list.set( list.size() + n, val );
239                 }
240             } catch ( UnsupportedOperationException ex ) {
241                 throw new ImmutableMapAlert().culprit(
242                     "index",
243                     key
244                 ).culprit(
245                     "list size",
246                     new Integer( ((List)obj).size() )
247                 ).culprit(
248                     "list",
249                     obj
250                 ).mishap();
251             } catch ( IndexOutOfBoundsException ex ) {
252                 throw new ListIndexOutOfBoundsAlert().culprit(
253                     "index",
254                     key
255                 ).culprit(
256                     "list size",
257                     new Integer( ((List)obj).size() )
258                 ).culprit(
259                     "list",
260                     obj
261                 ).mishap();
262             } catch ( ClassCastException ex ) {
263                 throw new InvalidIndexForListsAlert().culprit(
264                     "key",
265                     key
266                 ).culprit(
267                     "list size",
268                     new Integer( ((List)obj).size() )
269                 ).culprit(
270                     "list",
271                     obj
272                 ).mishap();
273             }
274         } else {
275             throw new Alert(
276                 "Trying to update the index of an unsupported type",
277                 "Only mutable Lists and Maps are supported at the moment"
278             ).culprit(
279                 "key",
280                 key
281             ).culprit(
282                 "value",
283                 obj
284             ).culprit(
285                 "type",
286                 obj == null ? null : obj.getClass()
287             ).mishap();
288         }
289     }
290 
291     /**
292      * Returns a map iterator to iterate over all the entries in the specified
293      * map like object.
294      *
295      * @param obj   the map like object to get a map interator for
296      * @return  a MapIterator to iterate over the entries in the map like
297      * object
298      */
299     @SuppressWarnings( "unchecked" )
300     public static MapIterator mapIterator( final Object obj ) {
301         // We might as well share backing store where we can, as most of the
302         // implementations we fall back to copy-on-write if required 
303         if ( obj instanceof IMap ) {
304             return ((IMap) obj).iterator( true );
305         } else if ( obj instanceof Map ) {
306             // We don't need an IJavaUtilMap so don't create one
307             // FIXME - We need to counter the Java Collections API issues
308             // surrounding concurrent modification at some point
309             return new IJavaUtilMap.JavaUtilMapIterator< Object, Object >( (Map) obj );
310         } else {
311             return MapFactory.make( obj ).iterator( true );
312         }
313     }
314 
315     /**
316      * Returns the size of the specified object, seen as a map. If the object
317      * cannot be understood directly, this method attempts to construct a new
318      * map from the specified object and then calculate it's size.
319      *
320      * @param obj   the object to determine the size of
321      * @return  an int for the size of the specified object
322      * @see CollectionFactory
323      */
324     @SuppressWarnings( "unchecked" )
325     public static int size( final Object obj ) {
326         if ( obj instanceof IMap ) {
327             // This handles MillScript-Util Maps and Lists(as a list is a map
328             // we can just carry on without having to do anything special)
329             return ((IMap< ?, ? >) obj).size();
330         } else if ( obj instanceof Collection ) {
331             return ((Collection) obj).size();
332         } else if ( obj instanceof String ) {
333             //  Provided for efficiency.
334             return ((String) obj).length();
335         } else if ( obj instanceof Map ) {
336             //  Provided for efficiency.
337             return ((Map) obj).size();
338         } else if ( obj instanceof XmlElement ) {
339             // Provided for efficiency.
340             return ((XmlElement) obj).getChildren().length;
341         } else if ( obj instanceof Maplet ) {
342             //  Provided for efficiency.
343             return 2;
344         } else if ( obj instanceof Map.Entry ) {
345             //  Provided for efficiency.
346             return 2;
347         } else {
348             return MapFactory.make( obj ).size();
349         }
350     }
351 
352     /**
353      * Hidden constructor.
354      */
355     private MapAwareTools() {
356     }
357 
358 }