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.EList;
26 import org.millscript.commons.util.IList;
27 import org.millscript.commons.util.IMap;
28 import org.millscript.commons.util.ListIterator;
29 import org.millscript.commons.util.MapIterator;
30 import org.millscript.commons.util.UList;
31 import org.millscript.commons.util.alerts.InvalidIndexForListsAlert;
32 import org.millscript.commons.util.alerts.ListIndexOutOfBoundsAlert;
33 import org.millscript.commons.util.list.EArrayList;
34 import org.millscript.commons.util.list.ELinkedList;
35 import org.millscript.commons.util.list.IJavaUtilList;
36 import org.millscript.commons.util.list.IStringList;
37 import org.millscript.millscript.alert.Alerts;
38 import org.millscript.millscript.vm.Machine;
39
40 import java.util.ArrayList;
41 import java.util.Collection;
42 import java.util.Iterator;
43 import java.util.LinkedList;
44 import java.util.List;
45
46 /**
47 * This utility class implements list aware tools for MillScript. List aware
48 * means that these methods unify different datatypes in a list-like way. e.g.
49 * getting a specified item in a list.
50 * <p>
51 * These static methods provide a location to optimize List-like operations for
52 * particular classes. Each one provides a general fallback defined in terms
53 * of <code>ListFactory.make</code>.
54 * </p>
55 */
56 public final class ListAwareTools {
57
58 /**
59 * Performs an add-last operation, adding the specified value to the given
60 * list. This method allows MillScript to work with List implementations
61 * from the MillScript-Util API or the Java Collections API.
62 *
63 * @param obj the object to be resolved as a list
64 * @param val the value to add to the end of the list
65 */
66 @SuppressWarnings( "unchecked" )
67 public static void addLast( final Object obj, final Object val ) {
68 if ( obj instanceof EList ) {
69 ((EList) obj).addLast( val );
70 } else if ( obj instanceof List ) {
71 try {
72 ((List) obj).add( val );
73 } catch ( UnsupportedOperationException ex ) {
74 throw new Alert(
75 "Trying to append an item to an immutable list",
76 "Immutable lists cannot be altered"
77 ).culprit(
78 "item",
79 val
80 ).culprit(
81 "list type",
82 obj == null ? null : obj.getClass()
83 ).mishap();
84 }
85 } else if ( obj instanceof UList ) {
86 throw new Alert(
87 "Trying to append an item to an updateable list",
88 "Updateable lists cannot be structurally changed"
89 ).culprit(
90 "item",
91 val
92 ).culprit(
93 "list type",
94 obj == null ? null : obj.getClass()
95 ).mishap();
96 } else if ( obj instanceof IList ) {
97 throw new Alert(
98 "Trying to append an item to an immutable list",
99 "Immutable lists cannot be altered"
100 ).culprit(
101 "item",
102 val
103 ).culprit(
104 "list type",
105 obj == null ? null : obj.getClass()
106 ).mishap();
107 } else {
108 throw new Alert(
109 "Trying to append an item to an unsupported type",
110 "Only mutable Lists are supported at the moment"
111 ).culprit(
112 "value",
113 val
114 ).culprit(
115 "list type",
116 obj == null ? null : obj.getClass()
117 ).mishap();
118 }
119 }
120
121 /**
122 * Returns a new object that is the result of appending the two specified
123 * objects together.
124 *
125 * @param x the first object
126 * @param y the second object, to append to the first
127 * @return a new object resulting from appending the second object to the
128 * first
129 */
130 public static Object append( final Object x, final Object y ) {
131 if ( x instanceof IList && y instanceof IList ) {
132 final IList< ? > xList = (IList) x;
133 final IList< ? > yList = (IList) y;
134 final EArrayList< Object > list = new EArrayList< Object >( xList.size() + yList.size() );
135 list.append( xList );
136 list.append( yList );
137 return list;
138 } else if ( x instanceof List && y instanceof List ) {
139 final ArrayList< Object > list = new ArrayList< Object >( (List< ? >) x );
140 list.addAll( (List< ? >) y );
141 return list;
142 } else if ( x instanceof String && y instanceof String ) {
143 return ((String) x) + ((String) y);
144 } else if ( ListFactory.sameAs( x, y ) ) {
145 final IList< ? > xList = ListFactory.make( x );
146 final IList< ? > yList = ListFactory.make( y );
147 final EArrayList< Object > list = new EArrayList< Object >( xList.size() + yList.size() );
148 list.append( xList );
149 list.append( yList );
150 return ListFactory.unmake( x, list );
151 } else {
152 throw(
153 Alerts.eval(
154 "Incompatible types for append",
155 "Appending requires types that can interconvert"
156 ).
157 culprit( "arg(1)", x ).
158 culprit( "arg(2)", y ).
159 mishap()
160 );
161 }
162 }
163
164 /**
165 * This method explodes the specified object into the specified machine. If
166 * the object cannot be understood directly, this method will attempt to
167 * construct a new list from the specified object, get an iterator and then
168 * explode it.
169 *
170 * @param obj the object to explode
171 * @param mc the machine to put the pieces of the explosion into
172 */
173 public static void explode( final Object obj, final Machine mc ) {
174 if ( obj instanceof IMap ) {
175
176 mc.pushArgsMapIterator( ((IMap) obj).iterator( true ) );
177 } else if ( obj instanceof Collection ) {
178
179
180 mc.pushArgsIterator( ((Collection)obj).iterator() );
181 } else if ( obj instanceof String ) {
182
183 mc.pushArgsMapIterator(
184 new IStringList.StringListIterator( (String) obj )
185 );
186 } else if ( obj instanceof XmlElement ) {
187
188 mc.pushArgsArray(
189 ((XmlElement)obj).getChildren()
190 );
191 } else {
192 explode( ListFactory.make( obj ), mc );
193 }
194 }
195
196 /**
197 * Returns the item at the specified position, from the specified object.
198 * If the object cannot be understood directly, this method attempts to
199 * contructs a new list from the specified object and then makes an
200 * iterator for that.
201 *
202 * @param obj the object to index as a list
203 * @param i the index
204 * @return the object at the specified index in the target object
205 */
206 public static Object get( final Object obj, final int i ) {
207 if ( obj instanceof IList ) {
208 if ( i > 0 ) {
209 return ((IList) obj).get( i );
210 } else {
211 final IList< ? > list = (IList) obj;
212 return list.get( list.size() + i + 1 );
213 }
214 } else if ( obj instanceof List ) {
215 try {
216
217 final List list = (List)obj;
218 if ( i > 0 ) {
219 return list.get( i - 1 );
220 } else {
221 return list.get( list.size() + i );
222 }
223 } catch ( IndexOutOfBoundsException ex ) {
224 throw new ListIndexOutOfBoundsAlert().culprit(
225 "index",
226 i
227 ).culprit(
228 "list size",
229 ((List) obj).size()
230 ).culprit(
231 "list",
232 obj
233 ).mishap();
234 } catch ( ClassCastException ex ) {
235 throw new InvalidIndexForListsAlert().culprit(
236 "key",
237 i
238 ).culprit(
239 "list size",
240 ((List) obj).size()
241 ).culprit(
242 "list",
243 obj
244 ).mishap();
245 }
246 } else if ( obj instanceof String ) {
247 try {
248
249 final String string = (String)obj;
250 if ( i > 0 ) {
251 return new Character( string.charAt( i - 1 ) );
252 } else {
253 return new Character( string.charAt( string.length() + i ) );
254 }
255 } catch ( IndexOutOfBoundsException ex ) {
256 throw new Alert(
257 "String index out of bounds",
258 "String index must be between 1 and the length of the string"
259 ).culprit(
260 "index",
261 i
262 ).culprit(
263 "string length",
264 ((String)obj).length()
265 ).culprit(
266 "string",
267 obj
268 ).mishap();
269 } catch ( ClassCastException ex ) {
270 throw new Alert(
271 "Invalid index for a string",
272 "Strings may only be indexed by whole numbers"
273 ).culprit(
274 "key",
275 i
276 ).culprit(
277 "string length",
278 ((String)obj).length()
279 ).culprit(
280 "string",
281 obj
282 ).mishap();
283 }
284 } else if ( obj instanceof XmlElement ) {
285 try {
286
287 final XmlElement frag = (XmlElement)obj;
288 if ( i > 0 ) {
289 return frag.get( i - 1 );
290 } else {
291 return frag.get( frag.size() + i );
292 }
293 } catch ( IndexOutOfBoundsException ex ) {
294 throw new Alert(
295 "XML element index out of bounds",
296 "XML element index must be between 1 and the number of its children"
297 ).culprit(
298 "index",
299 i
300 ).culprit(
301 "number of children",
302 ((XmlElement) obj).size()
303 ).culprit(
304 "xml element",
305 obj
306 ).mishap();
307 }
308 } else {
309 return ListFactory.make( obj ).get( i );
310 }
311 }
312
313 /**
314 * Returns a map iterator to iterate over all the entries in the specified
315 * list like object. If the object cannot be understood directly, this
316 * method attempts to contructs a new list from the specified object and
317 * then makes an iterator for that.
318 *
319 * @param obj the list like object to get a map interator for
320 * @return a MapIterator to iterate over the values in the list like
321 * object
322 * @see ListFactory
323 */
324 @SuppressWarnings( "unchecked" )
325 public static MapIterator mapIterator( final Object obj ) {
326 if ( obj instanceof IMap ) {
327
328 return ((IMap) obj).valueList( true ).iterator( true );
329 } else if ( obj instanceof Collection ) {
330
331
332
333 return new IJavaUtilList.JavaUtilCollectionListIterator< Object >( (Collection) obj );
334 } else if ( obj instanceof String ) {
335
336 return new IStringList.StringListIterator( (String) obj );
337 } else {
338 return ListFactory.make( obj ).iterator( true );
339 }
340 }
341
342 /**
343 * Performs a remove-last operation, removing the last value from the given
344 * list. This method allows MillScript to work with List implementations
345 * from the MillScript-Util API or the Java Collections API.
346 *
347 * @param obj the object to be resolved as a list
348 */
349 public static void removeLast( final Object obj ) {
350 if ( obj instanceof EList ) {
351 ((EList< ? >) obj).deleteLast();
352 } else if ( obj instanceof List ) {
353 try {
354 if ( obj instanceof LinkedList ) {
355 ((LinkedList) obj).removeLast();
356 } else {
357 final List list = (List) obj;
358 list.remove( list.size() - 1 );
359 }
360 } catch ( UnsupportedOperationException ex ) {
361 throw new Alert(
362 "Trying to remove an item from an immutable list",
363 "Immutable lists cannot be altered"
364 ).culprit(
365 "list type",
366 obj == null ? null : obj.getClass()
367 ).mishap();
368 }
369 } else if ( obj instanceof UList ) {
370 throw new Alert(
371 "Trying to remove an item from an updateable list",
372 "Updateable lists cannot be structurally changed"
373 ).culprit(
374 "list type",
375 obj == null ? null : obj.getClass()
376 ).mishap();
377 } else if ( obj instanceof IList ) {
378 throw new Alert(
379 "Trying to remove an item from an immutable list",
380 "Immutable lists cannot be altered"
381 ).culprit(
382 "list type",
383 obj == null ? null : obj.getClass()
384 ).mishap();
385 } else {
386 throw new Alert(
387 "Trying to remove an item from an unsupported type",
388 "Only mutable Lists are supported at the moment"
389 ).culprit(
390 "list type",
391 obj == null ? null : obj.getClass()
392 ).mishap();
393 }
394 }
395
396 /**
397 * Returns a list containing the elements from the specified list in
398 * reverse order.
399 *
400 * @param x the list to reverse
401 * @return a List containing the elements from the specified list in
402 * reverse order
403 */
404 private static IList rev( final IList< ? > x ) {
405 final ListIterator< ? > itr = x.iterator( true );
406 final ELinkedList< Object > res = new ELinkedList< Object >();
407 while ( itr.hasNext() ) {
408 res.addFirst( itr.nextValue() );
409 }
410 return res;
411 }
412
413 /**
414 * Returns a list containing the elements from the specified list in
415 * reverse order.
416 *
417 * @param x the list to reverse
418 * @return a List containing the elements from the specified list in
419 * reverse order
420 */
421 private static List rev( final List< ? > x ) {
422 final Iterator< ? > itr = x.iterator();
423 final LinkedList< Object > res = new LinkedList< Object >();
424 while ( itr.hasNext() ) {
425 res.addFirst( itr.next() );
426 }
427 return res;
428 }
429
430 /**
431 * Returns an object containing the elements from the specified object, in
432 * reverse order. e.g. if list is passed, a new list is returned with the
433 * elements in the reverse order. if a string is passed a new string is
434 * returned with the characters in the reverse order.
435 *
436 * @param obj the object to reverse
437 * @return an object containing the elements from the specified object in
438 * reverse order
439 */
440 public static Object reverse( final Object obj ) {
441 if ( obj instanceof IList ) {
442 return rev( (IList) obj );
443 } else if ( obj instanceof List ) {
444 return rev( (List) obj );
445 } else if ( obj instanceof String ) {
446 final String s = (String) obj;
447 final StringBuffer b = new StringBuffer();
448 for ( int i = s.length() - 1; i >= 0; i-- ) {
449 b.append( s.charAt( i ) );
450 }
451 return b.toString();
452 } else {
453 return (
454 ListFactory.unmake(
455 obj,
456 rev( ListFactory.make( obj ) )
457 )
458 );
459 }
460 }
461
462 /**
463 * Returns an object representing the specified subrange of items from the
464 * specified object.
465 *
466 * @param obj the target object to subrange
467 * @param lo the low end of the range(one based, inclusive)
468 * @param hi the high end of the range(one based, inclusive)
469 * @return an object representing the subrange of the target object
470 */
471 public static Object subrange( final Object obj, final int lo, final int hi ) {
472
473 final int start = lo < 0 ? MapAwareTools.size( obj ) + lo + 1 : lo;
474 final int end = hi < 0 ? MapAwareTools.size( obj ) + hi + 1 : hi;
475 if ( obj instanceof IList ) {
476 return ((IList) obj).slice( start, end, false );
477 } else if ( obj instanceof List ) {
478 return ((List)obj).subList( start - 1, end );
479 } else if ( obj instanceof String ) {
480 return ((String)obj).substring( start - 1, end );
481 } else {
482
483
484
485 return (
486 ListFactory.unmake(
487 obj,
488 ListFactory.make( obj ).slice( start, end, true )
489 )
490 );
491 }
492 }
493
494 /**
495 * Hidden constructor.
496 */
497 private ListAwareTools() {
498 }
499
500 }