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, PersistenceDelegate delegate) 219: { 220: // If the argument is null this will cause a NullPointerException 221: // which is expected behavior. 222: 223: // This makes custom PDs for array, primitive types and their wrappers 224: // impossible but this is how the JDK behaves. 225: if (type.isArray() || type.isPrimitive() || type == Boolean.class 226: || type == Byte.class || type == Short.class || type == Integer.class 227: || type == Long.class || type == Float.class || type == Double.class) 228: return; 229: 230: synchronized (delegates) 231: { 232: delegates.put(type, delegate); 233: } 234: 235: } 236: 237: public Object remove(Object oldInstance) 238: { 239: return candidates.remove(oldInstance); 240: } 241: 242: /** 243: * Returns the replacement object which has been created by the encoder during 244: * the instantiation sequence or <code>null</code> if the object has not 245: * been processed yet. 246: * <p> 247: * Note: The <code>String</code> class acts as an endpoint for the 248: * inherently recursive algorithm of the {@link Encoder}. Therefore instances 249: * of <code>String</code> will always be returned by this method. In other 250: * words the assertion: <code> 251: * assert (anyEncoder.get(anyString) == anyString) 252: * </code< 253: * will always hold.</p> 254: * 255: * <p>Note: If <code>null</code> is requested, the result will 256: * always be <code>null</code>.</p> 257: */ 258: public Object get(Object oldInstance) 259: { 260: // String instances are handled in a special way. 261: // No one knows why this is not officially specified 262: // because this is a rather important design decision. 263: return (oldInstance == null) ? null : 264: (oldInstance.getClass() == String.class) ? 265: oldInstance : candidates.get(oldInstance); 266: } 267: 268: /** 269: * <p> 270: * Note: If you call this method not from within an object instantiation and 271: * initialization sequence it will be silently ignored. 272: * </p> 273: */ 274: public void writeStatement(Statement stmt) 275: { 276: // Silently ignore out of bounds calls. 277: if (accessCounter <= 0) 278: return; 279: 280: Object target = stmt.getTarget(); 281: 282: Object newTarget = get(target); 283: if (newTarget == null) 284: { 285: writeObject(target); 286: newTarget = get(target); 287: } 288: 289: Object[] args = stmt.getArguments(); 290: Object[] newArgs = new Object[args.length]; 291: 292: for (int i = 0; i < args.length; i++) 293: { 294: newArgs[i] = get(args[i]); 295: if (newArgs[i] == null || isImmutableType(args[i].getClass())) 296: { 297: writeObject(args[i]); 298: newArgs[i] = get(args[i]); 299: } 300: } 301: 302: Statement newStmt = new Statement(newTarget, stmt.getMethodName(), newArgs); 303: 304: try 305: { 306: newStmt.execute(); 307: } 308: catch (Exception e) 309: { 310: exceptionListener.exceptionThrown(e); 311: } 312: 313: } 314: 315: /** 316: * <p> 317: * Note: If you call this method not from within an object instantiation and 318: * initialization sequence it will be silently ignored. 319: * </p> 320: */ 321: public void writeExpression(Expression expr) 322: { 323: // Silently ignore out of bounds calls. 324: if (accessCounter <= 0) 325: return; 326: 327: Object target = expr.getTarget(); 328: Object value = null; 329: Object newValue = null; 330: 331: try 332: { 333: value = expr.getValue(); 334: } 335: catch (Exception e) 336: { 337: exceptionListener.exceptionThrown(e); 338: return; 339: } 340: 341: 342: newValue = get(value); 343: 344: if (newValue == null) 345: { 346: Object newTarget = get(target); 347: if (newTarget == null) 348: { 349: writeObject(target); 350: newTarget = get(target); 351: 352: // May happen if exception was thrown. 353: if (newTarget == null) 354: { 355: return; 356: } 357: } 358: 359: Object[] args = expr.getArguments(); 360: Object[] newArgs = new Object[args.length]; 361: 362: for (int i = 0; i < args.length; i++) 363: { 364: newArgs[i] = get(args[i]); 365: if (newArgs[i] == null || isImmutableType(args[i].getClass())) 366: { 367: writeObject(args[i]); 368: newArgs[i] = get(args[i]); 369: } 370: } 371: 372: Expression newExpr = new Expression(newTarget, expr.getMethodName(), 373: newArgs); 374: 375: // Fakes the result of Class.forName(<primitiveType>) to make it possible 376: // to hand such a type to the encoding process. 377: if (value instanceof Class && ((Class) value).isPrimitive()) 378: newExpr.setValue(value); 379: 380: // Instantiates the new object. 381: try 382: { 383: newValue = newExpr.getValue(); 384: 385: candidates.put(value, newValue); 386: } 387: catch (Exception e) 388: { 389: exceptionListener.exceptionThrown(e); 390: 391: return; 392: } 393: 394: writeObject(value); 395: 396: } 397: else if(value.getClass() == String.class || value.getClass() == Class.class) 398: { 399: writeObject(value); 400: } 401: 402: } 403: 404: /** Returns whether the given class is an immutable 405: * type which has to be handled differently when serializing it. 406: * 407: * <p>Immutable objects always have to be instantiated instead of 408: * modifying an existing instance.</p> 409: * 410: * @param type The class to test. 411: * @return Whether the first argument is an immutable type. 412: */ 413: boolean isImmutableType(Class type) 414: { 415: return type == String.class || type == Class.class 416: || type == Integer.class || type == Boolean.class 417: || type == Byte.class || type == Short.class 418: || type == Long.class || type == Float.class 419: || type == Double.class; 420: } 421: 422: /** Sets the stream candidate for a given object. 423: * 424: * @param oldObject The object given to the encoder. 425: * @param newObject The object the encoder generated. 426: */ 427: void putCandidate(Object oldObject, Object newObject) 428: { 429: candidates.put(oldObject, newObject); 430: } 431: 432: }