/*==========================================================================*\ | $Id: MutableArray.java,v 1.2 2011/03/07 18:44:37 stedwar2 Exp $ |*-------------------------------------------------------------------------*| | Copyright (C) 2006-2011 Virginia Tech | | This file is part of Web-CAT. | | Web-CAT is free software; you can redistribute it and/or modify | it under the terms of the GNU Affero General Public License as published | by the Free Software Foundation; either version 3 of the License, or | (at your option) any later version. | | Web-CAT 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 General Public License for more details. | | You should have received a copy of the GNU Affero General Public License | along with Web-CAT; if not, see <http://www.gnu.org/licenses/>. \*==========================================================================*/ package org.webcat.core; import com.webobjects.foundation.*; import java.io.*; import java.util.Collection; import java.util.Iterator; import java.util.Vector; import org.webcat.core.MutableArray; import org.webcat.core.MutableContainer; import org.webcat.core.MutableDictionary; import org.apache.log4j.Logger; //------------------------------------------------------------------------- /** * A customized subcass of NSArray that can be used as the value class * for an EO attribute. Based on the info in * <a href="http://docs.info.apple.com/article.html?artnum=75173">http://docs.info.apple.com/article.html?artnum=75173</a>. * * See the description in {@link MutableDictionary} for critical * usage details. * * @author Stephen Edwards * @version $Id: MutableArray.java,v 1.2 2011/03/07 18:44:37 stedwar2 Exp $ */ @SuppressWarnings("unchecked") public class MutableArray extends er.extensions.foundation.ERXMutableArray implements MutableContainer { //~ Constructors .......................................................... // ---------------------------------------------------------- /** * Creates an empty array. */ public MutableArray() { super(); } // ---------------------------------------------------------- /** * Creates an empty mutable array with enough allocated memory to hold * the number of objects specified by capacity, a number greater than or * equal to 0. * * MutableArrays expand as needed, so capacity simply establishes the * object's initial capacity. * * @param capacity a size hint for the anticipated upper bound */ public MutableArray( int capacity ) { super( capacity ); } // ---------------------------------------------------------- /** * Creates a mutable array containing the single element object. * @param object the single element contained in the mutable array */ public MutableArray( Object object ) { super( object ); resetChildParents( false ); } // ---------------------------------------------------------- /** * Creates a mutable array containing the objects from objects in the * range specified by range. * * After an immutable array has been initialized in this way, it can't * be modified. * * @param objects the objects contained in the mutable array * @param range the range specified */ public MutableArray( Object[] objects, NSRange range ) { super( objects, range ); resetChildParents( false ); } // ---------------------------------------------------------- /** * Creates a mutable array containing objects. * @param objects the objects contained in the mutable array */ public MutableArray( Object[] objects ) { super( objects ); resetChildParents( false ); } // ---------------------------------------------------------- /** * One should use the mutableClone method instead. Creates a mutable * array containing the objects in otherArray. * @param otherArray contains the objects */ public MutableArray( NSArray otherArray ) { super( otherArray ); resetChildParents( false ); } // ---------------------------------------------------------- /** * Creates a mutable array containing the objects from vector in the * range specified by range. * * The ignoreNull argument controls the method's behavior when it * encounters a value in the vector: if ignoreNull is true, the null * value is simply ignored. * * @param vector mutable array contains objects from this * @param range the specified range * @param ignoreNull the null value is ignored * @throws IllegalArgumentException if ignoreNull is false and a null * reference exists within the specified range of the vector. */ public MutableArray( Vector vector, NSRange range, boolean ignoreNull ) { super( vector, range, ignoreNull ); resetChildParents( false ); } // ---------------------------------------------------------- /** * Constructs an array containing the elements of the specified * collection, in the order they are returned by the collection's * iterator. * @param c the collection whose elements are to be placed into this list. */ public MutableArray( Collection c ) { super( c.toArray() ); resetChildParents( false ); } //~ Methods ............................................................... //---------------------------------------------------------- /** * Creates a new, distinct dictionary that contains all the same * values as this one. This method is necessary to support custom * value class usage correctly. * @return The duplicate object */ public Object clone() { return new MutableArray( this ); } // ---------------------------------------------------------- /** * This is the conversion method that serializes this array for * storage in the database. It uses java serialization to serialize * this object into bytes within an NSData object. We're using this * instead of ERXMutableArray.toBlob() since it is slightly more * efficient and has better error checking. * * @return An NSData object containing the serialized bytes of this object. */ public NSData archiveData() { ByteArrayOutputStream bos = new ByteArrayOutputStream( kOverheadAdjustment + count() * kCountBytesFactor ); NSData result = null; try { ObjectOutputStream oos = new ObjectOutputStream( bos ); oos.writeObject( this ); oos.flush(); oos.close(); result = new NSData( bos.toByteArray() ); } catch ( IOException ioe ) { if ( NSLog.debugLoggingAllowedForLevelAndGroups( NSLog.DebugLevelCritical, NSLog.DebugGroupArchiving ) ) { NSLog.debug.appendln( ioe ); } if ( kThrowOnError ) { throw new NSForwardException( ioe ); } } return result; } // ---------------------------------------------------------- /** * This is the factory method used to recreate an array from a * database attribute. It uses java Serialization to turn bytes from an * NSData into a reconstituted Object. We're using this instead of * ERXMutableArray.fromBlob() since it is slightly more efficient * and has better error checking. * * @param data This is the NSData holding the previously serialized bytes. * @return The un-serialized Object. */ public static MutableArray objectWithArchiveData( NSData data ) { if ( data == null ) return new MutableArray(); ByteArrayInputStream bis = new ByteArrayInputStream( data.bytes() ); MutableArray result = null; Throwable exception = null; try { ObjectInputStream ois = new ObjectInputStream( bis ); Object o = ois.readObject(); if ( o instanceof MutableArray ) { result = (MutableArray)o; } else if ( o instanceof NSArray ) { result = new MutableArray( (NSArray)o ); } else { exception = new ClassCastException( "objectWithArchiveData(): " + "cannot cast " + o.getClass().getName() + " to " + MutableArray.class.getName() ); // result is already null } } catch ( IOException ioe ) { exception = ioe; } catch ( ClassNotFoundException cnfe ) { exception = cnfe; } if ( exception != null ) { if ( NSLog.debugLoggingAllowedForLevelAndGroups( NSLog.DebugLevelCritical, NSLog.DebugGroupArchiving ) ) { NSLog.debug.appendln( exception ); } if ( kThrowOnError ) { throw new NSForwardException( exception ); } } return result; } //---------------------------------------------------------- /** * Create an array from a property list string. * @param plist the property list to read from * @return a new MutableArray containing the keys and values from * the property list (The signature returns the base class type to be * compatible with the method in the base class, but the actual object * belongs to this subclass) */ public static er.extensions.foundation.ERXMutableArray fromPropertyList( String plist ) { NSArray a = (NSArray)NSPropertyListSerialization. propertyListFromString( plist ); return new MutableArray( a ); } //---------------------------------------------------------- /** * Test this object to see if it has been changed (mutated) since it * was last saved. * @return true if this dictionary has been changed */ public boolean hasChanged() { log.debug( "hasChanged() = " + hasChanged ); return hasChanged; } //---------------------------------------------------------- /** * Mark this object as having changed (mutated) since it * was last saved. * @param value true if this dictionary has been changed */ public void setHasChanged( boolean value ) { log.debug( "setHasChanged() = " + value ); hasChanged = value; if ( hasChanged ) { if ( parent != null ) { parent.setHasChanged( value ); } if ( owner != null ) { owner.mutableContainerHasChanged(); } } } //---------------------------------------------------------- /** * Set the enclosing container that holds this one, if any. * @param parent a reference to the enclosing container */ public void setParent( MutableContainer parent ) { this.parent = parent; } //---------------------------------------------------------- /** * Set the owner of this container. * @param owner the owner of this container container */ public void setOwner( MutableContainerOwner owner ) { this.owner = owner; } //---------------------------------------------------------- /** * Set the enclosing container that holds this one, if any. * Also, recursively cycle through all contained mutable containers, * resetting their parents to this object as well. * @param parent a reference to the enclosing container */ public void setParentRecursively( MutableContainer parent ) { this.parent = parent; for ( int i = 0; i < count(); i++ ) { Object o = objectAtIndex( i ); if ( o instanceof MutableContainer ) { ( (MutableContainer)o ).setParentRecursively( this ); } } } //---------------------------------------------------------- /** * Examine all contained objects within the specified range for * mutable containers, and reset the parent relationships for any * that are found. Any NS containers found will be converted to * mutable versions. * @param start the starting index of the range * @param end one greater than the last index in the range * @param recurse if true, force the reset to cascade recursively down * the tree, rather than just affecting this node's * immediate children. */ public void resetChildParents( int start, int end, boolean recurse ) { for ( int i = start; i < end; i++ ) { Object o = objectAtIndex( i ); if ( o instanceof MutableContainer ) { MutableContainer mc = (MutableContainer)o; mc.setParent( this ); if ( recurse ) { mc.resetChildParents( recurse ); } } else if ( o instanceof NSDictionary ) { set( i, new MutableDictionary( (NSDictionary)o ) ); } else if ( o instanceof NSArray ) { set( i, new MutableArray( (NSArray)o ) ); } } } //---------------------------------------------------------- /** * Examine all contained objects for mutable containers, and reset * the parent relationships for any that are found. Any NS containers * found will be converted to mutable versions. * @param recurse if true, force the reset to cascade recursively down * the tree, rather than just affecting this node's * immediate children. */ public void resetChildParents( boolean recurse ) { resetChildParents( 0, count(), recurse ); } //---------------------------------------------------------- /** * Examine all contained objects within the specified range for * mutable containers, and clear the parent relationships for any * that are found. * @param start the starting index of the range * @param end one greater than the last index in the range */ public void clearChildParents( int start, int end ) { for ( int i = start; i < end; i++ ) { setParentIfPossible( objectAtIndex( i ), null ); } } //---------------------------------------------------------- /** * Examine all contained objects for mutable containers, and clear * the parent relationships for any that are found. */ public void clearChildParents() { clearChildParents( 0, count() ); } //---------------------------------------------------------- /** * Retrieve the enclosing container that holds this one, if any. * @return a reference to the enclosing container */ public MutableContainer parent() { return parent; } // ---------------------------------------------------------- protected Object setParentIfPossible( Object o, MutableContainer p ) { if ( o instanceof MutableContainer ) { ( (MutableContainer)o ).setParent( p ); } return o; } // ---------------------------------------------------------- protected Object convertToMutableIfPossible( Object o ) { if ( o instanceof MutableContainer ) { ( (MutableContainer)o ).setParent( this ); } else if ( o instanceof NSDictionary ) { MutableDictionary md = new MutableDictionary( (NSDictionary)o ); md.setParent( this ); o = md; } else if ( o instanceof NSArray ) { MutableArray ma = new MutableArray( (NSArray)o ); ma.setParent( this ); o = ma; } return o; } // ---------------------------------------------------------- public void add( int arg0, Object arg1 ) { setHasChanged( true ); super.add( arg0, convertToMutableIfPossible( arg1 ) ); } // ---------------------------------------------------------- public boolean add( Object arg0 ) { setHasChanged( true ); return super.add( convertToMutableIfPossible( arg0 ) ); } // ---------------------------------------------------------- public boolean addAll( Collection arg0 ) { setHasChanged( true ); boolean result = super.addAll( arg0 ); resetChildParents( false ); return result; } // ---------------------------------------------------------- public boolean addAll( int arg0, Collection arg1 ) { setHasChanged( true ); boolean result = super.addAll( arg0, arg1 ); resetChildParents( false ); return result; } // ---------------------------------------------------------- public void addObject( Object arg0 ) { setHasChanged( true ); super.addObject( convertToMutableIfPossible( arg0 ) ); } // ---------------------------------------------------------- public void addObjects( Object[] arg0 ) { setHasChanged( true ); for ( int i = 0; i < arg0.length; i++ ) { arg0[i] = convertToMutableIfPossible( arg0[i] ); } super.addObjects( arg0 ); } // ---------------------------------------------------------- public void addObjectsFromArray( NSArray arg0 ) { setHasChanged( true ); super.addObjectsFromArray( arg0 ); resetChildParents( false ); } // ---------------------------------------------------------- public Object remove( int arg0 ) { setHasChanged( true ); Object result = super.remove( arg0 ); setParentIfPossible( result, null ); return result; } // ---------------------------------------------------------- public boolean remove( Object o ) { boolean result = false; if ( o == null ) { result = super.remove( o ); } else { for ( Iterator e = iterator(); e.hasNext(); ) { Object next = e.next(); if ( o.equals( next ) ) { setParentIfPossible( next, null ); e.remove(); result = true; break; } } } if ( result ) { setHasChanged( true ); } return result; } // ---------------------------------------------------------- public boolean removeAll( Collection arg0 ) { setHasChanged( true ); clearChildParents(); boolean result = super.removeAll( arg0 ); resetChildParents( false ); return result; } // ---------------------------------------------------------- public void removeAllObjects() { setHasChanged( true ); clearChildParents(); super.removeAllObjects(); } // ---------------------------------------------------------- public boolean removeIdenticalObject( Object arg0 ) { boolean result = super.removeIdenticalObject( arg0 ); if ( result ) { setHasChanged( true ); setParentIfPossible( arg0, null ); } return result; } // ---------------------------------------------------------- public boolean removeIdenticalObject( Object arg0, NSRange arg1 ) { boolean result = super.removeIdenticalObject( arg0, arg1 ); if ( result ) { setHasChanged( true ); setParentIfPossible( arg0, null ); } return result; } // ---------------------------------------------------------- public Object removeLastObject() { if ( count() > 0 ) { setHasChanged( true ); setParentIfPossible( objectAtIndex( count() - 1 ), null ); } return super.removeLastObject(); } // ---------------------------------------------------------- public boolean removeObject( Object o ) { boolean result = false; if ( o == null ) { result = super.removeObject( o ); } else { for ( Iterator e = iterator(); e.hasNext(); ) { Object next = e.next(); if ( o.equals( next ) ) { setParentIfPossible( next, null ); e.remove(); result = true; } } } if ( result ) { setHasChanged( true ); } return result; } // ---------------------------------------------------------- public boolean removeObject( Object arg0, NSRange arg1 ) { boolean result = false; clearChildParents( arg1.location(), arg1.location() + arg1.length() ); result = super.removeObject( arg0, arg1 ); if ( result ) { setHasChanged( true ); } resetChildParents( arg1.location(), arg1.location() + arg1.length(), false ); return result; } // ---------------------------------------------------------- public Object removeObjectAtIndex( int arg0 ) { setHasChanged( true ); Object result = super.removeObjectAtIndex( arg0 ); setParentIfPossible( result, null ); return result; } // ---------------------------------------------------------- public void removeObjects( Object[] arg0 ) { setHasChanged( true ); clearChildParents(); super.removeObjects( arg0 ); resetChildParents( false ); } // ---------------------------------------------------------- public void removeObjectsInArray( NSArray arg0 ) { setHasChanged( true ); clearChildParents(); super.removeObjectsInArray( arg0 ); resetChildParents( false ); } // ---------------------------------------------------------- public void removeObjectsInRange( NSRange arg0 ) { setHasChanged( true ); clearChildParents( arg0.location(), arg0.location() + arg0.length() ); super.removeObjectsInRange( arg0 ); } // ---------------------------------------------------------- protected void removeRange( int arg0, int arg1 ) { setHasChanged( true ); clearChildParents( arg0, arg1 ); super.removeRange( arg0, arg1 ); } // ---------------------------------------------------------- public boolean retainAll( Collection arg0 ) { setHasChanged( true ); clearChildParents(); boolean result = super.retainAll( arg0 ); resetChildParents( false ); return result; } // ---------------------------------------------------------- public Object set( int arg0, Object arg1 ) { setHasChanged( true ); setParentIfPossible( objectAtIndex( arg0 ), null ); return super.set( arg0, convertToMutableIfPossible( arg1 ) ); } // ---------------------------------------------------------- public void setArray( NSArray arg0 ) { setHasChanged( true ); clearChildParents(); super.setArray( arg0 ); resetChildParents( false ); } //---------------------------------------------------------- /** * Replace this container's contents by copying from another (and * assuming parent ownership over any subcontainers). The container * is free to assume the argument is of a compatible container type. * @param other the container to copy from */ public void copyFrom( MutableContainer other ) { setArray( (NSArray)other ); } // ---------------------------------------------------------- public void takeValueForKey( Object arg0, String arg1 ) { setHasChanged( true ); super.takeValueForKey( convertToMutableIfPossible( arg0 ), arg1 ); } // ---------------------------------------------------------- public void takeValueForKeyPath( Object arg0, String arg1 ) { setHasChanged( true ); super.takeValueForKeyPath( convertToMutableIfPossible( arg0 ), arg1 ); } //~ Instance/static variables ............................................. /** * This helps create the ByteArrayOutputStream with a good space estimate. */ private static final int kOverheadAdjustment = 512; /** * This also helps create the ByteArrayOutputStream with a good space * estimate. */ private static final int kCountBytesFactor = 16; /** * This determines, when an error occurs, if we should throw an * NSForwardException or just return null. */ private static final boolean kThrowOnError = true; private boolean hasChanged = false; private MutableContainer parent = null; private transient MutableContainerOwner owner = null; static final long serialVersionUID = -69878431293182070L; static Logger log = Logger.getLogger( MutableArray.class ); }