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.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             // Provided for efficiency.
176             mc.pushArgsMapIterator( ((IMap) obj).iterator( true ) );
177         } else if ( obj instanceof Collection ) {
178             // Provided for efficient handling of any Java Collections based
179             // objects
180             mc.pushArgsIterator( ((Collection)obj).iterator() );
181         } else if ( obj instanceof String ) {
182             //  Provided for efficiency.
183             mc.pushArgsMapIterator(
184                 new IStringList.StringListIterator( (String) obj )
185             );
186         } else if ( obj instanceof XmlElement ) {
187             //  Provided for efficiency.
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                 //  Adjust for negative and 1-indexing.
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                 //  Adjust for negative and 1-indexing.
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                 //  Adjust for negative and 1-indexing.
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             //  Provided for efficiency.
328             return ((IMap) obj).valueList( true ).iterator( true );
329         } else if ( obj instanceof Collection ) {
330             // We don't need an IJavaUtilList so don't create one
331             // FIXME - We need to counter the Java Collections API issues
332             // surrounding concurrent modification at some point
333             return new IJavaUtilList.JavaUtilCollectionListIterator< Object >( (Collection) obj );
334         } else if ( obj instanceof String ) {
335             //  Provided for efficiency.
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         //  Adjust for negative-indexing.
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             // NOTE - It should be safe to share backing store on the slice, as
483             // it would have done when we were using java.util.List's and the
484             // unmake operation will generally construct a brand new object
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 }