OSDN Git Service

Merged gcj-eclipse branch to trunk.
[pf3gnuchains/gcc-fork.git] / libjava / classpath / java / beans / Encoder.java
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 }