View Javadoc

1   // ========================================================================
2   // Copyright (c) 2009-2009 Mort Bay Consulting Pty. Ltd.
3   // ------------------------------------------------------------------------
4   // All rights reserved. This program and the accompanying materials
5   // are made available under the terms of the Eclipse Public License v1.0
6   // and Apache License v2.0 which accompanies this distribution.
7   // The Eclipse Public License is available at 
8   // http://www.eclipse.org/legal/epl-v10.html
9   // The Apache License v2.0 is available at
10  // http://www.opensource.org/licenses/apache2.0.php
11  // You may elect to redistribute this code under either of these licenses. 
12  // ========================================================================
13  
14  package org.eclipse.jetty.util.ajax;
15  
16  import java.lang.reflect.Array;
17  import java.lang.reflect.InvocationTargetException;
18  import java.lang.reflect.Method;
19  import java.lang.reflect.Modifier;
20  import java.util.Arrays;
21  import java.util.HashMap;
22  import java.util.HashSet;
23  import java.util.Iterator;
24  import java.util.Map;
25  import java.util.Set;
26  
27  import org.eclipse.jetty.util.ajax.JSON.Output;
28  import org.eclipse.jetty.util.log.Log;
29  import org.eclipse.jetty.util.log.Logger;
30  /**
31   * Converts POJOs to JSON and vice versa.
32   * The key difference:
33   *  - returns the actual object from Convertor.fromJSON (JSONObjectConverter returns a Map)
34   *  - the getters/setters are resolved at initialization (JSONObjectConverter resolves it at runtime)
35   *  - correctly sets the number fields
36   * 
37   */
38  public class JSONPojoConvertor implements JSON.Convertor
39  {
40      private static final Logger LOG = Log.getLogger(JSONPojoConvertor.class);
41      public static final Object[] GETTER_ARG = new Object[]{}, NULL_ARG = new Object[]{null};
42      private static final Map<Class<?>, NumberType> __numberTypes = new HashMap<Class<?>, NumberType>();
43      
44      public static NumberType getNumberType(Class<?> clazz)
45      {
46          return __numberTypes.get(clazz);
47      }
48      
49      protected boolean _fromJSON;
50      protected Class<?> _pojoClass;
51      protected Map<String,Method> _getters = new HashMap<String,Method>();
52      protected Map<String,Setter> _setters = new HashMap<String,Setter>();
53      protected Set<String> _excluded;
54  
55      /**
56       * @param pojoClass The class to convert
57       */
58      public JSONPojoConvertor(Class<?> pojoClass)
59      {
60          this(pojoClass, (Set<String>)null, true);
61      }
62  
63      /**
64       * @param pojoClass The class to convert
65       * @param excluded The fields to exclude
66       */
67      public JSONPojoConvertor(Class<?> pojoClass, String[] excluded)
68      {
69          this(pojoClass, new HashSet<String>(Arrays.asList(excluded)), true);
70      }
71  
72      /**
73       * @param pojoClass The class to convert
74       * @param excluded The fields to exclude
75       */
76      public JSONPojoConvertor(Class<?> pojoClass, Set<String> excluded)
77      {
78          this(pojoClass, excluded, true);
79      }
80  
81      /**
82       * @param pojoClass The class to convert
83       * @param excluded The fields to exclude
84       * @param fromJSON If true, add a class field to the JSON
85       */
86      public JSONPojoConvertor(Class<?> pojoClass, Set<String> excluded, boolean fromJSON)
87      {
88          _pojoClass = pojoClass;
89          _excluded = excluded;
90          _fromJSON = fromJSON;
91          init();
92      }    
93  
94      /**
95       * @param pojoClass The class to convert
96       * @param fromJSON If true, add a class field to the JSON
97       */
98      public JSONPojoConvertor(Class<?> pojoClass, boolean fromJSON)
99      {
100         this(pojoClass, (Set<String>)null, fromJSON);
101     }
102     
103     /* ------------------------------------------------------------ */
104     protected void init()
105     {
106         Method[] methods = _pojoClass.getMethods();
107         for (int i=0;i<methods.length;i++)
108         {
109             Method m=methods[i];
110             if (!Modifier.isStatic(m.getModifiers()) && m.getDeclaringClass()!=Object.class)
111             {
112                 String name=m.getName();
113                 switch(m.getParameterTypes().length)
114                 {
115                     case 0:
116                         
117                         if(m.getReturnType()!=null)
118                         {
119                             if (name.startsWith("is") && name.length()>2)
120                                 name=name.substring(2,3).toLowerCase()+name.substring(3);
121                             else if (name.startsWith("get") && name.length()>3)
122                                 name=name.substring(3,4).toLowerCase()+name.substring(4);
123                             else 
124                                 break;
125                             if(includeField(name, m))
126                                 addGetter(name, m);
127                         }
128                         break;
129                     case 1:
130                         if (name.startsWith("set") && name.length()>3)
131                         {
132                             name=name.substring(3,4).toLowerCase()+name.substring(4);
133                             if(includeField(name, m))
134                                 addSetter(name, m);
135                         }
136                         break;                
137                 }
138             }
139         }
140     }
141     
142     /* ------------------------------------------------------------ */
143     protected void addGetter(String name, Method method)
144     {
145         _getters.put(name, method);
146     }
147     
148     /* ------------------------------------------------------------ */
149     protected void addSetter(String name, Method method)
150     {
151         _setters.put(name, new Setter(name, method));
152     }
153     
154     /* ------------------------------------------------------------ */
155     protected Setter getSetter(String name)
156     {
157         return _setters.get(name);
158     }
159 
160     /* ------------------------------------------------------------ */
161     protected boolean includeField(String name, Method m)
162     {
163         return _excluded==null || !_excluded.contains(name);
164     }
165     
166     /* ------------------------------------------------------------ */
167     protected int getExcludedCount()
168     {
169         return _excluded==null ? 0 : _excluded.size();
170     }
171 
172     /* ------------------------------------------------------------ */
173     public Object fromJSON(Map object)
174     {        
175         Object obj = null;
176         try
177         {
178             obj = _pojoClass.newInstance();
179         }
180         catch(Exception e)
181         {
182             // TODO return Map instead?
183             throw new RuntimeException(e);
184         }
185         
186         setProps(obj, object);
187         return obj;
188     }
189     
190     /* ------------------------------------------------------------ */
191     public int setProps(Object obj, Map<?,?> props)
192     {
193         int count = 0;
194         for(Iterator<?> iterator = props.entrySet().iterator(); iterator.hasNext();)
195         {
196             Map.Entry<?, ?> entry = (Map.Entry<?,?>) iterator.next();
197             Setter setter = getSetter((String)entry.getKey());
198             if(setter!=null)
199             {
200                 try
201                 {
202                     setter.invoke(obj, entry.getValue());                    
203                     count++;
204                 }
205                 catch(Exception e)
206                 {
207                     // TODO throw exception?
208                     LOG.warn(_pojoClass.getName()+"#"+setter.getPropertyName()+" not set from "+
209                             (entry.getValue().getClass().getName())+"="+entry.getValue().toString());
210                     log(e);
211                 }
212             }
213         }
214         return count;
215     }
216 
217     /* ------------------------------------------------------------ */
218     public void toJSON(Object obj, Output out)
219     {
220         if(_fromJSON)
221             out.addClass(_pojoClass);
222         for(Map.Entry<String,Method> entry : _getters.entrySet())
223         {            
224             try
225             {
226                 out.add(entry.getKey(), entry.getValue().invoke(obj, GETTER_ARG));                    
227             }
228             catch(Exception e)
229             {
230                 // TODO throw exception?
231                 LOG.warn("{} property '{}' excluded. (errors)", _pojoClass.getName(), 
232                         entry.getKey());
233                 log(e);
234             }
235         }        
236     }
237     
238     /* ------------------------------------------------------------ */
239     protected void log(Throwable t)
240     {
241         LOG.ignore(t);
242     }
243 
244     /* ------------------------------------------------------------ */
245     public static class Setter
246     {
247         protected String _propertyName;
248         protected Method _setter;
249         protected NumberType _numberType;
250         protected Class<?> _type;
251         protected Class<?> _componentType;
252         
253         public Setter(String propertyName, Method method)
254         {
255             _propertyName = propertyName;
256             _setter = method;
257             _type = method.getParameterTypes()[0];
258             _numberType = __numberTypes.get(_type);
259             if(_numberType==null && _type.isArray())
260             {
261                 _componentType = _type.getComponentType();
262                 _numberType = __numberTypes.get(_componentType);
263             }
264         }
265         
266         public String getPropertyName()
267         {
268             return _propertyName;
269         }
270         
271         public Method getMethod()
272         {
273             return _setter;
274         }
275         
276         public NumberType getNumberType()
277         {
278             return _numberType;
279         }
280         
281         public Class<?> getType()
282         {
283             return _type;
284         }
285         
286         public Class<?> getComponentType()
287         {
288             return _componentType;
289         }
290         
291         public boolean isPropertyNumber()
292         {
293             return _numberType!=null;
294         }
295         
296         public void invoke(Object obj, Object value) throws IllegalArgumentException, 
297         IllegalAccessException, InvocationTargetException
298         {
299             if(value==null)
300                 _setter.invoke(obj, NULL_ARG);
301             else
302                 invokeObject(obj, value);
303         }
304         
305         protected void invokeObject(Object obj, Object value) throws IllegalArgumentException, 
306             IllegalAccessException, InvocationTargetException
307         {
308             
309             if (_type.isEnum())
310             {
311                 if (value instanceof Enum)
312                     _setter.invoke(obj, new Object[]{value});
313                 else
314                     _setter.invoke(obj, new Object[]{Enum.valueOf((Class<? extends Enum>)_type,value.toString())});
315             }
316             else if(_numberType!=null && value instanceof Number)
317             {
318                 _setter.invoke(obj, new Object[]{_numberType.getActualValue((Number)value)});
319             }
320             else if (Character.TYPE.equals(_type) || Character.class.equals(_type))
321             {
322                 _setter.invoke(obj, new Object[]{String.valueOf(value).charAt(0)});
323             }
324             else if(_componentType!=null && value.getClass().isArray())
325             {
326                 if(_numberType==null)
327                 {
328                     int len = Array.getLength(value);
329                     Object array = Array.newInstance(_componentType, len);
330                     try
331                     {
332                         System.arraycopy(value, 0, array, 0, len);
333                     }
334                     catch(Exception e)
335                     {                        
336                         // unusual array with multiple types
337                         LOG.ignore(e);
338                         _setter.invoke(obj, new Object[]{value});
339                         return;
340                     }                    
341                     _setter.invoke(obj, new Object[]{array});
342                 }
343                 else
344                 {
345                     Object[] old = (Object[])value;
346                     Object array = Array.newInstance(_componentType, old.length);
347                     try
348                     {
349                         for(int i=0; i<old.length; i++)
350                             Array.set(array, i, _numberType.getActualValue((Number)old[i]));
351                     }
352                     catch(Exception e)
353                     {                        
354                         // unusual array with multiple types
355                         LOG.ignore(e);
356                         _setter.invoke(obj, new Object[]{value});
357                         return;
358                     }
359                     _setter.invoke(obj, new Object[]{array});
360                 }
361             }
362             else
363                 _setter.invoke(obj, new Object[]{value});
364         }
365     }
366     
367     public interface NumberType
368     {        
369         public Object getActualValue(Number number);     
370     }
371     
372     public static final NumberType SHORT = new NumberType()
373     {
374         public Object getActualValue(Number number)
375         {            
376             return new Short(number.shortValue());
377         } 
378     };
379 
380     public static final NumberType INTEGER = new NumberType()
381     {
382         public Object getActualValue(Number number)
383         {            
384             return new Integer(number.intValue());
385         }
386     };
387     
388     public static final NumberType FLOAT = new NumberType()
389     {
390         public Object getActualValue(Number number)
391         {            
392             return new Float(number.floatValue());
393         }      
394     };
395 
396     public static final NumberType LONG = new NumberType()
397     {
398         public Object getActualValue(Number number)
399         {            
400             return number instanceof Long ? number : new Long(number.longValue());
401         }     
402     };
403 
404     public static final NumberType DOUBLE = new NumberType()
405     {
406         public Object getActualValue(Number number)
407         {            
408             return number instanceof Double ? number : new Double(number.doubleValue());
409         }       
410     };
411 
412     static
413     {
414         __numberTypes.put(Short.class, SHORT);
415         __numberTypes.put(Short.TYPE, SHORT);
416         __numberTypes.put(Integer.class, INTEGER);
417         __numberTypes.put(Integer.TYPE, INTEGER);
418         __numberTypes.put(Long.class, LONG);
419         __numberTypes.put(Long.TYPE, LONG);
420         __numberTypes.put(Float.class, FLOAT);
421         __numberTypes.put(Float.TYPE, FLOAT);
422         __numberTypes.put(Double.class, DOUBLE);
423         __numberTypes.put(Double.TYPE, DOUBLE);
424     }
425 }