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 }