View Javadoc

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