1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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
83
84 return ((IMap) obj).get( key );
85 } else if ( obj instanceof Map ) {
86
87 return ((Map)obj).get( key );
88 } else if ( obj instanceof String ) {
89 try {
90
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
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
171 try {
172 return parent.checkVEntry( path );
173 } catch ( VEntryNotFoundAlert a ) {
174
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
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
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
302
303 if ( obj instanceof IMap ) {
304 return ((IMap) obj).iterator( true );
305 } else if ( obj instanceof Map ) {
306
307
308
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
328
329 return ((IMap< ?, ? >) obj).size();
330 } else if ( obj instanceof Collection ) {
331 return ((Collection) obj).size();
332 } else if ( obj instanceof String ) {
333
334 return ((String) obj).length();
335 } else if ( obj instanceof Map ) {
336
337 return ((Map) obj).size();
338 } else if ( obj instanceof XmlElement ) {
339
340 return ((XmlElement) obj).getChildren().length;
341 } else if ( obj instanceof Maplet ) {
342
343 return 2;
344 } else if ( obj instanceof Map.Entry ) {
345
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 }