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