fi.foyt.foursquare.api.JSONFieldParser.java Source code

Java tutorial

Introduction

Here is the source code for fi.foyt.foursquare.api.JSONFieldParser.java

Source

/*
 * FoursquareAPI - Foursquare API for Java
 * Copyright (C) 2008 - 2011 Antti Lepp / Foyt
 * http://www.foyt.fi
 * 
 * License: 
 * 
 * Licensed under GNU Lesser General Public License Version 3 or later (the "LGPL")
 * http://www.gnu.org/licenses/lgpl.html
 */

package fi.foyt.foursquare.api;

import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.List;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

/**
 * Class responsible of parsing API responses
 * 
 * @author Antti Lepp
 */
public class JSONFieldParser {

    /**
     * Utility class so no constructor needed.
     */
    private JSONFieldParser() {

    }

    /**
     * Static method that parses JSON array into array of FoursquareEntities
     * 
     * @param clazz entity class
     * @param jsonArray JSON Array
     * @param skipNonExistingFields whether parser should ignore non-existing fields
     * @return list of entities
     * @throws FoursquareApiException when something unexpected happens
     */
    public static FoursquareEntity[] parseEntities(Class<?> clazz, JSONArray jsonArray,
            boolean skipNonExistingFields) throws FoursquareApiException {
        FoursquareEntity[] result = (FoursquareEntity[]) Array.newInstance(clazz, jsonArray.length());

        for (int i = 0, l = jsonArray.length(); i < l; i++) {
            JSONObject jsonObject;
            try {
                jsonObject = jsonArray.getJSONObject(i);
                result[i] = parseEntity(clazz, jsonObject, skipNonExistingFields);
            } catch (JSONException e) {
                throw new FoursquareApiException(e);
            }
        }

        return result;
    }

    /**
     * Static method that parses JSON "named array" into array of FoursquareEntities
     * 
     * @param clazz entity class
     * @param jsonHashList JSON "named array"
     * @param skipNonExistingFields whether parser should ignore non-existing fields
     * @return list of entities
     * @throws FoursquareApiException when something unexpected happens
     */
    public static FoursquareEntity[] parseEntitiesHash(Class<?> clazz, JSONObject jsonHashList,
            boolean skipNonExistingFields) throws FoursquareApiException {
        String[] keys = getFieldNames(jsonHashList);

        FoursquareEntity[] result = (FoursquareEntity[]) Array.newInstance(clazz, keys.length);
        int i = 0;
        for (String key : keys) {
            JSONObject jsonObject;
            try {
                jsonObject = jsonHashList.getJSONObject(key);
                result[i++] = parseEntity(clazz, jsonObject, skipNonExistingFields);
            } catch (JSONException e) {
                throw new FoursquareApiException(e);
            }
        }

        return result;
    }

    /**
     * Static method that parses single JSON Object into FoursquareEntity
     * 
     * @param clazz entity class
     * @param jsonObject JSON Object
     * @param skipNonExistingFields whether parser should ignore non-existing fields
     * @return entity
     * @throws FoursquareApiException when something unexpected happens
     */
    public static FoursquareEntity parseEntity(Class<?> clazz, JSONObject jsonObject, boolean skipNonExistingFields)
            throws FoursquareApiException {
        FoursquareEntity entity = createNewEntity(clazz);

        String[] objectFieldNames = getFieldNames(jsonObject);
        if (objectFieldNames != null) {
            for (String objectFieldName : objectFieldNames) {
                Class<?> fieldClass = getFieldClass(entity.getClass(), objectFieldName);
                if (fieldClass == null) {
                    Method setterMethod = getSetterMethod(entity.getClass(), objectFieldName);
                    if (setterMethod == null) {
                        if (!skipNonExistingFields) {
                            throw new FoursquareApiException("Could not find field " + objectFieldName + " from "
                                    + entity.getClass().getName() + " class");
                        }
                    } else {
                        Class<?>[] parameters = setterMethod.getParameterTypes();
                        if (parameters.length == 1) {
                            fieldClass = parameters[0];

                            try {
                                setterMethod.setAccessible(true);
                                setterMethod.invoke(entity,
                                        parseValue(fieldClass, jsonObject, objectFieldName, skipNonExistingFields));
                            } catch (JSONException e) {
                                throw new FoursquareApiException(e);
                            } catch (IllegalArgumentException e) {
                                throw new FoursquareApiException(e);
                            } catch (IllegalAccessException e) {
                                throw new FoursquareApiException(e);
                            } catch (InvocationTargetException e) {
                                throw new FoursquareApiException(e);
                            }
                        } else {
                            throw new FoursquareApiException("Could not find field " + objectFieldName + " from "
                                    + entity.getClass().getName() + " class");
                        }
                    }
                } else {
                    try {
                        setEntityFieldValue(entity, objectFieldName,
                                parseValue(fieldClass, jsonObject, objectFieldName, skipNonExistingFields));
                    } catch (JSONException e) {
                        throw new FoursquareApiException(e);
                    }
                }
            }
        }

        return entity;
    }

