/*==========================================================================*\
| $Id: MutableDictionary.java,v 1.5 2011/03/07 18:39:42 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 General Public License as published by
| the Free Software Foundation; either version 2 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 General Public License
| along with Web-CAT; if not, write to the Free Software
| Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
| Project manager: Stephen Edwards <edwards@cs.vt.edu>
| Virginia Tech CS Dept, 660 McBryde Hall (0106), Blacksburg, VA 24061 USA
\*==========================================================================*/
package edu.vt.cs.Web_CAT.Core;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.Enumeration;
import org.apache.log4j.Logger;
import org.webcat.core.MutableContainer;
import com.webobjects.foundation.NSArray;
import com.webobjects.foundation.NSData;
import com.webobjects.foundation.NSDictionary;
import com.webobjects.foundation.NSForwardException;
import com.webobjects.foundation.NSLog;
import com.webobjects.foundation.NSPropertyListSerialization;
import er.extensions.foundation.ERXFileUtilities;
//-------------------------------------------------------------------------
/**
* A customized subcass of NSDictionary 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>.
*
* <b>NOTE:</b> there appears to be a bug when using MySQL that prevents
* custom value classes from working properly. As a result, we have
* implemented our own portable version using EOGenerator, our customized
* EO Java template, and special values stored in the attribute's userInfo
* dictionary. See the preferences attribute in the User class of the
* Core model for an example of how to set up a custom value class that
* works in MySQL. Also, the userInfo dictionary is not inherited from
* an attribute's prototype, so each value class must provide its own
* copy of the userInfo dictionary in the EOModel.
*
* This custom solution also allows us to keep mutable attributes in an
* EO, since the EOGenerator template plus the new notifications provided
* by its ERXGenericRecord base class allow the value to be appropriately
* handled and updates to be safely recorded. For this to work, a
* mutable class must provide both a {@link #hasChanged()} and a
* {@link #setHasChanged(boolean)} method (see below).
*
* @deprecated use the org.webcat.core version of this class instead.
* This version is only provided for database compatibility during the
* Spring 2006 semester.
*
* @author Stephen Edwards
* @author Last changed by $Author: stedwar2 $
* @version $Revision: 1.5 $, $Date: 2011/03/07 18:39:42 $
*/
@Deprecated
public class MutableDictionary
extends er.extensions.foundation.ERXMutableDictionary
implements MutableContainer
{
//~ Constructors ..........................................................
// ----------------------------------------------------------
/**
* Creates an empty dictionary.
*/
public MutableDictionary()
{
super();
}
//----------------------------------------------------------
/**
* Creates a dictionary containing the keys and values found in
* otherDictionary.
* @param otherDictionary the input dictionary from which the duplicate
* dictionary is to be created
*/
public MutableDictionary( NSDictionary<?, ?> otherDictionary )
{
super( otherDictionary );
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 MutableDictionary( this );
}
// ----------------------------------------------------------
/**
* This is the conversion method that serializes this dictionary 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 ERXMutableDictionary.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 a dictionary from a
* database attribute. It uses java Serialization to turn bytes from an
* NSData into a reconstituted Object. We're using this instead of
* ERXMutableDictionary.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 MutableDictionary objectWithArchiveData( NSData data )
{
if ( data == null ) return new MutableDictionary();
ByteArrayInputStream bis = new ByteArrayInputStream( data.bytes() );
MutableDictionary result = null;
Throwable exception = null;
try
{
ObjectInputStream ois = new ObjectInputStream( bis );
result = (MutableDictionary)ois.readObject();
}
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 a dictionary from a property list string.
* @param plist the property list to read from
* @return a new MutableDictionary 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.ERXMutableDictionary
fromPropertyList(String plist )
{
NSDictionary<?, ?> dict =
(NSDictionary<?, ?>)NSPropertyListSerialization
.propertyListFromString( plist );
return new MutableDictionary( dict );
}
//----------------------------------------------------------
/**
* Create a dictionary from a property list string.
* @param file the file to read from
* @return a new MutableDictionary containing the keys and values from
* the property list contents of the given file
* @throws IOException if a problem occurs reading from the file
*/
public static MutableDictionary fromPropertyList( File file )
throws IOException
{
String stringFromFile = ERXFileUtilities.stringFromFile( file );
NSDictionary<?, ?> dict =
(NSDictionary<?, ?>)NSPropertyListSerialization
.propertyListFromString( stringFromFile );
return new MutableDictionary( dict );
}
//----------------------------------------------------------
/**
* 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 ( Enumeration<?> e = objectEnumerator(); e.hasMoreElements(); )
{
Object o = e.nextElement();
if ( o instanceof MutableContainer )
{
( (MutableContainer)o ).setParentRecursively( this );
}
}
}
//----------------------------------------------------------
/**
* 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 )
{
Object[] keys = keysNoCopy();
for ( int i = 0; i < keys.length; i++ )
{
Object o = objectForKey( keys[i] );
if ( o instanceof MutableContainer )
{
MutableContainer mc = (MutableContainer)o;
mc.setParent( this );
if ( recurse )
{
mc.resetChildParents( recurse );
}
}
else if ( o instanceof NSDictionary )
{
MutableDictionary md =
new MutableDictionary( (NSDictionary<?, ?>)o );
setObjectForKey( md, keys[i] );
}
else if ( o instanceof NSArray )
{
MutableArray ma = new MutableArray( (NSArray<?>)o );
setObjectForKey( ma, keys[i] );
}
}
}
//----------------------------------------------------------
/**
* Examine all contained objects for mutable containers, and clear
* the parent relationships for any that are found.
*/
public void clearChildParents()
{
for ( Enumeration<?> e = objectEnumerator(); e.hasMoreElements(); )
{
setParentIfPossible( e.nextElement(), null );
}
}
//----------------------------------------------------------
/**
* 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;
}
//----------------------------------------------------------
@SuppressWarnings("unchecked")
public void addEntriesFromDictionary( NSDictionary dictionary )
{
setHasChanged( true );
clearChildParents();
super.addEntriesFromDictionary( dictionary );
resetChildParents( false );
}
// ----------------------------------------------------------
public Object remove( Object object )
{
setHasChanged( true );
return setParentIfPossible( super.remove( object ), null );
}
// ----------------------------------------------------------
public void removeAllObjects()
{
setHasChanged( true );
clearChildParents();
super.removeAllObjects();
}
// ----------------------------------------------------------
public Object removeObjectForKey( Object key )
{
setHasChanged( true );
return setParentIfPossible( super.removeObjectForKey( key ), null );
}
// ----------------------------------------------------------
@SuppressWarnings("unchecked")
public void removeObjectsForKeys( NSArray array )
{
setHasChanged( true );
for ( int i = 0; i < array.count(); i++ )
{
setParentIfPossible( objectForKey( array.objectAtIndex( i ) ),
null );
}
super.removeObjectsForKeys( array );
}
// ----------------------------------------------------------
@SuppressWarnings("unchecked")
public void setDictionary( NSDictionary dictionary )
{
setHasChanged( true );
clearChildParents();
super.setDictionary( dictionary );
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 )
{
setDictionary( (NSDictionary<?, ?>)other );
}
// ----------------------------------------------------------
public void setObjectForKey( Object object, Object key )
{
setHasChanged( true );
Object old = objectForKey( key );
if ( old != null )
{
setParentIfPossible( old, null );
}
super.setObjectForKey( convertToMutableIfPossible( object ), key );
}
// ----------------------------------------------------------
public void takeValueForKey( Object value, String key )
{
setHasChanged( true );
Object old = valueForKey( key );
if ( old != null )
{
setParentIfPossible( old, null );
}
super.takeValueForKey( convertToMutableIfPossible( value ), key );
}
// ----------------------------------------------------------
public void takeValueForKeyPath( Object value, String keyPath )
{
setHasChanged( true );
Object old = valueForKeyPath( keyPath );
if ( old != null )
{
setParentIfPossible( old, null );
}
super.takeValueForKeyPath(
convertToMutableIfPossible( value ), keyPath );
}
// ----------------------------------------------------------
/* (non-Javadoc)
* @see com.webobjects.foundation.NSDictionary#toString()
*/
// public String toString()
// {
// return "[@" + Integer.toHexString( hashCode() ) + "]"
// + super.toString();
// }
//~ 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 = -8128095167354032042L;
static Logger log = Logger.getLogger( MutableDictionary.class );
}