View Javadoc

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