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