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