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("{} property '{}' not set. (errors)", _pojoClass.getName(), 
191                             setter.getPropertyName());
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     public static class Setter
227     {
228         protected String _propertyName;
229         protected Method _method;
230         protected NumberType _numberType;
231         protected Class<?> _type;
232         protected Class<?> _componentType;
233         
234         public Setter(String propertyName, Method method)
235         {
236             _propertyName = propertyName;
237             _method = method;
238             _type = method.getParameterTypes()[0];
239             _numberType = (NumberType)__numberTypes.get(_type);
240             if(_numberType==null && _type.isArray())
241             {
242                 _componentType = _type.getComponentType();
243                 _numberType = (NumberType)__numberTypes.get(_componentType);
244             }
245         }
246         
247         public String getPropertyName()
248         {
249             return _propertyName;
250         }
251         
252         public Method getMethod()
253         {
254             return _method;
255         }
256         
257         public NumberType getNumberType()
258         {
259             return _numberType;
260         }
261         
262         public Class<?> getType()
263         {
264             return _type;
265         }
266         
267         public Class<?> getComponentType()
268         {
269             return _componentType;
270         }
271         
272         public boolean isPropertyNumber()
273         {
274             return _numberType!=null;
275         }
276         
277         public void invoke(Object obj, Object value) throws IllegalArgumentException, 
278         IllegalAccessException, InvocationTargetException
279         {
280             if(value==null)
281                 _method.invoke(obj, NULL_ARG);
282             else
283                 invokeObject(obj, value);
284         }
285         
286         protected void invokeObject(Object obj, Object value) throws IllegalArgumentException, 
287             IllegalAccessException, InvocationTargetException
288         {
289             if(_numberType!=null && value instanceof Number)
290                 _method.invoke(obj, new Object[]{_numberType.getActualValue((Number)value)});
291             else if(_componentType!=null && value.getClass().isArray())
292             {
293                 if(_numberType==null)
294                 {
295                     int len = Array.getLength(value);
296                     Object array = Array.newInstance(_componentType, len);
297                     try
298                     {
299                         System.arraycopy(value, 0, array, 0, len);
300                     }
301                     catch(Exception e)
302                     {                        
303                         // unusual array with multiple types
304                         Log.ignore(e);
305                         _method.invoke(obj, new Object[]{value});
306                         return;
307                     }                    
308                     _method.invoke(obj, new Object[]{array});
309                 }
310                 else
311                 {
312                     Object[] old = (Object[])value;
313                     Object array = Array.newInstance(_componentType, old.length);
314                     try
315                     {
316                         for(int i=0; i<old.length; i++)
317                             Array.set(array, i, _numberType.getActualValue((Number)old[i]));
318                     }
319                     catch(Exception e)
320                     {                        
321                         // unusual array with multiple types
322                         Log.ignore(e);
323                         _method.invoke(obj, new Object[]{value});
324                         return;
325                     }
326                     _method.invoke(obj, new Object[]{array});
327                 }
328             }
329             else
330                 _method.invoke(obj, new Object[]{value});
331         }
332     }
333     
334     public interface NumberType
335     {        
336         public Object getActualValue(Number number);     
337     }
338     
339     public static final NumberType SHORT = new NumberType()
340     {
341         public Object getActualValue(Number number)
342         {            
343             return new Short(number.shortValue());
344         } 
345     };
346 
347     public static final NumberType INTEGER = new NumberType()
348     {
349         public Object getActualValue(Number number)
350         {            
351             return new Integer(number.intValue());
352         }
353     };
354     
355     public static final NumberType FLOAT = new NumberType()
356     {
357         public Object getActualValue(Number number)
358         {            
359             return new Float(number.floatValue());
360         }      
361     };
362 
363     public static final NumberType LONG = new NumberType()
364     {
365         public Object getActualValue(Number number)
366         {            
367             return number instanceof Long ? number : new Long(number.longValue());
368         }     
369     };
370 
371     public static final NumberType DOUBLE = new NumberType()
372     {
373         public Object getActualValue(Number number)
374         {            
375             return number instanceof Double ? number : new Double(number.doubleValue());
376         }       
377     };
378 
379     static
380     {
381         __numberTypes.put(Short.class, SHORT);
382         __numberTypes.put(Short.TYPE, SHORT);
383         __numberTypes.put(Integer.class, INTEGER);
384         __numberTypes.put(Integer.TYPE, INTEGER);
385         __numberTypes.put(Long.class, LONG);
386         __numberTypes.put(Long.TYPE, LONG);
387         __numberTypes.put(Float.class, FLOAT);
388         __numberTypes.put(Float.TYPE, FLOAT);
389         __numberTypes.put(Double.class, DOUBLE);
390         __numberTypes.put(Double.TYPE, DOUBLE);
391     }
392 }