Source for java.beans.Encoder

   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: }