    /**
     * Parses single JSON object field into a value. Value might be of type String, Integer, Long, Double, Boolean or FoursquareEntity depending classes field type
     * 
     * @param clazz class
     * @param jsonObject JSON Object
     * @param objectFieldName field to be parsed
     * @param skipNonExistingFields whether parser should ignore non-existing fields
     * @return field's value
     * @throws JSONException when JSON parsing error occures
     * @throws FoursquareApiException when something unexpected happens
     */
    private static Object parseValue(Class<?> clazz, JSONObject jsonObject, String objectFieldName,
            boolean skipNonExistingFields) throws JSONException, FoursquareApiException {
        if (clazz.isArray()) {
            Object value = jsonObject.get(objectFieldName);

            JSONArray jsonArray;
            if (value instanceof JSONArray) {
                jsonArray = (JSONArray) value;
            } else {
                if ((value instanceof JSONObject) && (((JSONObject) value).has("items"))) {
                    jsonArray = ((JSONObject) value).getJSONArray("items");
                } else {
                    throw new FoursquareApiException("JSONObject[\"" + objectFieldName
                            + "\"] is neither a JSONArray nor a {count, items} object.");
                }
            }

            Class<?> arrayClass = clazz.getComponentType();
            Object[] arrayValue = (Object[]) Array.newInstance(arrayClass, jsonArray.length());

            for (int i = 0, l = jsonArray.length(); i < l; i++) {
                if (arrayClass.equals(String.class)) {
                    arrayValue[i] = jsonArray.getString(i);
                } else if (arrayClass.equals(Integer.class)) {
                    arrayValue[i] = jsonArray.getInt(i);
                } else if (arrayClass.equals(Long.class)) {
                    arrayValue[i] = jsonArray.getLong(i);
                } else if (arrayClass.equals(Double.class)) {
                    arrayValue[i] = jsonArray.getDouble(i);
                } else if (arrayClass.equals(Boolean.class)) {
                    arrayValue[i] = jsonArray.getBoolean(i);
                } else if (isFoursquareEntity(arrayClass)) {
                    arrayValue[i] = parseEntity(arrayClass, jsonArray.getJSONObject(i), skipNonExistingFields);
                } else {
                    throw new FoursquareApiException("Unknown array type: " + arrayClass);
                }
            }

            return arrayValue;
        } else if (clazz.equals(String.class)) {
            return jsonObject.getString(objectFieldName);
        } else if (clazz.equals(Integer.class)) {
            return jsonObject.getInt(objectFieldName);
        } else if (clazz.equals(Long.class)) {
            return jsonObject.getLong(objectFieldName);
        } else if (clazz.equals(Double.class)) {
            return jsonObject.getDouble(objectFieldName);
        } else if (clazz.equals(Boolean.class)) {
            return jsonObject.getBoolean(objectFieldName);
        } else if (isFoursquareEntity(clazz)) {
            return parseEntity(clazz, jsonObject.getJSONObject(objectFieldName), skipNonExistingFields);
        } else {
            throw new FoursquareApiException("Unknown type: " + clazz);
        }
    }

