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 }