001/* Encoder.java
002 Copyright (C) 2005, 2006 Free Software Foundation, Inc.
003
004 This file is part of GNU Classpath.
005
006 GNU Classpath is free software; you can redistribute it and/or modify
007 it under the terms of the GNU General Public License as published by
008 the Free Software Foundation; either version 2, or (at your option)
009 any later version.
010
011 GNU Classpath is distributed in the hope that it will be useful, but
012 WITHOUT ANY WARRANTY; without even the implied warranty of
013 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014 General Public License for more details.
015
016 You should have received a copy of the GNU General Public License
017 along with GNU Classpath; see the file COPYING.  If not, write to the
018 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
019 02110-1301 USA.
020
021 Linking this library statically or dynamically with other modules is
022 making a combined work based on this library.  Thus, the terms and
023 conditions of the GNU General Public License cover the whole
024 combination.
025
026 As a special exception, the copyright holders of this library give you
027 permission to link this library with independent modules to produce an
028 executable, regardless of the license terms of these independent
029 modules, and to copy and distribute the resulting executable under
030 terms of your choice, provided that you also meet, for each linked
031 independent module, the terms and conditions of the license of that
032 module.  An independent module is a module which is not derived from
033 or based on this library.  If you modify this library, you may extend
034 this exception to your version of the library, but you are not
035 obligated to do so.  If you do not wish to do so, delete this
036 exception statement from your version. */
037
038
039package java.beans;
040
041import gnu.java.beans.DefaultExceptionListener;
042import gnu.java.beans.encoder.ArrayPersistenceDelegate;
043import gnu.java.beans.encoder.ClassPersistenceDelegate;
044import gnu.java.beans.encoder.CollectionPersistenceDelegate;
045import gnu.java.beans.encoder.MapPersistenceDelegate;
046import gnu.java.beans.encoder.PrimitivePersistenceDelegate;
047
048import java.util.AbstractCollection;
049import java.util.HashMap;
050import java.util.IdentityHashMap;
051
052/**
053 * @author Robert Schuster (robertschuster@fsfe.org)
054 * @since 1.4
055 */
056public class Encoder
057{
058
059  /**
060   * An internal DefaultPersistenceDelegate instance that is used for every
061   * class that does not a have a special special PersistenceDelegate.
062   */
063  private static PersistenceDelegate defaultPersistenceDelegate;
064
065  private static PersistenceDelegate fakePersistenceDelegate;
066
067  /**
068   * Stores the relation Class->PersistenceDelegate.
069   */
070  private static HashMap delegates = new HashMap();
071
072  /**
073   * Stores the relation oldInstance->newInstance
074   */
075  private IdentityHashMap candidates = new IdentityHashMap();
076
077  private ExceptionListener exceptionListener;
078
079  /**
080   * A simple number that is used to restrict the access to writeExpression and
081   * writeStatement. The rule is that both methods should only be used when an
082   * object is written to the stream (= writeObject). Therefore accessCounter is
083   * incremented just before the call to writeObject and decremented afterwards.
084   * Then writeStatement and writeExpression allow execution only if
085   * accessCounter is bigger than zero.
086   */
087  private int accessCounter = 0;
088
089  public Encoder()
090  {
091    setupDefaultPersistenceDelegates();
092
093    setExceptionListener(null);
094  }
095
096  /**
097   * Sets up a bunch of {@link PersistenceDelegate} instances which are needed
098   * for the basic working of a {@link Encoder}s.
099   */
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 : 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}