    /**
     * Returns whether class is derived from FoursquareEntity interface
     * 
     * @param clazz class
     * @return whether class is derived from FoursquareEntity interface
     */
    private static boolean isFoursquareEntity(Class<?> clazz) {
        for (Class<?> intrf : clazz.getInterfaces()) {
            if (intrf.equals(FoursquareEntity.class)) {
                return true;
            }
        }

        Class<?> superClass = clazz.getSuperclass();
        if (!superClass.equals(Object.class)) {
            return isFoursquareEntity(superClass);
        }

        return false;
    }

    /**
     * Returns field of class
     * 
     * @param entityClass class
     * @param fieldName field
     * @return Field
     */
    private static Field getField(Class<?> entityClass, String fieldName) {
        try {
            Field field = entityClass.getDeclaredField(fieldName);
            field.setAccessible(true);
            return field;
        } catch (SecurityException e) {
            return null;
        } catch (NoSuchFieldException e) {
            Class<?> superClass = entityClass.getSuperclass();
            if (superClass.equals(Object.class)) {
                return null;
            } else {
                return getField(superClass, fieldName);
            }
        }
    }

    /**
     * Returns list of all methods in a class
     * 
     * @param entityClass class
     * @return list of all methods in a class
     */
    private static List<Method> getMethods(Class<?> entityClass) {
        List<Method> result = new ArrayList<Method>(Arrays.asList(entityClass.getDeclaredMethods()));
        if (!entityClass.getSuperclass().equals(Object.class)) {
            result.addAll(getMethods(entityClass.getSuperclass()));
        }

        return result;
    }

    /**
     * Returns setter method for a field
     * 
     * @param entityClass class
     * @param fieldName field
     * @return setter method for a field
     */
    private static Method getSetterMethod(Class<?> entityClass, String fieldName) {
        StringBuilder methodNameBuilder = new StringBuilder();
        methodNameBuilder.append("set");
        methodNameBuilder.append(Character.toUpperCase(fieldName.charAt(0)));
        methodNameBuilder.append(fieldName.substring(1));

        String methodName = methodNameBuilder.toString();

        List<Method> methods = getMethods(entityClass);
        for (Method method : methods) {
            if (method.getName().equals(methodName)) {
                return method;
            }
        }

        return null;
    }

    /**
     * Returns class for class's field
     * 
     * @param entityClass class
     * @param fieldName field
     * @return class's field's class
     */
    private static Class<?> getFieldClass(Class<?> entityClass, String fieldName) {
        Field field = getField(entityClass, fieldName);
        return field != null ? field.getType() : null;
    }

    /**
     * Sets entity's field's value
     * 
     * @param entity entity
     * @param fieldName field
     * @param value value
     * @throws FoursquareApiException when something unexpected happens
     */
    private static void setEntityFieldValue(FoursquareEntity entity, String fieldName, Object value)
            throws FoursquareApiException {
        java.lang.reflect.Field fieldProperty;
        try {
            fieldProperty = getField(entity.getClass(), fieldName);
            fieldProperty.setAccessible(true);
            fieldProperty.set(entity, value);
        } catch (Exception e) {
            throw new FoursquareApiException(e);
        }
    }

    /**
     * Initializes new entity instance
     * 
     * @param clazz class
     * @return new entity instance
     */
    private static FoursquareEntity createNewEntity(Class<?> clazz) {
        try {
            return (FoursquareEntity) clazz.newInstance();
        } catch (InstantiationException e) {
            return null;
        } catch (IllegalAccessException e) {
            return null;
        }
    }

    /**
     * Returns JSONObject's field names. 
     * 
     * This method is used instead of JSONObject.getNames -method because 
     * Android's org.json implementation does not contain that method.
     * 
     * @param jsonObject JSONObject
     * @return JSONObject's field names
     */
    private static String[] getFieldNames(JSONObject jsonObject) {
        int length = jsonObject.length();
        if (length == 0) {
            return null;
        }

        Iterator<?> iterator = jsonObject.keys();
        String[] names = new String[length];
        int i = 0;

        while (iterator.hasNext()) {
            names[i] = (String) iterator.next();
            i += 1;
        }

        return names;
    }
}