| Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||||||
| KeyValueCodingUtilities |
|
| 2.3846153846153846;2.385 |
| 1 | /* |
|
| 2 | Wotonomy: OpenStep design patterns for pure Java applications. |
|
| 3 | Copyright (C) 2001 Intersect Software Corporation |
|
| 4 | ||
| 5 | This library is free software; you can redistribute it and/or |
|
| 6 | modify it under the terms of the GNU Lesser General Public |
|
| 7 | License as published by the Free Software Foundation; either |
|
| 8 | version 2.1 of the License, or (at your option) any later version. |
|
| 9 | ||
| 10 | This library is distributed in the hope that it will be useful, |
|
| 11 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
| 12 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
|
| 13 | Lesser General Public License for more details. |
|
| 14 | ||
| 15 | You should have received a copy of the GNU Lesser General Public |
|
| 16 | License along with this library; if not, see http://www.gnu.org |
|
| 17 | */ |
|
| 18 | ||
| 19 | package net.wotonomy.control; |
|
| 20 | ||
| 21 | import java.io.ByteArrayInputStream; |
|
| 22 | import java.io.ByteArrayOutputStream; |
|
| 23 | import java.io.IOException; |
|
| 24 | import java.io.InputStream; |
|
| 25 | import java.io.ObjectInputStream; |
|
| 26 | import java.io.ObjectOutputStream; |
|
| 27 | import java.io.ObjectStreamClass; |
|
| 28 | import java.io.OutputStream; |
|
| 29 | import java.io.Serializable; |
|
| 30 | import java.util.Iterator; |
|
| 31 | import java.util.LinkedList; |
|
| 32 | import java.util.List; |
|
| 33 | import java.util.Map; |
|
| 34 | ||
| 35 | import net.wotonomy.foundation.NSDictionary; |
|
| 36 | import net.wotonomy.foundation.NSMutableDictionary; |
|
| 37 | import net.wotonomy.foundation.internal.Duplicator; |
|
| 38 | import net.wotonomy.foundation.internal.WotonomyException; |
|
| 39 | ||
| 40 | /** |
|
| 41 | * KeyValueCodingUtilities implements what |
|
| 42 | * EOKeyValueCodingSupport leaves out. Importantly, |
|
| 43 | * this class implements the deep clone and deep copy |
|
| 44 | * operations that are essential to the functioning of |
|
| 45 | * nested editing contexts. |
|
| 46 | * |
|
| 47 | * @author michael@mpowers.net |
|
| 48 | * @author $Author: cgruber $ |
|
| 49 | * @version $Revision: 900 $ |
|
| 50 | */ |
|
| 51 | 0 | public class KeyValueCodingUtilities |
| 52 | { |
|
| 53 | /** |
|
| 54 | * Returns a Map of the specified keys to their values, |
|
| 55 | * each of which is obtained by calling valueForKey |
|
| 56 | * on the specified object if it implements EOKeyValueCoding, |
|
| 57 | * and otherwise falling back on EOKeyValueCodingSupport. |
|
| 58 | * Null values must be represented by NSNull.nullValue(). |
|
| 59 | */ |
|
| 60 | static public NSDictionary valuesForKeys( |
|
| 61 | Object anObject, List aKeyList ) |
|
| 62 | { |
|
| 63 | 0 | return valuesForKeys( anObject, aKeyList, false ); |
| 64 | } |
|
| 65 | ||
| 66 | /** |
|
| 67 | * Returns a Map of the specified keys to their values, |
|
| 68 | * each of which is obtained by calling storedValueForKey |
|
| 69 | * on the specified object if it implements EOKeyValueCoding, |
|
| 70 | * and otherwise falling back on EOKeyValueCodingSupport. |
|
| 71 | * Null values must be represented by NSNull.nullValue(). |
|
| 72 | */ |
|
| 73 | static public NSDictionary storedValuesForKeys( |
|
| 74 | Object anObject, List aKeyList ) |
|
| 75 | { |
|
| 76 | 0 | return valuesForKeys( anObject, aKeyList, true ); |
| 77 | } |
|
| 78 | ||
| 79 | /** |
|
| 80 | * Called by valuesForKeys and storedValuesForKeys. |
|
| 81 | * This uses storedValueForKey if isStored is true, |
|
| 82 | * otherwise uses valueForKey. |
|
| 83 | */ |
|
| 84 | static private NSDictionary valuesForKeys( |
|
| 85 | Object anObject, List aKeyList, boolean isStored ) |
|
| 86 | { |
|
| 87 | EOKeyValueCoding coding; |
|
| 88 | 0 | if ( anObject instanceof EOKeyValueCoding ) |
| 89 | { |
|
| 90 | 0 | coding = (EOKeyValueCoding) anObject; |
| 91 | 0 | } |
| 92 | else |
|
| 93 | { |
|
| 94 | 0 | coding = null; |
| 95 | } |
|
| 96 | ||
| 97 | String key; |
|
| 98 | Object value; |
|
| 99 | 0 | NSMutableDictionary result = new NSMutableDictionary(); |
| 100 | 0 | Iterator it = aKeyList.iterator(); |
| 101 | 0 | while ( it.hasNext() ) |
| 102 | { |
|
| 103 | //TODO: get rid of this try/catch - exceptions should be fatal (?) |
|
| 104 | try |
|
| 105 | { |
|
| 106 | 0 | key = it.next().toString(); |
| 107 | 0 | if ( coding != null ) |
| 108 | { |
|
| 109 | 0 | if ( isStored ) |
| 110 | 0 | value = coding.storedValueForKey( key ); |
| 111 | else |
|
| 112 | 0 | value = coding.valueForKey( key ); |
| 113 | 0 | } |
| 114 | else |
|
| 115 | { |
|
| 116 | 0 | if ( isStored ) |
| 117 | 0 | value = EOKeyValueCodingSupport.storedValueForKey( anObject, key ); |
| 118 | else |
|
| 119 | 0 | value = EOKeyValueCodingSupport.valueForKey( anObject, key ); |
| 120 | } |
|
| 121 | 0 | if ( value == null ) |
| 122 | { |
|
| 123 | 0 | value = EONullValue.nullValue(); |
| 124 | } |
|
| 125 | 0 | result.setObjectForKey( value, key ); |
| 126 | } |
|
| 127 | 0 | catch ( RuntimeException exc ) |
| 128 | { |
|
| 129 | 0 | System.out.println( |
| 130 | 0 | "KeyValueCodingUtilities.valuesForKeys: " |
| 131 | 0 | + isStored + " : " + exc ); |
| 132 | 0 | } |
| 133 | 0 | } |
| 134 | 0 | return result; |
| 135 | } |
|
| 136 | ||
| 137 | /** |
|
| 138 | * Takes the keys from the specified Map as properties |
|
| 139 | * and applies the corresponding values, each of which |
|
| 140 | * might be set by calling takeValueForKey on the |
|
| 141 | * specified object if it implements EOKeyValueCoding, |
|
| 142 | * and otherwise falling back on EOKeyValueCodingSupport. |
|
| 143 | * Null values must be represented by NSNull.nullValue(). |
|
| 144 | */ |
|
| 145 | static public void takeValuesFromDictionary( |
|
| 146 | Object anObject, Map aMap ) |
|
| 147 | { |
|
| 148 | 0 | takeStoredValuesFromDictionary( anObject, aMap, false ); |
| 149 | 0 | } |
| 150 | ||
| 151 | /** |
|
| 152 | * Takes the keys from the specified Map as properties |
|
| 153 | * and applies the corresponding values, each of which |
|
| 154 | * might be set by calling takeStoredValueForKey on the |
|
| 155 | * specified object if it implements EOKeyValueCoding, |
|
| 156 | * and otherwise falling back on EOKeyValueCodingSupport. |
|
| 157 | * Null values must be represented by NSNull.nullValue(). |
|
| 158 | */ |
|
| 159 | static public void takeStoredValuesFromDictionary( |
|
| 160 | Object anObject, Map aMap ) |
|
| 161 | { |
|
| 162 | 0 | takeStoredValuesFromDictionary( anObject, aMap, true ); |
| 163 | 0 | } |
| 164 | ||
| 165 | /** |
|
| 166 | * Called by takeValuesFromDictionary and takeStoredValuesFromDictionary. |
|
| 167 | * This uses takeStoredValueForKey if isStored is true, |
|
| 168 | * otherwise uses takeValueForKey. |
|
| 169 | */ |
|
| 170 | static private void takeStoredValuesFromDictionary( |
|
| 171 | Object anObject, Map aMap, boolean isStored ) |
|
| 172 | { |
|
| 173 | EOKeyValueCoding coding; |
|
| 174 | 0 | if ( anObject instanceof EOKeyValueCoding ) |
| 175 | { |
|
| 176 | 0 | coding = (EOKeyValueCoding) anObject; |
| 177 | 0 | } |
| 178 | else |
|
| 179 | { |
|
| 180 | 0 | coding = null; |
| 181 | } |
|
| 182 | ||
| 183 | String key; |
|
| 184 | Object value; |
|
| 185 | 0 | NSMutableDictionary result = new NSMutableDictionary(); |
| 186 | 0 | Iterator it = aMap.keySet().iterator(); |
| 187 | 0 | while ( it.hasNext() ) |
| 188 | { |
|
| 189 | //TODO: get rid of this try/catch - exceptions should be fatal (?) |
|
| 190 | try |
|
| 191 | { |
|
| 192 | 0 | key = it.next().toString(); |
| 193 | 0 | value = aMap.get( key ); |
| 194 | 0 | if ( value instanceof EONullValue ) |
| 195 | // can't use == nullValue() because of cloning/serialization |
|
| 196 | { |
|
| 197 | 0 | value = null; |
| 198 | } |
|
| 199 | 0 | if ( coding != null ) |
| 200 | { |
|
| 201 | 0 | if ( isStored ) |
| 202 | 0 | coding.takeStoredValueForKey( value, key ); |
| 203 | else |
|
| 204 | 0 | coding.takeValueForKey( value, key ); |
| 205 | 0 | } |
| 206 | else |
|
| 207 | { |
|
| 208 | 0 | if ( isStored ) |
| 209 | 0 | EOKeyValueCodingSupport.takeStoredValueForKey( |
| 210 | 0 | anObject, value, key ); |
| 211 | else |
|
| 212 | 0 | EOKeyValueCodingSupport.takeValueForKey( |
| 213 | 0 | anObject, value, key ); |
| 214 | } |
|
| 215 | } |
|
| 216 | 0 | catch ( WotonomyException exc ) |
| 217 | { |
|
| 218 | 0 | System.out.println( |
| 219 | 0 | "KeyValueCodingUtilities.takeStoredValuesFromDictionary: " |
| 220 | 0 | + isStored + " : " + exc ); |
| 221 | 0 | } |
| 222 | 0 | } |
| 223 | 0 | } |
| 224 | ||
| 225 | /** |
|
| 226 | * Creates a deep clone of the specified object. |
|
| 227 | * (Object.clone() only creates a shallow clone.) |
|
| 228 | * Returns null if operation fails. |
|
| 229 | */ |
|
| 230 | static public Object clone( Object aSource ) |
|
| 231 | { |
|
| 232 | 0 | return Duplicator.deepClone( aSource ); |
| 233 | } |
|
| 234 | ||
| 235 | /** |
|
| 236 | * Creates a deep clone of the specified object, |
|
| 237 | * registered in the specified source editing context, |
|
| 238 | * transposing it into the specified destination |
|
| 239 | * editing context. |
|
| 240 | * Returns null if operation fails. |
|
| 241 | */ |
|
| 242 | static public Object clone( |
|
| 243 | EOEditingContext aSourceContext, Object aSource, |
|
| 244 | EOEditingContext aDestinationContext ) |
|
| 245 | { |
|
| 246 | 0 | return clone( aSourceContext, aSource, aDestinationContext, aSource ); |
| 247 | } |
|
| 248 | ||
| 249 | /** |
|
| 250 | * Called by clone and copy. |
|
| 251 | * The specified root object will not be replaced |
|
| 252 | * by an object in the destination editing context: |
|
| 253 | * this should be the same as the source object for |
|
| 254 | * cloning, but should be null for copying. |
|
| 255 | * Returns null if operation fails. |
|
| 256 | */ |
|
| 257 | static private Object clone( |
|
| 258 | EOEditingContext aSourceContext, Object aSource, |
|
| 259 | EOEditingContext aDestinationContext, |
|
| 260 | Object aRootObject ) |
|
| 261 | { |
|
| 262 | ||
| 263 | //System.out.println(); |
|
| 264 | //System.out.println( "clone: " + aSourceContext ); |
|
| 265 | //System.out.println( " : " + aSource ); |
|
| 266 | //System.out.println( " : " + aDestinationContext ); |
|
| 267 | //System.out.println(); |
|
| 268 | ||
| 269 | // the only known way to deep copy in |
|
| 270 | // java without native code is serialization |
|
| 271 | ||
| 272 | 0 | return thaw( |
| 273 | 0 | freeze( aSource, aSourceContext, aRootObject, true ), |
| 274 | 0 | aDestinationContext, true ); |
| 275 | } |
|
| 276 | ||
| 277 | /** |
|
| 278 | * Serializes an object to a byte array containing |
|
| 279 | * GlobalIDMarkers in place of references to other objects |
|
| 280 | * registered in the specified context. |
|
| 281 | * The specified root object will be serialized, |
|
| 282 | * even if it is registered in the specified context: |
|
| 283 | * this is typically the root object you're trying to |
|
| 284 | * serialize. |
|
| 285 | * Package access, as this method is used by editing |
|
| 286 | * context for snapshots. |
|
| 287 | */ |
|
| 288 | static public byte[] freeze( |
|
| 289 | Object anObject, EOEditingContext aContext, Object aRootObject, boolean transpose ) |
|
| 290 | { |
|
| 291 | try |
|
| 292 | { |
|
| 293 | //long t = System.currentTimeMillis(); |
|
| 294 | 0 | ByteArrayOutputStream byteOutput = |
| 295 | 0 | new ByteArrayOutputStream();// CloneBufferSize ); |
| 296 | ObjectOutputStream objectOutput; |
|
| 297 | 0 | if ( transpose ) |
| 298 | { |
|
| 299 | 0 | objectOutput = |
| 300 | 0 | new TransposingContextObjectOutputStream( |
| 301 | 0 | byteOutput, aContext, aRootObject ); |
| 302 | 0 | } |
| 303 | else |
|
| 304 | { |
|
| 305 | 0 | objectOutput = |
| 306 | 0 | new ContextObjectOutputStream( |
| 307 | 0 | byteOutput, aContext ); |
| 308 | } |
|
| 309 | ||
| 310 | 0 | objectOutput.writeObject( anObject ); |
| 311 | 0 | objectOutput.flush(); |
| 312 | 0 | objectOutput.close(); |
| 313 | ||
| 314 | 0 | return byteOutput.toByteArray(); |
| 315 | ||
| 316 | // profiling |
|
| 317 | /* |
|
| 318 | byte[] result = byteOutput.toByteArray(); |
|
| 319 | long size = result.length; |
|
| 320 | long time = ( System.currentTimeMillis() - t ); |
|
| 321 | maxSize = Math.max( size, maxSize ); |
|
| 322 | minSize = Math.min( size, minSize ); |
|
| 323 | totSize += size; |
|
| 324 | maxTime = Math.max( time, maxTime ); |
|
| 325 | minTime = Math.min( time, minTime ); |
|
| 326 | totTime += time; |
|
| 327 | nTime++; |
|
| 328 | System.out.println( "freeze: size = [ " + size + " : " + minSize + " : " + ( (float)totSize / (float)nTime ) + " : " + maxSize |
|
| 329 | + " ] time = [ " + time + " : " + minTime + " : " + ( (float)totTime / (float)nTime ) + " : " + maxTime + " ]" ); |
|
| 330 | return result; |
|
| 331 | */ |
|
| 332 | // end profiling |
|
| 333 | ||
| 334 | } |
|
| 335 | 0 | catch ( Exception exc ) |
| 336 | { |
|
| 337 | 0 | throw new WotonomyException( exc ); |
| 338 | } |
|
| 339 | } |
|
| 340 | ||
| 341 | //static long maxTime, minTime, totTime, nTime, maxSize, minSize, totSize; |
|
| 342 | //static long maxTimeThaw, minTimeThaw, totTimeThaw, nTimeThaw; |
|
| 343 | ||
| 344 | /** |
|
| 345 | * De-serializes an object from the specified byte |
|
| 346 | * array, replacing GlobalIDMarkers with reference |
|
| 347 | * to objects registered in the specified editing |
|
| 348 | * context. |
|
| 349 | * Package access, as this method is used by editing |
|
| 350 | * context for snapshots. |
|
| 351 | */ |
|
| 352 | static public Object thaw( |
|
| 353 | byte[] aByteArray, EOEditingContext aContext, boolean transpose ) |
|
| 354 | { |
|
| 355 | 0 | return thaw( aByteArray, aContext, null, transpose ); |
| 356 | } |
|
| 357 | ||
| 358 | /** |
|
| 359 | * De-serializes an object from the specified byte |
|
| 360 | * array, replacing GlobalIDMarkers with reference |
|
| 361 | * to objects registered in the specified editing |
|
| 362 | * context. |
|
| 363 | * Package access, as this method is used by editing |
|
| 364 | * context for snapshots. |
|
| 365 | */ |
|
| 366 | static public Object thaw( |
|
| 367 | byte[] aByteArray, EOEditingContext aContext, ClassLoader aLoader, boolean transpose ) |
|
| 368 | { |
|
| 369 | try |
|
| 370 | { |
|
| 371 | //long t = System.currentTimeMillis(); |
|
| 372 | 0 | ByteArrayInputStream byteInput = |
| 373 | 0 | new ByteArrayInputStream( aByteArray ); |
| 374 | ObjectInputStream objectInput; |
|
| 375 | 0 | if ( transpose ) |
| 376 | { |
|
| 377 | 0 | objectInput = |
| 378 | 0 | new TransposingContextObjectInputStream( |
| 379 | 0 | byteInput, aContext, aLoader ); |
| 380 | 0 | } |
| 381 | else |
|
| 382 | { |
|
| 383 | 0 | objectInput = |
| 384 | 0 | new ContextObjectInputStream( |
| 385 | 0 | byteInput, aContext, aLoader ); |
| 386 | } |
|
| 387 | ||
| 388 | 0 | return objectInput.readObject(); |
| 389 | // profiling |
|
| 390 | /* |
|
| 391 | Object result = objectInput.readObject(); |
|
| 392 | long timeThaw = ( System.currentTimeMillis() - t ); |
|
| 393 | maxTimeThaw = Math.max( timeThaw, maxTimeThaw ); |
|
| 394 | minTimeThaw = Math.min( timeThaw, minTimeThaw ); |
|
| 395 | totTimeThaw += timeThaw; |
|
| 396 | nTimeThaw++; |
|
| 397 | System.out.println( "thaw: size = " + aByteArray.length + ", time = [ " + timeThaw + " : " + minTimeThaw + " : " + ( (float)totTimeThaw / (float)nTimeThaw ) + " : " + maxTimeThaw + " ]" ); |
|
| 398 | return result; |
|
| 399 | */ |
|
| 400 | // end profiling |
|
| 401 | } |
|
| 402 | 0 | catch ( Exception exc ) |
| 403 | { |
|
| 404 | 0 | throw new WotonomyException( exc ); |
| 405 | } |
|
| 406 | } |
|
| 407 | ||
| 408 | /** |
|
| 409 | * Copies values from one object registered in the |
|
| 410 | * specified origin context to the specified destination |
|
| 411 | * object |
|
| 412 | * The values themselves are cloned, so this is a deep copy. |
|
| 413 | * Returns the destination object, or throws exception |
|
| 414 | * if operation fails. |
|
| 415 | */ |
|
| 416 | static public Object copy( Object aSource, Object aDestination ) |
|
| 417 | { |
|
| 418 | 0 | NSDictionary values = (NSDictionary) |
| 419 | 0 | clone( valuesForKeys( aSource, |
| 420 | 0 | EOClassDescription.classDescriptionForClass( |
| 421 | 0 | aSource.getClass() ).attributeKeys() ) ); |
| 422 | ||
| 423 | 0 | takeStoredValuesFromDictionary( aDestination, values ); |
| 424 | 0 | return aDestination; |
| 425 | } |
|
| 426 | ||
| 427 | /** |
|
| 428 | * Copies values from one object registered in the |
|
| 429 | * specified origin context to the specified destination |
|
| 430 | * object |
|
| 431 | * The values themselves are cloned, so this is a deep copy. |
|
| 432 | * Returns the destination object, or throws exception |
|
| 433 | * if operation fails. |
|
| 434 | */ |
|
| 435 | static public Object copy( |
|
| 436 | EOEditingContext aSourceContext, Object aSource, |
|
| 437 | EOEditingContext aDestinationContext, Object aDestination ) |
|
| 438 | { |
|
| 439 | // get all keys for this object |
|
| 440 | 0 | EOClassDescription classDesc = |
| 441 | 0 | EOClassDescription.classDescriptionForClass( aSource.getClass() ); |
| 442 | 0 | List keys = new LinkedList(); |
| 443 | 0 | keys.addAll( classDesc.attributeKeys() ); |
| 444 | 0 | keys.addAll( classDesc.toOneRelationshipKeys() ); |
| 445 | 0 | keys.addAll( classDesc.toManyRelationshipKeys() ); |
| 446 | ||
| 447 | // transpose all objects registered in source context |
|
| 448 | 0 | NSDictionary values = storedValuesForKeys( aSource, keys ); |
| 449 | 0 | values = (NSDictionary) |
| 450 | 0 | clone( aSourceContext, values, aDestinationContext, null ); |
| 451 | ||
| 452 | // apply to destination object |
|
| 453 | 0 | takeStoredValuesFromDictionary( aDestination, values ); |
| 454 | 0 | return aDestination; |
| 455 | } |
|
| 456 | ||
| 457 | // inner classes |
|
| 458 | ||
| 459 | /** |
|
| 460 | * An ObjectOutputStream that serializes objects with references |
|
| 461 | * to an editing context. The specified context will not be |
|
| 462 | * serialized but referenced, so that a ContextObjectInputStream |
|
| 463 | * can replace the reference with another editing context. |
|
| 464 | */ |
|
| 465 | static private class ContextObjectOutputStream extends ObjectOutputStream |
|
| 466 | { |
|
| 467 | 0 | private EditingContextMarker marker = new EditingContextMarker(); |
| 468 | protected EOEditingContext editingContext; |
|
| 469 | ||
| 470 | /** |
|
| 471 | * Specifies the output stream to wrap, |
|
| 472 | * and the source context that should be |
|
| 473 | * referenced but not serialized. |
|
| 474 | */ |
|
| 475 | public ContextObjectOutputStream( |
|
| 476 | OutputStream anOutputStream, |
|
| 477 | EOEditingContext aContext ) |
|
| 478 | throws IOException |
|
| 479 | { |
|
| 480 | 0 | super( anOutputStream ); |
| 481 | 0 | editingContext = aContext; |
| 482 | try |
|
| 483 | { |
|
| 484 | 0 | enableReplaceObject(true); |
| 485 | } |
|
| 486 | 0 | catch ( Exception exc ) |
| 487 | { |
|
| 488 | 0 | exc.printStackTrace(); |
| 489 | 0 | } |
| 490 | 0 | } |
| 491 | ||
| 492 | protected Object replaceObject(Object anObject) throws IOException |
|
| 493 | { |
|
| 494 | // if ( anObject == editingContext ) return marker; |
|
| 495 | //FIXME: this should be more strict as above |
|
| 496 | 0 | if ( anObject instanceof EOEditingContext ) return marker; |
| 497 | 0 | return anObject; |
| 498 | } |
|
| 499 | ||
| 500 | } |
|
| 501 | ||
| 502 | /** |
|
| 503 | * A ContextObjectOutputStream that replaces any objects registered |
|
| 504 | * in the source editing context with markers to be used in |
|
| 505 | * ContextObjectInputStream. |
|
| 506 | */ |
|
| 507 | static private class TransposingContextObjectOutputStream |
|
| 508 | extends ContextObjectOutputStream |
|
| 509 | { |
|
| 510 | protected Object rootObject; |
|
| 511 | ||
| 512 | /** |
|
| 513 | * Specifies the output stream to wrap, |
|
| 514 | * the source context containing objects that |
|
| 515 | * should be replaced if found, |
|
| 516 | * and the object which should not be re-registered, |
|
| 517 | * which is typically the object being cloned, but |
|
| 518 | * may be null. |
|
| 519 | */ |
|
| 520 | public TransposingContextObjectOutputStream( |
|
| 521 | OutputStream anOutputStream, |
|
| 522 | EOEditingContext aContext, |
|
| 523 | Object anObject ) |
|
| 524 | throws IOException |
|
| 525 | { |
|
| 526 | 0 | super( anOutputStream, aContext ); |
| 527 | 0 | rootObject = anObject; |
| 528 | 0 | } |
| 529 | ||
| 530 | protected Object replaceObject(Object anObject) throws IOException |
|
| 531 | { |
|
| 532 | 0 | if ( anObject == rootObject ) return anObject; |
| 533 | 0 | if ( editingContext != null ) |
| 534 | { |
|
| 535 | 0 | EOGlobalID id = editingContext.globalIDForObject( anObject ); |
| 536 | 0 | if ( id != null ) |
| 537 | { |
|
| 538 | 0 | Object result = new GlobalIDMarker( id ); |
| 539 | //System.out.println( "KeyValueCodingUtilities.replaceObject: returning: " + result ); |
|
| 540 | 0 | return result; |
| 541 | } |
|
| 542 | } |
|
| 543 | 0 | return super.replaceObject( anObject ); |
| 544 | } |
|
| 545 | ||
| 546 | } |
|
| 547 | ||
| 548 | /** |
|
| 549 | * A marker class so references to objects registered in editing |
|
| 550 | * contexts get transposed rather than cloned. |
|
| 551 | */ |
|
| 552 | static private class GlobalIDMarker implements Serializable |
|
| 553 | { |
|
| 554 | private EOGlobalID id; |
|
| 555 | ||
| 556 | 0 | public GlobalIDMarker( EOGlobalID anID ) |
| 557 | 0 | { |
| 558 | 0 | id = anID; |
| 559 | 0 | } |
| 560 | ||
| 561 | public EOGlobalID getID() |
|
| 562 | { |
|
| 563 | 0 | return id; |
| 564 | } |
|
| 565 | ||
| 566 | public String toString() |
|
| 567 | { |
|
| 568 | 0 | return "[GlobalIDMarker:"+id+"]"; |
| 569 | } |
|
| 570 | } |
|
| 571 | ||
| 572 | /** |
|
| 573 | * A marker class so references an object's editing context |
|
| 574 | * gets transposed rather than cloned. |
|
| 575 | */ |
|
| 576 | 0 | static private class EditingContextMarker implements Serializable |
| 577 | { |
|
| 578 | // just a marker class - no implementation necessary |
|
| 579 | } |
|
| 580 | ||
| 581 | /** |
|
| 582 | * An ObjectInputStream that replaces any markers from |
|
| 583 | * ContextObjectOutputStream with objects registered |
|
| 584 | * in the destination editing context. |
|
| 585 | */ |
|
| 586 | static private class ContextObjectInputStream extends ObjectInputStream |
|
| 587 | { |
|
| 588 | protected EOEditingContext editingContext; |
|
| 589 | protected ClassLoader classLoader; |
|
| 590 | ||
| 591 | /** |
|
| 592 | * Specifies the output stream to wrap, |
|
| 593 | * the source context containing objects that |
|
| 594 | * should be to replace any markers. |
|
| 595 | * The class loader may be null. |
|
| 596 | */ |
|
| 597 | public ContextObjectInputStream( |
|
| 598 | InputStream anInputStream, |
|
| 599 | EOEditingContext aContext, |
|
| 600 | ClassLoader aClassLoader ) |
|
| 601 | throws IOException |
|
| 602 | { |
|
| 603 | 0 | super( anInputStream ); |
| 604 | 0 | editingContext = aContext; |
| 605 | 0 | classLoader = aClassLoader; |
| 606 | 0 | if ( classLoader == null ) |
| 607 | { |
|
| 608 | 0 | classLoader = |
| 609 | 0 | KeyValueCodingUtilities.class.getClassLoader(); |
| 610 | } |
|
| 611 | try |
|
| 612 | { |
|
| 613 | 0 | enableResolveObject(true); |
| 614 | } |
|
| 615 | 0 | catch ( Exception exc ) |
| 616 | { |
|
| 617 | 0 | exc.printStackTrace(); |
| 618 | 0 | } |
| 619 | 0 | } |
| 620 | ||
| 621 | protected Object resolveObject(Object anObject) throws IOException |
|
| 622 | { |
|
| 623 | 0 | if ( anObject instanceof EditingContextMarker ) |
| 624 | { |
|
| 625 | 0 | return editingContext; |
| 626 | } |
|
| 627 | 0 | return anObject; |
| 628 | } |
|
| 629 | ||
| 630 | protected Class resolveClass(ObjectStreamClass v) |
|
| 631 | throws IOException, ClassNotFoundException |
|
| 632 | { |
|
| 633 | 0 | return classLoader.loadClass( v.getName() ); |
| 634 | } |
|
| 635 | } |
|
| 636 | ||
| 637 | /** |
|
| 638 | * A ContextObjectInputStream that replaces any markers from |
|
| 639 | * TransposingContextObjectOutputStream with objects registered |
|
| 640 | * in the destination editing context. |
|
| 641 | */ |
|
| 642 | 0 | static private class TransposingContextObjectInputStream |
| 643 | extends ContextObjectInputStream |
|
| 644 | { |
|
| 645 | /** |
|
| 646 | * Specifies the output stream to wrap, |
|
| 647 | * the source context containing objects that |
|
| 648 | * should be to replace any markers. |
|
| 649 | */ |
|
| 650 | public TransposingContextObjectInputStream( |
|
| 651 | InputStream anInputStream, |
|
| 652 | EOEditingContext aContext, |
|
| 653 | ClassLoader aClassLoader ) |
|
| 654 | throws IOException |
|
| 655 | { |
|
| 656 | 0 | super( anInputStream, aContext, aClassLoader ); |
| 657 | 0 | } |
| 658 | ||
| 659 | protected Object resolveObject(Object anObject) throws IOException |
|
| 660 | { |
|
| 661 | 0 | if ( anObject instanceof GlobalIDMarker ) |
| 662 | { |
|
| 663 | 0 | return editingContext.faultForGlobalID( |
| 664 | 0 | ((GlobalIDMarker)anObject).getID(), editingContext ); |
| 665 | } |
|
| 666 | 0 | return super.resolveObject( anObject ); |
| 667 | } |
|
| 668 | } |
|
| 669 | ||
| 670 | } |
|
| 671 | ||
| 672 | /* |
|
| 673 | * $Log$ |
|
| 674 | * Revision 1.3 2006/02/18 22:46:44 cgruber |
|
| 675 | * Add Surrogate map from .util into control's internal package, and fix imports. |
|
| 676 | * |
|
| 677 | * Revision 1.2 2006/02/16 16:47:14 cgruber |
|
| 678 | * Move some classes in to "internal" packages and re-work imports, etc. |
|
| 679 | * |
|
| 680 | * Also use UnsupportedOperationExceptions where appropriate, instead of WotonomyExceptions. |
|
| 681 | * |
|
| 682 | * Revision 1.1 2006/02/16 13:19:57 cgruber |
|
| 683 | * Check in all sources in eclipse-friendly maven-enabled packages. |
|
| 684 | * |
|
| 685 | * Revision 1.15 2003/01/21 22:30:10 mpowers |
|
| 686 | * thaw() now allows you to pass in a class loader. |
|
| 687 | * |
|
| 688 | * Revision 1.14 2002/05/15 13:46:35 mpowers |
|
| 689 | * Exposed freeze and thaw as public. |
|
| 690 | * |
|
| 691 | * Revision 1.13 2001/08/22 19:25:13 mpowers |
|
| 692 | * Added (and commented out) profiling code for freeze. |
|
| 693 | * |
|
| 694 | * Revision 1.12 2001/05/06 18:27:10 mpowers |
|
| 695 | * More broadly catching editing contexts for now. |
|
| 696 | * |
|
| 697 | * Revision 1.11 2001/05/05 13:18:49 mpowers |
|
| 698 | * Fixed: transposing output stream was not returning the object to replace. |
|
| 699 | * |
|
| 700 | * Revision 1.10 2001/05/04 16:57:56 mpowers |
|
| 701 | * Now correctly transposing references to editing contexts when |
|
| 702 | * cloning/copying between editing contexts. |
|
| 703 | * |
|
| 704 | * Revision 1.9 2001/05/04 14:42:58 mpowers |
|
| 705 | * Now getting stored values in KeyValueCoding. |
|
| 706 | * MasterDetail now marks dirty based on whether it's an attribute |
|
| 707 | * or relation. |
|
| 708 | * Implemented editing context marker. |
|
| 709 | * |
|
| 710 | * Revision 1.8 2001/05/02 15:47:40 mpowers |
|
| 711 | * Fixed the pernicious problem with reverts: recordObject was recording |
|
| 712 | * a snapshot of the clone before the transposition-copy happened, |
|
| 713 | * so the revert object would lose all of its transposed relationships. |
|
| 714 | * |
|
| 715 | * Revision 1.7 2001/04/30 12:33:17 mpowers |
|
| 716 | * Fixed problem with use of EONullValue.nullValue(), which can't be used |
|
| 717 | * when we're serializably duplicating objects. |
|
| 718 | * |
|
| 719 | * Revision 1.6 2001/04/30 02:14:25 mpowers |
|
| 720 | * Copying should call takeStoredValueForKeys. |
|
| 721 | * |
|
| 722 | * Revision 1.5 2001/04/29 22:02:45 mpowers |
|
| 723 | * Work on id transposing between editing contexts. |
|
| 724 | * |
|
| 725 | * Revision 1.4 2001/04/29 02:29:31 mpowers |
|
| 726 | * Debugging relationship faulting. |
|
| 727 | * |
|
| 728 | * Revision 1.3 2001/04/28 16:18:44 mpowers |
|
| 729 | * Implementing relationships. |
|
| 730 | * |
|
| 731 | * Revision 1.2 2001/04/28 14:12:23 mpowers |
|
| 732 | * Refactored cloning/copying into KeyValueCodingUtilities. |
|
| 733 | * |
|
| 734 | * Revision 1.1 2001/04/27 23:41:12 mpowers |
|
| 735 | * Contributing file for KeyValueCodingUtilities. |
|
| 736 | * |
|
| 737 | * |
|
| 738 | */ |
|
| 739 | ||
| 740 |