Source for java.beans.Encoder

   1: /* Encoder.java
   2:  Copyright (C) 2005 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.encoder.ArrayPersistenceDelegate;
  42: import gnu.java.beans.encoder.ClassPersistenceDelegate;
  43: import gnu.java.beans.encoder.CollectionPersistenceDelegate;
  44: import gnu.java.beans.encoder.MapPersistenceDelegate;
  45: import gnu.java.beans.encoder.PrimitivePersistenceDelegate;
  46: 
  47: import java.util.ArrayList;
  48: import java.util.HashMap;
  49: import java.util.HashSet;
  50: import java.util.IdentityHashMap;
  51: import java.util.LinkedHashSet;
  52: import java.util.LinkedList;
  53: import java.util.TreeMap;
  54: import java.util.TreeSet;
  55: import java.util.Vector;
  56: 
  57: /**
  58:  * @author Robert Schuster (robertschuster@fsfe.org)
  59:  * @since 1.4
  60:  */
  61: public class Encoder
  62: {
  63: 
  64:   /**
  65:    * An internal DefaultPersistenceDelegate instance that is used for every
  66:    * class that does not a have a special special PersistenceDelegate.
  67:    */
  68:   private static PersistenceDelegate defaultPersistenceDelegate;
  69: 
  70:   private static PersistenceDelegate fakePersistenceDelegate;
  71: 
  72:   /**
  73:    * Stores the relation Class->PersistenceDelegate.
  74:    */
  75:   private static HashMap delegates = new HashMap();
  76: 
  77:   /**
  78:    * Stores the relation oldInstance->newInstance
  79:    */
  80:   private IdentityHashMap candidates = new IdentityHashMap();
  81: 
  82:   private ExceptionListener exceptionListener;
  83: 
  84:   /**
  85:    * A simple number that is used to restrict the access to writeExpression and
  86:    * writeStatement. The rule is that both methods should only be used when an
  87:    * object is written to the stream (= writeObject). Therefore accessCounter is
  88:    * incremented just before the call to writeObject and decremented afterwards.
  89:    * Then writeStatement and writeExpression allow execution only if
  90:    * accessCounter is bigger than zero.
  91:    */
  92:   private int accessCounter = 0;
  93: 
  94:   public Encoder()
  95:   {
  96:     setupDefaultPersistenceDelegates();
  97: 
  98:     setExceptionListener(null);
  99:   }
 100: 
 101:   /**
 102:    * Sets up a bunch of {@link PersistenceDelegate} instances which are needed
 103:    * for the basic working of a {@link Encoder}s.
 104:    */
 105:   private static void setupDefaultPersistenceDelegates()
 106:   {
 107:     synchronized (delegates)
 108:       {
 109:         if (defaultPersistenceDelegate != null)
 110:           return;
 111: 
 112:         delegates.put(Class.class, new ClassPersistenceDelegate());
 113: 
 114:         PersistenceDelegate pd = new PrimitivePersistenceDelegate();
 115:         delegates.put(Boolean.class, pd);
 116:         delegates.put(Byte.class, pd);
 117:         delegates.put(Short.class, pd);
 118:         delegates.put(Integer.class, pd);
 119:         delegates.put(Long.class, pd);
 120:         delegates.put(Float.class, pd);
 121:         delegates.put(Double.class, pd);
 122: 
 123:         delegates.put(Object[].class, new ArrayPersistenceDelegate());
 124: 
 125:         pd = new CollectionPersistenceDelegate();
 126:         delegates.put(ArrayList.class, pd);
 127:         delegates.put(LinkedList.class, pd);
 128:         delegates.put(Vector.class, pd);
 129:         delegates.put(HashSet.class, pd);
 130:         delegates.put(LinkedHashSet.class, pd);
 131:         delegates.put(TreeSet.class, pd);
 132:         
 133:         pd = new MapPersistenceDelegate();
 134:         delegates.put(HashMap.class, pd);
 135:         delegates.put(TreeMap.class, pd);
 136:         delegates.put(java.util.Hashtable.class, pd);
 137:         delegates.put(java.util.IdentityHashMap.class, pd);
 138:         
 139:         delegates.put(java.util.LinkedHashMap.class, pd);
 140:         delegates.put(java.util.Properties.class, pd);
 141: 
 142:         delegates.put(java.awt.RenderingHints.class, pd);
 143:         delegates.put(java.util.WeakHashMap.class, pd);
 144:         delegates.put(javax.swing.UIDefaults.class, pd);
 145:         
 146:         // TODO: These classes need to be implemented first
 147:         //delegates.put(java.security.AuthProvider.class, pd);
 148:         //delegates.put(java.util.concurrent.ConcurrentHashMap.class, pd);
 149:         //delegates.put(java.util.EnumMap.class, pd);
 150:         //delegates.put(javax.management.openmbean.TabularDataSupport.class, pd);
 151:         
 152:         defaultPersistenceDelegate = new DefaultPersistenceDelegate();
 153:         delegates.put(Object.class, defaultPersistenceDelegate);
 154: 
 155:         // Creates a PersistenceDelegate implementation which is
 156:         // returned for 'null'. In practice this instance is
 157:         // not used in any way and is just here to be compatible
 158:         // with the reference implementation which returns a
 159:         // similar instance when calling getPersistenceDelegate(null) .
 160:         fakePersistenceDelegate = new PersistenceDelegate()
 161:         {
 162:           protected Expression instantiate(Object o, Encoder e)
 163:           {
 164:             return null;
 165:           }
 166:         };
 167: 
 168:       }
 169:   }
 170: 
 171:   protected void writeObject(Object o)
 172:   {
 173:     // 'null' has no PersistenceDelegate and will not
 174:     // create an Expression which has to be cloned.
 175:     // However subclasses should be aware that writeObject
 176:     // may be called with a 'null' argument and should
 177:     // write the proper representation of it.
 178:     if (o == null)
 179:       return;
 180: 
 181:     PersistenceDelegate pd = getPersistenceDelegate(o.getClass());
 182: 
 183:     accessCounter++;
 184:     pd.writeObject(o, this);
 185:     accessCounter--;
 186:     
 187:   }
 188: 
 189:   /**
 190:    * Sets the {@link ExceptionListener} instance to be used for reporting
 191:    * recorable exceptions in the instantiation and initialization sequence. If
 192:    * the argument is <code>null</code> a default instance will be used that
 193:    * prints the thrown exception to <code>System.err</code>.
 194:    */
 195:   public void setExceptionListener(ExceptionListener listener)
 196:   {
 197:     exceptionListener = (listener != null) ? listener : new ExceptionListener()
 198:     {
 199:       public void exceptionThrown(Exception e)
 200:       {
 201:         System.err.println("exception thrown: " + e);
 202:         e.printStackTrace();
 203:       }
 204:     };
 205:   }
 206: 
 207:   /**
 208:    * Returns the currently active {@link ExceptionListener} instance.
 209:    */
 210:   public ExceptionListener getExceptionListener()
 211:   {
 212:     return exceptionListener;
 213:   }
 214: 
 215:   public PersistenceDelegate getPersistenceDelegate(Class type)
 216:   {
 217:     // This is not specified but the JDK behaves like this.
 218:     if (type == null)
 219:       return fakePersistenceDelegate;
 220: 
 221:     // Treats all array classes in the same way and assigns
 222:     // them a shared PersistenceDelegate implementation tailored
 223:     // for array instantation and initialization.
 224:     if (type.isArray())
 225:       return (PersistenceDelegate) delegates.get(Object[].class);
 226: 
 227:     PersistenceDelegate pd = (PersistenceDelegate) delegates.get(type);
 228: 
 229:     return (pd != null) ? pd : (PersistenceDelegate) defaultPersistenceDelegate;
 230:   }
 231: 
 232:   /**
 233:    * Sets the {@link PersistenceDelegate} instance for the given class.
 234:    * <p>
 235:    * Note: Throws a <code>NullPointerException</code> if the argument is
 236:    * <code>null</code>.
 237:    * </p>
 238:    * <p>
 239:    * Note: Silently ignores PersistenceDelegates for Array types and primitive
 240:    * wrapper classes.
 241:    * </p>
 242:    * <p>
 243:    * Note: Although this method is not declared <code>static</code> changes to
 244:    * the {@link PersistenceDelegate}s affect <strong>all</strong>
 245:    * {@link Encoder} instances. <strong>In this implementation</strong> the
 246:    * access is thread safe.
 247:    * </p>
 248:    */
 249:   public void setPersistenceDelegate(Class type, PersistenceDelegate delegate)
 250:   {
 251:     // If the argument is null this will cause a NullPointerException
 252:     // which is expected behavior.
 253: 
 254:     // This makes custom PDs for array, primitive types and their wrappers
 255:     // impossible but this is how the JDK behaves.
 256:     if (type.isArray() || type.isPrimitive() || type == Boolean.class
 257:         || type == Byte.class || type == Short.class || type == Integer.class
 258:         || type == Long.class || type == Float.class || type == Double.class)
 259:       return;
 260: 
 261:     synchronized (delegates)
 262:       {
 263:         delegates.put(type, delegate);
 264:       }
 265: 
 266:   }
 267: 
 268:   public Object remove(Object oldInstance)
 269:   {
 270:     return candidates.remove(oldInstance);
 271:   }
 272: 
 273:   /**
 274:    * Returns the replacement object which has been created by the encoder during
 275:    * the instantiation sequence or <code>null</code> if the object has not
 276:    * been processed yet.
 277:    * <p>
 278:    * Note: The <code>String</code> class acts as an endpoint for the
 279:    * inherently recursive algorithm of the {@link Encoder}. Therefore instances
 280:    * of <code>String</code> will always be returned by this method. In other
 281:    * words the assertion: <code>
 282:    * assert (anyEncoder.get(anyString) == anyString)
 283:    * </code<
 284:    * will always hold.</p>
 285:    *
 286:    * <p>Note: If <code>null</code> is requested, the result will
 287:    * always be <code>null</code>.</p>
 288:    */
 289:   public Object get(Object oldInstance)
 290:   {
 291:     // String instances are handled in a special way.
 292:     // No one knows why this is not officially specified
 293:     // because this is a rather important design decision.
 294:     return (oldInstance == null) ? null : 
 295:              (oldInstance.getClass() == String.class) ?
 296:                oldInstance : candidates.get(oldInstance);
 297:   }
 298: 
 299:   /**
 300:    * <p>
 301:    * Note: If you call this method not from within an object instantiation and
 302:    * initialization sequence it will be silently ignored.
 303:    * </p>
 304:    */
 305:   public void writeStatement(Statement stmt)
 306:   {
 307:     // Silently ignore out of bounds calls.
 308:     if (accessCounter <= 0)
 309:       return;
 310: 
 311:     Object target = stmt.getTarget();
 312: 
 313:     Object newTarget = get(target);
 314:     if (newTarget == null)
 315:       {
 316:         writeObject(target);
 317:         newTarget = get(target);
 318:       }
 319: 
 320:     Object[] args = stmt.getArguments();
 321:     Object[] newArgs = new Object[args.length];
 322: 
 323:     for (int i = 0; i < args.length; i++)
 324:       {
 325:         newArgs[i] = get(args[i]);
 326:         if (newArgs[i] == null || isImmutableType(args[i].getClass()))
 327:           {
 328:             writeObject(args[i]);
 329:             newArgs[i] = get(args[i]);
 330:           }
 331:       }
 332: 
 333:     Statement newStmt = new Statement(newTarget, stmt.getMethodName(), newArgs);
 334: 
 335:     try
 336:       {
 337:         newStmt.execute();
 338:       }
 339:     catch (Exception e)
 340:       {
 341:         exceptionListener.exceptionThrown(e);
 342:       }
 343: 
 344:   }
 345: 
 346:   /**
 347:    * <p>
 348:    * Note: If you call this method not from within an object instantiation and
 349:    * initialization sequence it will be silently ignored.
 350:    * </p>
 351:    */
 352:   public void writeExpression(Expression expr)
 353:   {
 354:     // Silently ignore out of bounds calls.
 355:     if (accessCounter <= 0)
 356:       return;
 357: 
 358:     Object target = expr.getTarget();
 359:     Object value = null;
 360:     Object newValue = null;
 361: 
 362:     try
 363:       {
 364:         value = expr.getValue();
 365:       }
 366:     catch (Exception e)
 367:       {
 368:         exceptionListener.exceptionThrown(e);
 369:         return;
 370:       }
 371:     
 372:     
 373:     newValue = get(value);
 374: 
 375:     if (newValue == null)
 376:       {
 377:         Object newTarget = get(target);
 378:         if (newTarget == null)
 379:           {
 380:             writeObject(target);
 381:             newTarget = get(target);
 382: 
 383:             // May happen if exception was thrown.
 384:             if (newTarget == null)
 385:               {
 386:                 return;
 387:               }
 388:           }
 389: 
 390:         Object[] args = expr.getArguments();
 391:         Object[] newArgs = new Object[args.length];
 392: 
 393:         for (int i = 0; i < args.length; i++)
 394:           {
 395:             newArgs[i] = get(args[i]);
 396:             if (newArgs[i] == null || isImmutableType(args[i].getClass()))
 397:               {
 398:                 writeObject(args[i]);
 399:                 newArgs[i] = get(args[i]);
 400:               }
 401:           }
 402:         
 403:         Expression newExpr = new Expression(newTarget, expr.getMethodName(),
 404:                                             newArgs);
 405:         
 406:         // Fakes the result of Class.forName(<primitiveType>) to make it possible
 407:         // to hand such a type to the encoding process.
 408:         if (value instanceof Class && ((Class) value).isPrimitive())
 409:           newExpr.setValue(value);
 410:         
 411:         // Instantiates the new object.
 412:         try
 413:           {
 414:             newValue = newExpr.getValue();
 415: 
 416:             candidates.put(value, newValue);
 417:           }
 418:         catch (Exception e)
 419:           {
 420:             exceptionListener.exceptionThrown(e);
 421:             
 422:             return;
 423:           }
 424:         
 425:         writeObject(value);
 426: 
 427:       }
 428:     else if(value.getClass() == String.class || value.getClass() == Class.class)
 429:       {
 430:         writeObject(value);
 431:       }
 432: 
 433:   }
 434: 
 435:   /** Returns whether the given class is an immutable
 436:    * type which has to be handled differently when serializing it.
 437:    * 
 438:    * <p>Immutable objects always have to be instantiated instead of
 439:    * modifying an existing instance.</p>
 440:    * 
 441:    * @param type The class to test.
 442:    * @return Whether the first argument is an immutable type.
 443:    */
 444:   boolean isImmutableType(Class type)
 445:   {
 446:     return type == String.class || type == Class.class
 447:       || type == Integer.class || type == Boolean.class
 448:       || type == Byte.class || type == Short.class
 449:       || type == Long.class || type == Float.class
 450:       || type == Double.class;
 451:   }
 452:   
 453:   /** Sets the stream candidate for a given object.
 454:    * 
 455:    * @param oldObject The object given to the encoder.
 456:    * @param newObject The object the encoder generated.
 457:    */
 458:   void putCandidate(Object oldObject, Object newObject)
 459:   {
 460:     candidates.put(oldObject, newObject);
 461:   }
 462:   
 463: }