/* Wotonomy: OpenStep design patterns for pure Java applications. Copyright (C) 2000 Blacksmith, Inc. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, see http://www.gnu.org */ package net.wotonomy.foundation; import java.io.Serializable; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.Enumeration; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.ListIterator; /** * NSArray is an unmodifiable List. * Calling the mutator methods of the List interface (add, addAll, * set, etc.) on an instance of NSArray will throw an Unsupported * Operation exception: use a NSMutableArray instead. This is to * simulate Objective-C's pattern of exposing mutator methods only * on mutable subinterface, which is wonderful for communicating * via interface the contract on returned collections (whether you * may modify return values) as well as implementing array faults. * * @author michael@mpowers.net * @author $Author: cgruber $ * @version $Revision: 929 $ */ public class NSArray implements List, Serializable { /** * Actual list that backs this instance. */ List list; /** * Return value when array index is not found. */ public static final int NotFound = -1; /** * A constant representing an empty array. */ public static final NSArray EmptyArray = new NSArray(); /** * Returns an NSArray backed by the specified List. * This is useful to "protect" an internal representation * that is returned by a method of return type NSArray. */ public static NSArray arrayBackedByList( List aList ) { return new NSArray( aList, null ); } /** * A constructor that uses the provided list as the backing * list object. This is unlike ArrayList and other * java.util.Collection types insofar as that API requires * that the provided Collection be copied into the newly constructed * Collection. * * TODO: See if this signature can be reasonably changed, as having a no-op parameter is a little counter-intuitive. * @param aList A list that the caller wishes to become the backing list for the NSArray. * @param ignored This parameter is entirely ignored, and is only there to distinguish the API. */ NSArray( List aList, Object ignored ) // differentiates { list = aList; } /** * Constructor with a size hint, used by NSMutableArray. */ NSArray( int aSize ) { list = new ArrayList( aSize ); } /** * Default constructor returns an empty array. */ public NSArray () { list = new ArrayList(); } /** * Produces an array containing only the specified object. */ public NSArray (Object anObject) { this(); list.add( anObject ); } /** * Produces an array containing the specified objects. */ public NSArray (Object[] anArray) { this(); for ( int i = 0; i < anArray.length; i++ ) { list.add( anArray[i] ); } } /** * Produces an array containing the objects in the specified collection. */ public NSArray (Collection aCollection) { this(); Iterator i = aCollection.iterator(); while ( i.hasNext() ) list.add( i.next() ); } /** * Returns the number of items in this array. */ public int count () { return list.size(); } /** * Returns an array containing all objects in this array * plus the specified object. */ public NSArray arrayByAddingObject (Object anObject) { NSArray result = new NSArray( this ); result.protectedAdd( anObject ); return result; } /** * Returns an array containing all objects in this array * plus all objects in the specified list. */ public NSArray arrayByAddingObjectsFromArray (Collection aCollection) { NSArray result = new NSArray( this ); result.protectedAddAll( aCollection ); return result; } /** * Returns a string containing the string representations of * each element in this array, with each element separated from * each neighboring element by the specified string. */ public String componentsJoinedByString (String separator) { StringBuffer buf = new StringBuffer(); Iterator it = list.iterator(); if ( it.hasNext() ) { buf.append( it.next().toString() ); } while ( it.hasNext() ) { buf.append( separator ); buf.append( String.valueOf(it.next()) ); } return buf.toString(); } /** * Returns whether an equivalent object is contained in this array. */ public boolean containsObject (Object anObject) { return list.contains( anObject ); } /** * Returns the first object in this array that is equivalent to * an object in the specified list, or null if no objects are * in common. */ public Object firstObjectCommonWithArray (Collection aCollection) { if ( aCollection == null ) return null; Object o; Iterator it = list.iterator(); while ( it.hasNext() ) { o = it.next(); if ( aCollection.contains( o ) ) return o; } return null; } /** * Returns whether the specified list contains elements equivalent * to those in this array in the same order. */ public boolean isEqualToArray (List aList) { return list.equals( aList ); } /** * Returns the last object in this array, or null if the array is empty. */ public Object lastObject () { int i; if ( (i = list.size()) == 0 ) return null; return list.get( i - 1 ); } /** * */ /* public NSArray sortedArrayUsingSelector (NSSelector); */ /** * Returns an array comprised of only those elements whose * indices fall within the specified range. */ public NSArray subarrayWithRange (NSRange aRange) { //TODO: Test this logic. NSArray result = new NSArray(); if ( aRange == null ) return result; int loc = aRange.location(); int max = aRange.maxRange(); int count = count(); for ( int i = loc; i <= max && i < count; i++ ) { result.protectedAdd( list.get( i ) ); } return result; } /** * Returns an enumeration over the the elements of the array. */ public Enumeration objectEnumerator () { //TODO: Test this logic. return new Enumeration() { Iterator it = NSArray.this.iterator(); public boolean hasMoreElements() { return it.hasNext(); } public Object nextElement() { return it.next(); } }; } /** * Returns an enumeration over the elements of the array in reverse order. */ public java.util.Enumeration reverseObjectEnumerator () { return new java.util.Enumeration() { ListIterator it = null; public ListIterator getIterator() { if ( it == null ) { it = NSArray.this.listIterator(); // zoom to end while ( it.hasNext() ) it.next(); } return it; } public boolean hasMoreElements() { return getIterator().hasPrevious(); } public Object nextElement() { return getIterator().previous(); } }; } /** * Copies the elements of this array into the specified object array * as the array's capacity permits. */ public void getObjects (Object[] anArray) { getObjects(anArray,null); } /** * Copies the elements of this array that fall within the specified range * into the specified object array as the array's capacity permits. This * method must not overflow, even in the face of a null range, an over or * under-sized array, or a bad range. It may underflow and fail to * entirely populate the array, if the array is larger than the data to * be copied. * * TODO: Check whether in WebObjects the range supposed to be measured against the parameter or the NSArray itself??? -ceg * * @param anArray An object array to be filled by this method. * @param range An NSRange object representing the range of data in the NSArray to be copied. */ public void getObjects (Object[] array, NSRange range) { if ( array == null ) return; if ( range == null) range = new NSRange(0,array.length); int limit = Math.min(Math.min(array.length,range.length()),(count()-range.location())); for ( int i = 0; i < limit ; i++ ) { //anArray[ i-aRange.location() ] = objectAtIndex( i ); array[ i ] = objectAtIndex( range.location() + i ); } } /** * Returns the index of the first object in the array equivalent * to the specified object. Returns NotFound if the item is not found. */ public int indexOfObject (Object anObject) { int result = list.indexOf( anObject ); if ( result == -1 ) return NotFound; // in case this changes return result; } /** * Returns the index of the first object in the array * within the specified range equivalent to the specified object. * Returns NotFound if the item is not found. */ public int indexOfObject (Object anObject, NSRange aRange) { if ( ( anObject == null ) || ( aRange == null ) ) return NotFound; int loc = aRange.location(); int max = aRange.maxRange(); for ( int i = loc; i < max; i++ ) { if ( anObject.equals( list.get(i) ) ) { return i; } } return NotFound; } /** * Returns the index of the specified object if it exists * in the array, comparing by reference. * Returns NotFound if the item is not found. */ public int indexOfIdenticalObject (Object anObject) { int size = list.size(); for ( int i = 0; i < size; i++ ) { if ( anObject == list.get(i) ) { return i; } } return NotFound; } /** * Returns the index of the first object in the array * within the specified range equivalent to the specified object. */ public int indexOfIdenticalObject (Object anObject, NSRange aRange) { if ( aRange == null ) return NotFound; int loc = aRange.location(); int max = aRange.maxRange(); for ( int i = loc; i < max; i++ ) { if ( anObject == list.get(i) ) { return i; } } return NotFound; } /** * Returns the object at the specified index. Throws an * IndexOutOfRange exception if the index is out of range. */ public Object objectAtIndex (int anIndex) { return list.get( anIndex ); } /** * Returns an array consisting of strings within the specified string * as delimited by the specified separator characters. */ public static NSArray componentsSeparatedByString (String aString, String aSeparator) { NSArray result = new NSArray(); if ( aString == null ) return result; if ( aSeparator == null ) return new NSArray( aString ); //FIXME: The spec probably considers the whole // string as a separator, unlike string tokenizer. java.util.StringTokenizer tokens = new java.util.StringTokenizer( aString, aSeparator ); while ( tokens.hasMoreTokens() ) { result.protectedAdd( tokens.nextToken() ); } return result; } public Object clone() { return new NSArray( list ); } public NSArray immutableClone() { return this; } public NSMutableArray mutableClone() { return new NSMutableArray( this ); } public String toString() { StringBuffer buf = new StringBuffer(); buf.append(NSPropertyListSerialization.TOKEN_BEGIN[NSPropertyListSerialization.PLIST_ARRAY]); for (int i = 0; i < count(); i++) { Object x = objectAtIndex(i); buf.append(NSPropertyListSerialization.stringForPropertyList(x)); if (i < count() - 1) buf.append(", "); } buf.append(NSPropertyListSerialization.TOKEN_END[NSPropertyListSerialization.PLIST_ARRAY]); return buf.toString(); } // interface List: accessors public boolean contains(Object o) { return list.contains(o); } public boolean containsAll(Collection c) { return list.containsAll(c); } public boolean equals(Object o) { return list.equals(o); } public Object get(int index) { return list.get(index); } public int hashCode() { int code = 19; code *= getClass().hashCode(); code *= list.hashCode(); return code; } public int indexOf(Object o) { return list.indexOf(o); } public boolean isEmpty() { return list.isEmpty(); } public int lastIndexOf(Object o) { return list.lastIndexOf(o); } public int size() { return list.size(); } public Object[] toArray() { return list.toArray(); } public Object[] toArray(Object[] a) { return list.toArray(a); } // interface List: mutators public void add(int index, Object element) { this.list.add(index,element); } public boolean add(Object o) { return this.list.add(o); } public boolean addAll(Collection coll) { return this.list.addAll(coll); } public boolean addAll(int index, Collection c) { return this.list.addAll(index,c); } public void clear() { this.list.clear(); } public Iterator iterator() { // make a copy to avoid ConcurrentModificationExceptions final Iterator i = new LinkedList( list ).iterator(); return new Iterator() { public boolean hasNext() {return i.hasNext();} public Object next() {return i.next();} public void remove() { throw new UnsupportedOperationException(); } }; } public ListIterator listIterator() { return listIterator(0); } public ListIterator listIterator(final int index) { // make a copy to avoid ConcurrentModificationExceptions final ListIterator i = new LinkedList( list ).listIterator(index); return new ListIterator() { public boolean hasNext() {return i.hasNext();} public Object next() {return i.next();} public boolean hasPrevious() {return i.hasPrevious();} public Object previous() {return i.previous();} public int nextIndex() {return i.nextIndex();} public int previousIndex() {return i.previousIndex();} public void remove() { throw new UnsupportedOperationException(); } public void set(Object o) { throw new UnsupportedOperationException(); } public void add(Object o) { throw new UnsupportedOperationException(); } }; } public Object remove(int index) { return this.list.remove(index); } public boolean remove(Object o) { return this.list.remove(o); } public boolean removeAll(Collection coll) { return this.list.removeAll(coll); } public boolean retainAll(Collection coll) { return this.list.retainAll(coll); } public Object set(int index, Object element) { return this.list.set(index,element); } public List subList(int fromIndex, int toIndex) { return Collections.unmodifiableList(list.subList(fromIndex, toIndex)); } /** * Provided for the use of subclasses like ArrayFault. */ protected boolean protectedAdd( Object o ) { return list.add( o ); } /** * Provided for the use of subclasses like ArrayFault. */ protected boolean protectedAddAll( Collection coll ) { return list.addAll( coll ); } } /* * $Log$ * Revision 1.2 2006/03/10 00:52:27 cgruber * Add tests for NSArray and fix some problems that became obvious as a result. * * Revision 1.1 2006/02/16 12:47:16 cgruber * Check in all sources in eclipse-friendly maven-enabled packages. * * Revision 1.16 2005/07/13 14:12:44 cgruber * Add mutableClone() and immutableClone() per. WebObjects 5.3 conformance. * * Revision 1.15 2003/08/06 23:07:52 chochos * general code cleanup (mostly, removing unused imports) * * Revision 1.14 2003/08/05 00:48:56 chochos * use NSPropertyListSerialization to get the opening and closing tokens for the string representation * * Revision 1.13 2003/08/04 20:26:10 chochos * use NSPropertyListSerialization inside toString() * * Revision 1.12 2003/08/04 18:18:43 chochos * toString() yields strings in the same format as Apple's NSArray * * Revision 1.11 2003/01/28 19:44:20 mpowers * Fixed reverse enumerator. * * Revision 1.10 2003/01/18 23:49:55 mpowers * Added mutableClone(). * * Revision 1.9 2003/01/18 23:30:42 mpowers * WODisplayGroup now compiles. * * Revision 1.8 2003/01/16 22:47:30 mpowers * Compatibility changes to support compiling woextensions source. * (34 out of 56 classes compile!) * * Revision 1.7 2003/01/10 19:16:40 mpowers * Implemented support for page caching. * * Revision 1.6 2002/10/24 21:15:36 mpowers * New implementations of NSArray and subclasses. * * Revision 1.5 2002/10/24 18:16:30 mpowers * Now enforcing NSArray's immutable nature. * * Revision 1.4 2002/03/08 19:02:54 mpowers * Long-overdue speed optimization of indexOfIdenticalObject. * * Revision 1.3 2002/02/13 22:02:56 mpowers * Fixed: bug in componentsSeparatedByString when separator is null * (thanks to Cedrik LIME). * * Revision 1.2 2001/01/11 20:34:26 mpowers * Implemented EOSortOrdering and added support in framework. * Added header-click to sort table columns. * * Revision 1.1.1.1 2000/12/21 15:47:26 mpowers * Contributing wotonomy. * * Revision 1.3 2000/12/20 16:25:37 michael * Added log to all files. * * */