Java tutorial
/** * Copyright 2012 J. Miguel P. Tavares <mtavares@bitpipeline.eu> * BitPipeline * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. ***************************************************************************/ package org.bitpipeline.lib.friendlyjson; import java.io.PrintWriter; import java.io.StringWriter; import java.lang.reflect.Array; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; /** The base of all JSON entities. * Classes that extend this abstract class can be easily serialized into JSON... well, with some very "strict" rules. * * Classes that extend this class can have fields defined as: * <ul> * <li>java primitives (boolean, byte, char, short, int, long, float and double)</li> * <li>java.util.Map<String, ?></li> * <li>java.util.List<?></li> * <li>other JSONEntities</li> * </ul> * * To convert the instances to JSON objects just call the method * <tt>toJSON ()</tt>. To create a instance of the object from JSON use the * constructor with <tt>JSONObject</tt> as parameter. */ public abstract class JSONEntity { private static final String MSG_MUST_HAVE_CONSTRUCTOR = " must implement a public constructor with a JSONObject as argument."; public JSONEntity() { } public JSONEntity(String jsonString) throws JSONMappingException, JSONException { this(new JSONObject(jsonString)); } /** Des-serialize a JSON object. * @throws JSONException * @throws JSONMappingException */ public JSONEntity(JSONObject json) throws JSONMappingException { if (json == null) return; Class<?> clazz = this.getClass(); List<Field> declaredFields = new ArrayList<Field>(); do { Field[] fields = clazz.getDeclaredFields(); declaredFields.addAll(Arrays.asList(fields)); clazz = clazz.getSuperclass(); } while (clazz != null && !clazz.isAssignableFrom(JSONEntity.class)); for (Field field : declaredFields) { if ((field.getModifiers() & Modifier.TRANSIENT) != 0) { // don't care about transient fields. continue; } String fieldName = field.getName(); if (fieldName.equals("this$0")) continue; boolean accessible = field.isAccessible(); if (!accessible) field.setAccessible(true); Class<?> type = field.getType(); if (type.isArray()) { Class<?> componentClass = type.getComponentType(); JSONArray jsonArray = null; try { jsonArray = json.getJSONArray(fieldName); } catch (JSONException e) { // no data for this field found. continue; } int size = jsonArray.length(); Object array = Array.newInstance(componentClass, size); for (int i = 0; i < size; i++) { try { Object item = fromJson(componentClass, jsonArray.get(i)); Array.set(array, i, item); } catch (Exception e) { System.err.println("Invalid array component for class " + this.getClass().getName() + " field " + field.getName() + "::" + field.getType().getName()); } } try { field.set(this, array); } catch (Exception e) { throw new JSONMappingException(e); } } else if (JSONEntity.class.isAssignableFrom(type)) { try { Object entity = readJSONEntity(type, json.getJSONObject(fieldName)); field.set(this, entity); } catch (JSONException e) { // keep going.. the json representation doesn't have value for this field. } catch (Exception e) { throw new JSONMappingException(e); } } else { FieldSetter setter = JSON_READERS.get(type.getName()); if (setter != null) { try { setter.setField(this, field, json, fieldName); } catch (JSONException e) { // do nothing. We just didn't receive data for this field } catch (Exception e) { throw new JSONMappingException(e); } } else { Object jsonObj; try { jsonObj = json.get(fieldName); } catch (Exception e) { jsonObj = null; } if (jsonObj != null) { Object value = fromJson(type, jsonObj); try { field.set(this, value); } catch (Exception e) { e.printStackTrace(); } } else { System.err.println("No setter for " + field); } } } if (!accessible) field.setAccessible(false); } } private Object readJSONEntity(Class<?> clazz, JSONObject json) throws JSONMappingException { Class<?> c = clazz; Constructor<?> constructor = null; try { constructor = c.getConstructor(JSONObject.class); } catch (Exception e) { throw new JSONMappingException(JSONEntity.MSG_MUST_HAVE_CONSTRUCTOR, e); } if (constructor == null) return null; Object entity = null; try { entity = constructor.newInstance(json); } catch (Exception e) { throw new JSONMappingException(e); } return entity; } static interface FieldSetter { void setField(Object obj, Field field, JSONObject json, String jsonName) throws Exception; } private static interface JSON2Obj { Object convert(Object json) throws JSONException; } /** Read from the JSON into Java objects */ final private static HashMap<String, FieldSetter> JSON_READERS = new HashMap<String, FieldSetter>(); /** Convert from JSON complex objects into Java Objects. */ final private static HashMap<String, JSON2Obj> JSON_CONVERTERS = new HashMap<String, JSON2Obj>(); static { JSON_READERS.put("boolean", new FieldSetter() { public void setField(Object obj, Field field, JSONObject json, String jsonName) throws Exception { field.setBoolean(obj, json.getBoolean(jsonName)); } }); JSON_READERS.put("byte", new FieldSetter() { public void setField(Object obj, Field field, JSONObject json, String jsonName) throws Exception { field.setByte(obj, (byte) json.getInt(jsonName)); } }); JSON_READERS.put("char", new FieldSetter() { public void setField(Object obj, Field field, JSONObject json, String jsonName) throws Exception { String string = json.getString(jsonName); if (string == null || string.length() == 0) return; char charAt = string.charAt(0); field.setChar(obj, charAt); } }); JSON_READERS.put("short", new FieldSetter() { public void setField(Object obj, Field field, JSONObject json, String jsonName) throws Exception { field.setShort(obj, (short) json.getInt(jsonName)); } }); JSON_READERS.put("int", new FieldSetter() { public void setField(Object obj, Field field, JSONObject json, String jsonName) throws Exception { field.setInt(obj, json.getInt(jsonName)); } }); JSON_READERS.put("long", new FieldSetter() { public void setField(Object obj, Field field, JSONObject json, String jsonName) throws Exception { field.setLong(obj, json.getLong(jsonName)); } }); JSON_READERS.put("float", new FieldSetter() { public void setField(Object obj, Field field, JSONObject json, String jsonName) throws Exception { field.setFloat(obj, (float) json.getDouble(jsonName)); } }); JSON_READERS.put("double", new FieldSetter() { public void setField(Object obj, Field field, JSONObject json, String jsonName) throws Exception { field.setDouble(obj, json.getDouble(jsonName)); } }); JSON_READERS.put(String.class.getName(), new FieldSetter() { public void setField(Object obj, Field field, JSONObject json, String jsonName) throws Exception { field.set(obj, json.getString(jsonName)); } }); JSON_READERS.put(List.class.getName(), new FieldSetter() { public void setField(Object obj, Field field, JSONObject json, String jsonName) throws Exception { JSONArray jsonArray = json.getJSONArray(jsonName); ParameterizedType genericType = (ParameterizedType) field.getGenericType(); Class<?> listClass = (Class<?>) genericType.getActualTypeArguments()[0]; ArrayList<Object> list = new ArrayList<Object>(jsonArray.length()); for (int i = 0; i < jsonArray.length(); i++) { Object entry = jsonArray.get(i); list.add(JSONEntity.fromJson(listClass, entry)); } field.set(obj, list); } }); JSON_READERS.put(Map.class.getName(), new FieldSetter() { public void setField(Object obj, Field field, JSONObject json, String jsonName) throws Exception { JSONObject jsonMap = json.getJSONObject(jsonName); if (jsonMap == null) return; ParameterizedType genericType = (ParameterizedType) field.getGenericType(); Type[] actualTypeArguments = genericType.getActualTypeArguments(); Type keyType = actualTypeArguments[0]; Type valueType = actualTypeArguments[1]; Class<?> keyClass; if (keyType instanceof ParameterizedType) keyClass = (Class<?>) ((ParameterizedType) keyType).getRawType(); else keyClass = (Class<?>) actualTypeArguments[0]; Class<?> valueClass; if (valueType instanceof ParameterizedType) valueClass = (Class<?>) ((ParameterizedType) valueType).getRawType(); else valueClass = (Class<?>) actualTypeArguments[1]; HashMap<Object, Object> map = new HashMap<Object, Object>(jsonMap.length()); Iterator<?> keys = jsonMap.keys(); while (keys.hasNext()) { Object key = keys.next(); Object jsonValue = jsonMap.get(key.toString()); map.put(JSONEntity.fromJson(keyClass, key), JSONEntity.fromJson(valueClass, jsonValue)); } field.set(obj, map); } }); JSON_CONVERTERS.put(List.class.getName(), new JSON2Obj() { public Object convert(Object json) throws JSONException { JSONArray jsonArray = (JSONArray) json; Object[] array = new Object[jsonArray.length()]; for (int i = 0; i < jsonArray.length(); i++) { Object jsonValue = jsonArray.get(i); array[i] = JSONEntity.fromJson(jsonValue); } return Arrays.asList(array); } }); JSON_CONVERTERS.put(JSONObject.class.getName(), new JSON2Obj() { public Object convert(Object json) throws JSONException { JSONObject jsonMap = (JSONObject) json; Map<Object, Object> map = new HashMap<Object, Object>(jsonMap.length()); Iterator<?> keys = jsonMap.keys(); while (keys.hasNext()) { Object key = keys.next(); Object jsonValue = jsonMap.get(key.toString()); map.put(key, JSONEntity.fromJson(jsonValue)); } return map; } }); JSON_CONVERTERS.put(JSONArray.class.getName(), new JSON2Obj() { public Object convert(Object json) throws JSONException { JSONArray jsonArray = (JSONArray) json; Object[] array = new Object[jsonArray.length()]; for (int i = 0; i < jsonArray.length(); i++) { Object jsonValue = jsonArray.get(i); array[i] = JSONEntity.fromJson(jsonValue); } return Arrays.asList(array); } }); } private static Object fromJson(Object json) throws JSONException { if (json == null) return null; JSON2Obj json2Obj = JSONEntity.JSON_CONVERTERS.get(json.getClass().getName()); if (json2Obj != null) { return json2Obj.convert(json); } return json; } @SuppressWarnings({ "unchecked", "rawtypes" }) private static Object fromJson(Class<?> clazz, Object json) throws JSONMappingException { if (json == null) return null; Object fromJson = null; if (clazz == null) { try { fromJson = JSONEntity.fromJson(json); } catch (JSONException e) { e.printStackTrace(); throw new JSONMappingException(e); } } else { Constructor<?> constructor; ; if (JSONEntity.class.isAssignableFrom(clazz)) { // A JSON Entity. Class<?> enclosingClass = clazz.getEnclosingClass(); if (enclosingClass != null && (clazz.getModifiers() & Modifier.STATIC) == 0) { // it's a non static inner class try { constructor = clazz.getDeclaredConstructor(enclosingClass, JSONObject.class); } catch (Exception e) { throw new JSONMappingException(clazz.getName() + JSONEntity.MSG_MUST_HAVE_CONSTRUCTOR, e); } try { /* we actually don't know the enclosing object... * this will be a problem with inner classes that * reference the enclosing class instance at * constructor time... although with json entities * that should not happen. */ fromJson = constructor.newInstance(null, json); } catch (Exception e) { throw new JSONMappingException(e); } } else { // static inner class try { constructor = clazz.getDeclaredConstructor(JSONObject.class); } catch (Exception e) { throw new JSONMappingException(clazz.getName() + JSONEntity.MSG_MUST_HAVE_CONSTRUCTOR, e); } try { fromJson = constructor.newInstance(json); } catch (Exception e) { throw new JSONMappingException("clazz = " + clazz.getName() + "; json = " + json.toString(), e); } } } else if (clazz.isEnum()) { try { fromJson = Enum.valueOf((Class<Enum>) clazz, json.toString()); } catch (Exception e) { fromJson = null; } } else { try { fromJson = JSONEntity.fromJson(json); } catch (JSONException e) { e.printStackTrace(); } } } if (clazz != null && !clazz.isAssignableFrom(fromJson.getClass())) throw new JSONMappingException("Was expeting a " + clazz.getName() + " but received a " + fromJson.getClass().getName() + " instead."); return fromJson; } /* ---------------------- */ /** */ static private Object toJson(Object obj) throws JSONMappingException { if (obj == null) return null; Object json = obj; if (obj instanceof JSONEntity) { json = ((JSONEntity) obj).toJson(); } else if (obj instanceof Map) { json = new JSONObject(); for (Object mapKey : ((Map<?, ?>) obj).keySet()) { Object jsonValue = toJson(((Map<?, ?>) obj).get(mapKey)); try { ((JSONObject) json).put(mapKey.toString(), jsonValue); } catch (JSONException e) { throw new JSONMappingException(e); } } } else if (obj instanceof List) { json = new JSONArray(); for (Object listItem : ((List<?>) obj)) { Object jsonListItem = toJson(listItem); ((JSONArray) json).put(jsonListItem); } } else if (obj.getClass().isArray()) { json = new JSONArray(); Object[] array = (Object[]) obj; for (Object item : array) { Object jsonItem = toJson(item); ((JSONArray) json).put(jsonItem); } } else { json = obj; } return json; } private JSONObject fillWithClass(JSONObject json, Class<?> clazz) throws JSONMappingException { for (Field field : clazz.getDeclaredFields()) { if ((field.getModifiers() & Modifier.TRANSIENT) != 0) { // don't care about transient fields. continue; } String jsonName = field.getName(); if (jsonName.equals("this$0")) continue; boolean accessible = field.isAccessible(); if (!accessible) field.setAccessible(true); try { Object value = field.get(this); Object jsonField = toJson(value); json.put(jsonName, jsonField); } catch (Exception e) { throw new JSONMappingException(e); } if (!accessible) field.setAccessible(false); } return json; } /** Serialize the object into a JSON object. * @throws JSONMappingException */ public JSONObject toJson() throws JSONMappingException { JSONObject json = new JSONObject(); Class<?> clazz = this.getClass(); do { json = fillWithClass(json, clazz); clazz = clazz.getSuperclass(); } while (clazz != null && !clazz.isAssignableFrom(JSONEntity.class)); return json; } /** Get a String representation of the object, using JSON data format. * @return <tt>null</tt> if there was a problem converting the object into a JSON representation, * a String representing the object with a JSON syntax otherwise. */ public String toString() { try { return toJson().toString(); } catch (JSONMappingException e) { return null; } } /** Get a String representation of the object, using JSON data format. * @param indent is the number of spaces to be used for indentation. * @return a String representing the object with a pretty formated JSON * syntax unless there's a error, then the stack trace of the error * is returned.*/ public String toString(int indent) { try { return toJson().toString(indent); } catch (Exception e) { StringWriter sw = new StringWriter(); e.printStackTrace(new PrintWriter(sw)); return sw.toString(); } } }