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