GNU Classpath (0.95) | |
Frames | No Frames |
1: /* Encoder.java 2: Copyright (C) 2005, 2006 Free Software Foundation, Inc. 3: 4: This file is part of GNU Classpath. 5: 6: GNU Classpath is free software; you can redistribute it and/or modify 7: it under the terms of the GNU General Public License as published by 8: the Free Software Foundation; either version 2, or (at your option) 9: any later version. 10: 11: GNU Classpath is distributed in the hope that it will be useful, but 12: WITHOUT ANY WARRANTY; without even the implied warranty of 13: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14: General Public License for more details. 15: 16: You should have received a copy of the GNU General Public License 17: along with GNU Classpath; see the file COPYING. If not, write to the 18: Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 19: 02110-1301 USA. 20: 21: Linking this library statically or dynamically with other modules is 22: making a combined work based on this library. Thus, the terms and 23: conditions of the GNU General Public License cover the whole 24: combination. 25: 26: As a special exception, the copyright holders of this library give you 27: permission to link this library with independent modules to produce an 28: executable, regardless of the license terms of these independent 29: modules, and to copy and distribute the resulting executable under 30: terms of your choice, provided that you also meet, for each linked 31: independent module, the terms and conditions of the license of that 32: module. An independent module is a module which is not derived from 33: or based on this library. If you modify this library, you may extend 34: this exception to your version of the library, but you are not 35: obligated to do so. If you do not wish to do so, delete this 36: exception statement from your version. */ 37: 38: 39: package java.beans; 40: 41: import gnu.java.beans.DefaultExceptionListener; 42: import gnu.java.beans.encoder.ArrayPersistenceDelegate; 43: import gnu.java.beans.encoder.ClassPersistenceDelegate; 44: import gnu.java.beans.encoder.CollectionPersistenceDelegate; 45: import gnu.java.beans.encoder.MapPersistenceDelegate; 46: import gnu.java.beans.encoder.PrimitivePersistenceDelegate; 47: 48: import java.util.AbstractCollection; 49: import java.util.HashMap; 50: import java.util.IdentityHashMap; 51: 52: /** 53: * @author Robert Schuster (robertschuster@fsfe.org) 54: * @since 1.4 55: */ 56: public class Encoder 57: { 58: 59: /** 60: * An internal DefaultPersistenceDelegate instance that is used for every 61: * class that does not a have a special special PersistenceDelegate. 62: */ 63: private static PersistenceDelegate defaultPersistenceDelegate; 64: 65: private static PersistenceDelegate fakePersistenceDelegate; 66: 67: /** 68: * Stores the relation Class->PersistenceDelegate. 69: */ 70: private static HashMap delegates = new HashMap(); 71: 72: /** 73: * Stores the relation oldInstance->newInstance 74: */ 75: private IdentityHashMap candidates = new IdentityHashMap(); 76: 77: private ExceptionListener exceptionListener; 78: 79: /** 80: * A simple number that is used to restrict the access to writeExpression and 81: * writeStatement. The rule is that both methods should only be used when an 82: * object is written to the stream (= writeObject). Therefore accessCounter is 83: * incremented just before the call to writeObject and decremented afterwards. 84: * Then writeStatement and writeExpression allow execution only if 85: * accessCounter is bigger than zero. 86: */ 87: private int accessCounter = 0; 88: 89: public Encoder() 90: { 91: setupDefaultPersistenceDelegates(); 92: 93: setExceptionListener(null); 94: } 95: 96: /** 97: * Sets up a bunch of {@link PersistenceDelegate} instances which are needed 98: * for the basic working of a {@link Encoder}s. 99: */ 100: private static void setupDefaultPersistenceDelegates() 101: { 102: synchronized (delegates) 103: { 104: if (defaultPersistenceDelegate != null) 105: return; 106: 107: delegates.put(Class.class, new ClassPersistenceDelegate()); 108: 109: PersistenceDelegate pd = new PrimitivePersistenceDelegate(); 110: delegates.put(Boolean.class, pd); 111: delegates.put(Byte.class, pd); 112: delegates.put(Short.class, pd); 113: delegates.put(Integer.class, pd); 114: delegates.put(Long.class, pd); 115: delegates.put(Float.class, pd); 116: delegates.put(Double.class, pd); 117: 118: delegates.put(Object[].class, new ArrayPersistenceDelegate()); 119: 120: pd = new CollectionPersistenceDelegate(); 121: delegates.put(AbstractCollection.class, pd); 122: 123: pd = new MapPersistenceDelegate(); 124: delegates.put(java.util.AbstractMap.class, pd); 125: delegates.put(java.util.Hashtable.class, pd); 126: 127: defaultPersistenceDelegate = new DefaultPersistenceDelegate(); 128: delegates.put(Object.class, defaultPersistenceDelegate); 129: 130: // Creates a PersistenceDelegate implementation which is 131: // returned for 'null'. In practice this instance is 132: // not used in any way and is just here to be compatible 133: // with the reference implementation which returns a 134: // similar instance when calling getPersistenceDelegate(null) . 135: fakePersistenceDelegate = new PersistenceDelegate() 136: { 137: protected Expression instantiate(Object o, Encoder e) 138: { 139: return null; 140: } 141: }; 142: 143: } 144: } 145: 146: protected void writeObject(Object o) 147: { 148: // 'null' has no PersistenceDelegate and will not 149: // create an Expression which has to be cloned. 150: // However subclasses should be aware that writeObject 151: // may be called with a 'null' argument and should 152: // write the proper representation of it. 153: if (o == null) 154: return; 155: 156: PersistenceDelegate pd = getPersistenceDelegate(o.getClass()); 157: 158: accessCounter++; 159: pd.writeObject(o, this); 160: accessCounter--; 161: 162: } 163: 164: /** 165: * Sets the {@link ExceptionListener} instance to be used for reporting 166: * recorable exceptions in the instantiation and initialization sequence. If 167: * the argument is <code>null</code> a default instance will be used that 168: * prints the thrown exception to <code>System.err</code>. 169: */ 170: public void setExceptionListener(ExceptionListener listener) 171: { 172: exceptionListener = (listener != null) 173: ? listener : DefaultExceptionListener.INSTANCE; 174: } 175: 176: /** 177: * Returns the currently active {@link ExceptionListener} instance. 178: */ 179: public ExceptionListener getExceptionListener() 180: { 181: return exceptionListener; 182: } 183: 184: public PersistenceDelegate getPersistenceDelegate(Class<?> type) 185: { 186: // This is not specified but the JDK behaves like this. 187: if (type == null) 188: return fakePersistenceDelegate; 189: 190: // Treats all array classes in the same way and assigns 191: // them a shared PersistenceDelegate implementation tailored 192: // for array instantation and initialization. 193: if (type.isArray()) 194: return (PersistenceDelegate) delegates.get(Object[].class); 195: 196: PersistenceDelegate pd = (PersistenceDelegate) delegates.get(type); 197: 198: return (pd != null) ? pd : (PersistenceDelegate) defaultPersistenceDelegate; 199: } 200: 201: /** 202: * Sets the {@link PersistenceDelegate} instance for the given class. 203: * <p> 204: * Note: Throws a <code>NullPointerException</code> if the argument is 205: * <code>null</code>. 206: * </p> 207: * <p> 208: * Note: Silently ignores PersistenceDelegates for Array types and primitive 209: * wrapper classes. 210: * </p> 211: * <p> 212: * Note: Although this method is not declared <code>static</code> changes to 213: * the {@link PersistenceDelegate}s affect <strong>all</strong> 214: * {@link Encoder} instances. <strong>In this implementation</strong> the 215: * access is thread safe. 216: * </p> 217: */ 218: public void setPersistenceDelegate(Class<?> type, 219: PersistenceDelegate delegate) 220: { 221: // If the argument is null this will cause a NullPointerException 222: // which is expected behavior. 223: 224: // This makes custom PDs for array, primitive types and their wrappers 225: // impossible but this is how the JDK behaves. 226: if (type.isArray() || type.isPrimitive() || type == Boolean.class 227: || type == Byte.class || type == Short.class || type == Integer.class 228: || type == Long.class || type == Float.class || type == Double.class) 229: return; 230: 231: synchronized (delegates) 232: { 233: delegates.put(type, delegate); 234: } 235: 236: } 237: 238: public Object remove(Object oldInstance) 239: { 240: return candidates.remove(oldInstance); 241: } 242: 243: /** 244: * Returns the replacement object which has been created by the encoder during 245: * the instantiation sequence or <code>null</code> if the object has not 246: * been processed yet. 247: * <p> 248: * Note: The <code>String</code> class acts as an endpoint for the 249: * inherently recursive algorithm of the {@link Encoder}. Therefore instances 250: * of <code>String</code> will always be returned by this method. In other 251: * words the assertion: <code> 252: * assert (anyEncoder.get(anyString) == anyString) 253: * </code< 254: * will always hold.</p> 255: * 256: * <p>Note: If <code>null</code> is requested, the result will 257: * always be <code>null</code>.</p> 258: */ 259: public Object get(Object oldInstance) 260: { 261: // String instances are handled in a special way. 262: // No one knows why this is not officially specified 263: // because this is a rather important design decision. 264: return (oldInstance == null) ? null : 265: (oldInstance.getClass() == String.class) ? 266: oldInstance : candidates.get(oldInstance); 267: } 268: 269: /** 270: * <p> 271: * Note: If you call this method not from within an object instantiation and 272: * initialization sequence it will be silently ignored. 273: * </p> 274: */ 275: public void writeStatement(Statement stmt) 276: { 277: // Silently ignore out of bounds calls. 278: if (accessCounter <= 0) 279: return; 280: 281: Object target = stmt.getTarget(); 282: 283: Object newTarget = get(target); 284: if (newTarget == null) 285: { 286: writeObject(target); 287: newTarget = get(target); 288: } 289: 290: Object[] args = stmt.getArguments(); 291: Object[] newArgs = new Object[args.length]; 292: 293: for (int i = 0; i < args.length; i++) 294: { 295: newArgs[i] = get(args[i]); 296: if (newArgs[i] == null || isImmutableType(args[i].getClass())) 297: { 298: writeObject(args[i]); 299: newArgs[i] = get(args[i]); 300: } 301: } 302: 303: Statement newStmt = new Statement(newTarget, stmt.getMethodName(), newArgs); 304: 305: try 306: { 307: newStmt.execute(); 308: } 309: catch (Exception e) 310: { 311: exceptionListener.exceptionThrown(e); 312: } 313: 314: } 315: 316: /** 317: * <p> 318: * Note: If you call this method not from within an object instantiation and 319: * initialization sequence it will be silently ignored. 320: * </p> 321: */ 322: public void writeExpression(Expression expr) 323: { 324: // Silently ignore out of bounds calls. 325: if (accessCounter <= 0) 326: return; 327: 328: Object target = expr.getTarget(); 329: Object value = null; 330: Object newValue = null; 331: 332: try 333: { 334: value = expr.getValue(); 335: } 336: catch (Exception e) 337: { 338: exceptionListener.exceptionThrown(e); 339: return; 340: } 341: 342: 343: newValue = get(value); 344: 345: if (newValue == null) 346: { 347: Object newTarget = get(target); 348: if (newTarget == null) 349: { 350: writeObject(target); 351: newTarget = get(target); 352: 353: // May happen if exception was thrown. 354: if (newTarget == null) 355: { 356: return; 357: } 358: } 359: 360: Object[] args = expr.getArguments(); 361: Object[] newArgs = new Object[args.length]; 362: 363: for (int i = 0; i < args.length; i++) 364: { 365: newArgs[i] = get(args[i]); 366: if (newArgs[i] == null || isImmutableType(args[i].getClass())) 367: { 368: writeObject(args[i]); 369: newArgs[i] = get(args[i]); 370: } 371: } 372: 373: Expression newExpr = new Expression(newTarget, expr.getMethodName(), 374: newArgs); 375: 376: // Fakes the result of Class.forName(<primitiveType>) to make it possible 377: // to hand such a type to the encoding process. 378: if (value instanceof Class && ((Class) value).isPrimitive()) 379: newExpr.setValue(value); 380: 381: // Instantiates the new object. 382: try 383: { 384: newValue = newExpr.getValue(); 385: 386: candidates.put(value, newValue); 387: } 388: catch (Exception e) 389: { 390: exceptionListener.exceptionThrown(e); 391: 392: return; 393: } 394: 395: writeObject(value); 396: 397: } 398: else if(value.getClass() == String.class || value.getClass() == Class.class) 399: { 400: writeObject(value); 401: } 402: 403: } 404: 405: /** Returns whether the given class is an immutable 406: * type which has to be handled differently when serializing it. 407: * 408: * <p>Immutable objects always have to be instantiated instead of 409: * modifying an existing instance.</p> 410: * 411: * @param type The class to test. 412: * @return Whether the first argument is an immutable type. 413: */ 414: boolean isImmutableType(Class type) 415: { 416: return type == String.class || type == Class.class 417: || type == Integer.class || type == Boolean.class 418: || type == Byte.class || type == Short.class 419: || type == Long.class || type == Float.class 420: || type == Double.class; 421: } 422: 423: /** Sets the stream candidate for a given object. 424: * 425: * @param oldObject The object given to the encoder. 426: * @param newObject The object the encoder generated. 427: */ 428: void putCandidate(Object oldObject, Object newObject) 429: { 430: candidates.put(oldObject, newObject); 431: } 432: 433: }
GNU Classpath (0.95) |