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    
039    package java.beans;
040    
041    import gnu.java.beans.DefaultExceptionListener;
042    import gnu.java.beans.encoder.ArrayPersistenceDelegate;
043    import gnu.java.beans.encoder.ClassPersistenceDelegate;
044    import gnu.java.beans.encoder.CollectionPersistenceDelegate;
045    import gnu.java.beans.encoder.MapPersistenceDelegate;
046    import gnu.java.beans.encoder.PrimitivePersistenceDelegate;
047    
048    import java.util.AbstractCollection;
049    import java.util.HashMap;
050    import java.util.IdentityHashMap;
051    
052    /**
053     * @author Robert Schuster (robertschuster@fsfe.org)
054     * @since 1.4
055     */
056    public 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 : (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    }