Coverage Report - org.apache.shindig.social.core.util.BeanJsonConverter
 
Classes in this File Line Coverage Branch Coverage Complexity
BeanJsonConverter
84%
145/173
88%
99/112
0
BeanJsonConverter$1
N/A
N/A
0
BeanJsonConverter$MethodPair
100%
5/5
N/A
0
 
 1  
 /*
 2  
  * Licensed to the Apache Software Foundation (ASF) under one
 3  
  * or more contributor license agreements. See the NOTICE file
 4  
  * distributed with this work for additional information
 5  
  * regarding copyright ownership. The ASF licenses this file
 6  
  * to you under the Apache License, Version 2.0 (the
 7  
  * "License"); you may not use this file except in compliance
 8  
  * with the License. You may obtain a copy of the License at
 9  
  *
 10  
  *     http://www.apache.org/licenses/LICENSE-2.0
 11  
  *
 12  
  * Unless required by applicable law or agreed to in writing,
 13  
  * software distributed under the License is distributed on an
 14  
  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 15  
  * KIND, either express or implied. See the License for the
 16  
  * specific language governing permissions and limitations under the License.
 17  
  */
 18  
 package org.apache.shindig.social.core.util;
 19  
 
 20  
 import org.apache.shindig.social.core.model.EnumImpl;
 21  
 import org.apache.shindig.social.opensocial.model.Enum;
 22  
 import org.apache.shindig.social.opensocial.service.BeanConverter;
 23  
 
 24  
 import com.google.common.collect.ImmutableSet;
 25  
 import com.google.common.collect.Lists;
 26  
 import com.google.common.collect.Maps;
 27  
 import com.google.common.collect.Sets;
 28  
 import com.google.inject.Inject;
 29  
 import com.google.inject.Injector;
 30  
 
 31  
 import org.joda.time.DateTime;
 32  
 import org.json.JSONArray;
 33  
 import org.json.JSONException;
 34  
 import org.json.JSONObject;
 35  
 
 36  
 import java.lang.reflect.InvocationTargetException;
 37  
 import java.lang.reflect.Method;
 38  
 import java.lang.reflect.ParameterizedType;
 39  
 import java.lang.reflect.Type;
 40  
 import java.util.Date;
 41  
 import java.util.Iterator;
 42  
 import java.util.List;
 43  
 import java.util.Map;
 44  
 import java.util.Map.Entry;
 45  
 import java.util.Set;
 46  
 import java.util.concurrent.ConcurrentHashMap;
 47  
 import java.util.regex.Matcher;
 48  
 import java.util.regex.Pattern;
 49  
 
 50  
 /**
 51  
  * Converts pojos to json objects.
 52  
  * TODO: Replace with standard library
 53  
  */
 54  
 public class BeanJsonConverter implements BeanConverter {
 55  
 
 56  1
   private static final Object[] EMPTY_OBJECT = {};
 57  1
   private static final Set<String> EXCLUDED_FIELDS = ImmutableSet.of("class", "declaringclass");
 58  
   private static final String GETTER_PREFIX  = "get";
 59  
   private static final String SETTER_PREFIX = "set";
 60  
 
 61  
   // Only compute the filtered getters/setters once per-class
 62  1
   private static final ConcurrentHashMap<Class,List<MethodPair>> GETTER_METHODS = Maps.newConcurrentHashMap();
 63  1
   private static final ConcurrentHashMap<Class,List<MethodPair>> SETTER_METHODS = Maps.newConcurrentHashMap();
 64  
 
 65  
   private Injector injector;
 66  
 
 67  
   @Inject
 68  101
   public BeanJsonConverter(Injector injector) {
 69  101
     this.injector = injector;
 70  101
   }
 71  
 
 72  
   public String getContentType() {
 73  21
     return "application/json";
 74  
   }
 75  
 
 76  
   /**
 77  
    * Convert the passed in object to a string.
 78  
    *
 79  
    * @param pojo The object to convert
 80  
    * @return An object whos toString method will return json
 81  
    */
 82  
   public String convertToString(final Object pojo) {
 83  23
     return convertToJson(pojo).toString();
 84  
   }
 85  
 
 86  
   /**
 87  
    * Convert the passed in object to a json object.
 88  
    *
 89  
    * @param pojo The object to convert
 90  
    * @return An object whos toString method will return json
 91  
    */
 92  
   public Object convertToJson(final Object pojo) {
 93  
     try {
 94  29
       return translateObjectToJson(pojo);
 95  0
     } catch (JSONException e) {
 96  0
       throw new RuntimeException("Could not translate " + pojo + " to json", e);
 97  
     }
 98  
   }
 99  
 
 100  
   private Object translateObjectToJson(final Object val) throws JSONException {
 101  397
     if (val instanceof Object[]) {
 102  1
       JSONArray array = new JSONArray();
 103  4
       for (Object asd : (Object[]) val) {
 104  3
         array.put(translateObjectToJson(asd));
 105  
       }
 106  1
       return array;
 107  
 
 108  396
     } else if (val instanceof List) {
 109  35
       JSONArray list = new JSONArray();
 110  35
       for (Object item : (List<?>) val) {
 111  63
         list.put(translateObjectToJson(item));
 112  63
       }
 113  35
       return list;
 114  
 
 115  361
     } else if (val instanceof Map) {
 116  35
       JSONObject map = new JSONObject();
 117  35
       Map<?, ?> originalMap = (Map<?, ?>) val;
 118  
 
 119  35
       for (Entry<?, ?> item : originalMap.entrySet()) {
 120  32
         map.put(item.getKey().toString(), translateObjectToJson(item.getValue()));
 121  32
       }
 122  35
       return map;
 123  
 
 124  326
     } else if (val != null && val.getClass().isEnum()) {
 125  10
       return val.toString();
 126  316
     } else if (val instanceof String
 127  
         || val instanceof Boolean
 128  
         || val instanceof Integer
 129  
         || val instanceof Date
 130  
         || val instanceof Long
 131  
         || val instanceof Float
 132  
         || val instanceof JSONObject
 133  
         || val instanceof JSONArray
 134  
         || val == null) {
 135  246
       return val;
 136  
     }
 137  
 
 138  70
     return convertMethodsToJson(val);
 139  
   }
 140  
 
 141  
   /**
 142  
    * Convert the object to {@link JSONObject} reading Pojo properties
 143  
    *
 144  
    * @param pojo The object to convert
 145  
    * @return A JSONObject representing this pojo
 146  
    */
 147  
   private JSONObject convertMethodsToJson(final Object pojo) {
 148  
     List<MethodPair> availableGetters;
 149  
 
 150  70
     availableGetters = GETTER_METHODS.get(pojo.getClass());
 151  70
     if (availableGetters == null) {
 152  13
       availableGetters = getMatchingMethods(pojo, GETTER_PREFIX);
 153  13
       GETTER_METHODS.putIfAbsent(pojo.getClass(), availableGetters);
 154  
     }
 155  
 
 156  70
     JSONObject toReturn = new JSONObject();
 157  70
     for (MethodPair getter : availableGetters) {
 158  
       try {
 159  870
         Object val = getter.method.invoke(pojo, EMPTY_OBJECT);
 160  870
         if (val != null) {
 161  270
           toReturn.put(getter.fieldName, translateObjectToJson(val));
 162  
         }
 163  0
       } catch (JSONException e) {
 164  0
         throw new RuntimeException(errorMessage(pojo, getter), e);
 165  0
       } catch (IllegalAccessException e) {
 166  0
         throw new RuntimeException(errorMessage(pojo, getter), e);
 167  0
       } catch (InvocationTargetException e) {
 168  0
         throw new RuntimeException(errorMessage(pojo, getter), e);
 169  0
       } catch (IllegalArgumentException e) {
 170  0
         throw new RuntimeException(errorMessage(pojo, getter), e);
 171  870
       }
 172  870
     }
 173  70
     return toReturn;
 174  
   }
 175  
 
 176  
   private static String errorMessage(Object pojo, MethodPair getter) {
 177  0
     return "Could not encode the " + getter.method + " method on "
 178  
         + pojo.getClass().getName();
 179  
   }
 180  
 
 181  316
   private static final class MethodPair {
 182  
     public Method method;
 183  
     public String fieldName;
 184  
 
 185  316
     private MethodPair(final Method method, final String fieldName) {
 186  316
       this.method = method;
 187  316
       this.fieldName = fieldName;
 188  316
     }
 189  
   }
 190  
 
 191  
 
 192  
   private List<MethodPair> getMatchingMethods(Object pojo, String prefix) {
 193  
 
 194  24
     List<MethodPair> availableGetters = Lists.newArrayList();
 195  24
     Method[] methods = pojo.getClass().getMethods();
 196  873
     for (Method method : methods) {
 197  849
       String name = method.getName();
 198  849
       if (!method.getName().startsWith(prefix)) {
 199  520
         continue;
 200  
       }
 201  329
       int prefixlen = prefix.length();
 202  
 
 203  329
       String fieldName = name.substring(prefixlen, prefixlen+1).toLowerCase() +
 204  
           name.substring(prefixlen + 1);
 205  
       
 206  329
       if (EXCLUDED_FIELDS.contains(fieldName.toLowerCase())) {
 207  13
         continue;
 208  
       }
 209  316
       availableGetters.add(new MethodPair(method, fieldName));
 210  
     }
 211  24
     return availableGetters;
 212  
   }
 213  
 
 214  
   public <T> T convertToObject(String json, Class<T> className) {
 215  243
     String errorMessage = "Could not convert " + json + " to " + className;
 216  
 
 217  
     try {
 218  243
       T pojo = injector.getInstance(className);
 219  243
       return convertToObject(json, pojo);
 220  0
     } catch (JSONException e) {
 221  0
       throw new RuntimeException(errorMessage, e);
 222  0
     } catch (InvocationTargetException e) {
 223  0
       throw new RuntimeException(errorMessage, e);
 224  0
     } catch (IllegalAccessException e) {
 225  0
       throw new RuntimeException(errorMessage, e);
 226  0
     } catch (InstantiationException e) {
 227  0
       throw new RuntimeException(errorMessage, e);
 228  0
     } catch (NoSuchFieldException e) {
 229  0
       throw new RuntimeException(errorMessage, e);
 230  
     }
 231  
   }
 232  
 
 233  
   private <T> T convertToObject(String json, T pojo)
 234  
       throws JSONException, InvocationTargetException, IllegalAccessException,
 235  
       InstantiationException, NoSuchFieldException {
 236  
 
 237  243
     if (pojo instanceof String) {
 238  101
       pojo = (T) json; // This is a weird cast...
 239  
 
 240  101
     } else if (pojo instanceof Map) {
 241  
       // TODO: Figure out how to get the actual generic type for the
 242  
       // second Map parameter. Right now we are hardcoding to String
 243  2
       Class<?> mapValueClass = String.class;
 244  
 
 245  2
       JSONObject jsonObject = new JSONObject(json);
 246  2
       Iterator<?> iterator = jsonObject.keys();
 247  5
       while (iterator.hasNext()) {
 248  3
         String key = (String) iterator.next();
 249  3
         Object value = convertToObject(jsonObject.getString(key), mapValueClass);
 250  3
         ((Map<String, Object>) pojo).put(key, value);
 251  3
       }
 252  
 
 253  2
     } else if (pojo instanceof List) {
 254  0
       JSONArray array = new JSONArray(json);
 255  0
       for (int i = 0; i < array.length(); i++) {
 256  0
         ((List<Object>) pojo).add(array.get(i));
 257  
       }
 258  0
     } else {
 259  140
       JSONObject jsonObject = new JSONObject(json);
 260  
       List<MethodPair> methods;
 261  140
       methods = SETTER_METHODS.get(pojo.getClass());
 262  140
       if (methods == null) {
 263  11
         methods = getMatchingMethods(pojo, SETTER_PREFIX);
 264  11
         SETTER_METHODS.putIfAbsent(pojo.getClass(), methods);
 265  
       }
 266  
 
 267  140
       for (MethodPair setter : methods) {
 268  2760
         if (jsonObject.has(setter.fieldName)) {
 269  709
           callSetterWithValue(pojo, setter.method, jsonObject, setter.fieldName);
 270  
         }
 271  2760
       }
 272  
     }
 273  243
     return pojo;
 274  
   }
 275  
 
 276  
   private <T> void callSetterWithValue(T pojo, Method method,
 277  
       JSONObject jsonObject, String fieldName)
 278  
       throws IllegalAccessException, InvocationTargetException, NoSuchFieldException,
 279  
       JSONException {
 280  
 
 281  709
     Class<?> expectedType = method.getParameterTypes()[0];
 282  709
     Object value = null;
 283  
 
 284  709
     if (!jsonObject.has(fieldName)) {
 285  
       // Skip
 286  0
     } else if (expectedType.equals(List.class)) {
 287  71
       ParameterizedType genericListType
 288  
           = (ParameterizedType) method.getGenericParameterTypes()[0];
 289  71
       Type type = genericListType.getActualTypeArguments()[0];
 290  
       Class<?> rawType;
 291  
       Class<?> listElementClass;
 292  71
       if (type instanceof ParameterizedType) {
 293  3
         listElementClass = (Class<?>)((ParameterizedType)type).getActualTypeArguments()[0];
 294  3
         rawType = (Class<?>)((ParameterizedType)type).getRawType();
 295  3
       } else {
 296  68
         listElementClass = (Class<?>) type;
 297  68
         rawType = listElementClass;
 298  
       }
 299  
 
 300  71
       List<Object> list = Lists.newArrayList();
 301  71
       JSONArray jsonArray = jsonObject.getJSONArray(fieldName);
 302  204
       for (int i = 0; i < jsonArray.length(); i++) {
 303  133
         if (org.apache.shindig.social.opensocial.model.Enum.class
 304  
             .isAssignableFrom(rawType)) {
 305  6
           list.add(convertEnum(listElementClass, jsonArray.getJSONObject(i)));
 306  6
         } else {
 307  127
           list.add(convertToObject(jsonArray.getString(i), listElementClass));
 308  
         }
 309  
       }
 310  
 
 311  71
       value = list;
 312  
 
 313  71
     } else if (expectedType.equals(Map.class)) {
 314  4
       ParameterizedType genericListType
 315  
           = (ParameterizedType) method.getGenericParameterTypes()[0];
 316  4
       Type[] types = genericListType.getActualTypeArguments();
 317  4
       Class<?> valueClass = (Class<?>) types[1];
 318  
 
 319  
       // We only support keys being typed as Strings.
 320  
       // Nothing else really makes sense in json.
 321  4
       Map<String, Object> map = Maps.newHashMap();
 322  4
       JSONObject jsonMap = jsonObject.getJSONObject(fieldName);
 323  
 
 324  4
       Iterator keys = jsonMap.keys();
 325  12
       while (keys.hasNext()) {
 326  8
         String keyName = (String) keys.next();
 327  8
         map.put(keyName, convertToObject(jsonMap.getString(keyName),
 328  
             valueClass));
 329  8
       }
 330  
 
 331  4
       value = map;
 332  
 
 333  4
     } else if (org.apache.shindig.social.opensocial.model.Enum.class
 334  
         .isAssignableFrom(expectedType)) {
 335  
       // TODO Need to stop using Enum as a class name :(
 336  9
       value = convertEnum(
 337  
           (Class<?>)((ParameterizedType) method.getGenericParameterTypes()[0]).
 338  
               getActualTypeArguments()[0],
 339  
           jsonObject.getJSONObject(fieldName));
 340  9
     } else if (expectedType.isEnum()) {
 341  24
       if (jsonObject.has(fieldName)) {
 342  42
         for (Object v : expectedType.getEnumConstants()) {
 343  42
           if (v.toString().equals(jsonObject.getString(fieldName))) {
 344  24
             value = v;
 345  24
             break;
 346  
           }
 347  
         }
 348  24
         if (value == null) {
 349  0
           throw new IllegalArgumentException(
 350  
               "No enum value  '" + jsonObject.getString(fieldName)
 351  
                   + "' in " + expectedType.getName());
 352  
         }
 353  
       }
 354  601
     } else if (expectedType.equals(String.class)) {
 355  481
       value = jsonObject.getString(fieldName);
 356  481
     } else if (expectedType.equals(Date.class)) {
 357  
       // Use JODA ISO parsing for the conversion
 358  22
       value = new DateTime(jsonObject.getString(fieldName)).toDate();
 359  22
     } else if (expectedType.equals(Long.class) || expectedType.equals(Long.TYPE)) {
 360  7
       value = jsonObject.getLong(fieldName);
 361  7
     } else if (expectedType.equals(Integer.class) || expectedType.equals(Integer.TYPE)) {
 362  8
       value = jsonObject.getInt(fieldName);
 363  8
     } else if (expectedType.equals(Boolean.class) || expectedType.equals(Boolean.TYPE)) {
 364  13
       value = jsonObject.getBoolean(fieldName);
 365  13
     } else if (expectedType.equals(Float.class) || expectedType.equals(Float.TYPE)) {
 366  22
       String stringFloat = jsonObject.getString(fieldName);
 367  22
       value = new Float(stringFloat);
 368  22
     } else {
 369  
       // Assume its an injected type
 370  48
       value = convertToObject(jsonObject.getJSONObject(fieldName).toString(), expectedType);
 371  
     }
 372  
 
 373  709
     if (value != null) {
 374  709
       method.invoke(pojo, value);
 375  
     }
 376  709
   }
 377  
 
 378  
   private Object convertEnum(Class<?> enumKeyType, JSONObject jsonEnum)
 379  
       throws JSONException, IllegalAccessException, NoSuchFieldException {
 380  
     // TODO This isnt injector friendly but perhaps implementors dont need it. If they do a
 381  
     // refactoring of the Enum handling in general is needed.
 382  
     Object value;
 383  15
     if (jsonEnum.has(Enum.Field.VALUE.toString())) {
 384  15
       Enum.EnumKey enumKey = (Enum.EnumKey) enumKeyType
 385  
           .getField(jsonEnum.getString(Enum.Field.VALUE.toString())).get(null);
 386  15
       value = new EnumImpl<Enum.EnumKey>(enumKey,
 387  
           jsonEnum.getString(Enum.Field.DISPLAY_VALUE.toString()));
 388  15
     } else {
 389  0
       value = new EnumImpl<Enum.EnumKey>(null,
 390  
           jsonEnum.getString(Enum.Field.DISPLAY_VALUE.toString()));
 391  
     }
 392  15
     return value;
 393  
   }
 394  
 }