//
// Copyright (c) 2014 VK.com
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of
// this software and associated documentation files (the "Software"), to deal in
// the Software without restriction, including without limitation the rights to
// use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
// the Software, and to permit persons to whom the Software is furnished to do so,
// subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
// FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
// IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
package com.vk.sdk.api.model;
import android.os.Parcelable;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.ParameterizedType;
/**
* Collection of helpers to parse server responses.
*/
class ParseUtils {
private ParseUtils() {
}
/**
* Parse boolean from server response.
*
* @param from server response like this format: {@code response: 1}
* @throws JSONException if server response is not valid
*/
public static boolean parseBoolean(String from) throws JSONException {
return new JSONObject(from).optInt("response", 0) == 1;
}
/**
* Parse boolean from JSONObject with given name.
*
* @param from server response like this format: {@code field: 1}
* @param name name of field to read
*/
public static boolean parseBoolean(JSONObject from, String name) {
return from != null && from.optInt(name, 0) == 1;
}
/**
* Parse int from JSONObject with given name.
*
* @param from server response like this format: {@code field: 34}
* @param name name of field to read
*/
public static int parseInt(JSONObject from, String name) {
if (from == null) return 0;
return from.optInt(name, 0);
}
/**
* Parse int from server response.
*
* @param from server response like this format: {@code response: 34}
* @throws JSONException if server response is not valid
*/
public static int parseInt(String from) throws JSONException {
if (from == null) return 0;
return new JSONObject(from).optInt("response");
}
/**
* Parse long from JSONObject with given name.
*
* @param from server response like this format: {@code field: 34}
* @param name name of field to read
*/
public static long parseLong(JSONObject from, String name) {
if (from == null) return 0;
return from.optLong(name, 0);
}
/**
* Parse int array from JSONObject with given name.
*
* @param from int JSON array like this one {@code {11, 34, 42}}
*/
public static int[] parseIntArray(JSONArray from) {
int[] result = new int[from.length()];
for (int i = 0; i < result.length; i++) {
result[i] = from.optInt(i);
}
return result;
}
/**
* Returns root JSONObject from server response
*
* @param source standart VK server response
* @throws JSONException if source is not valid
*/
public static JSONObject rootJSONObject(String source) throws JSONException {
return new JSONObject(source).getJSONObject("response");
}
/**
* Returns root JSONArray from server response
*
* @param source standart VK server response
* @throws JSONException if source is not valid
*/
public static JSONArray rootJSONArray(String source) throws JSONException {
return new JSONObject(source).getJSONArray("response");
}
/**
* Parses object with follow rules:
*
* 1. All fields should had a public access.
* 2. The name of the filed should be fully equal to name of JSONObject key.
* 3. Supports parse of all Java primitives, all {@link java.lang.String},
* arrays of primitive types, {@link java.lang.String}s and {@link com.vk.sdk.api.model.VKApiModel}s,
* list implementation line {@link com.vk.sdk.api.model.VKList}, {@link com.vk.sdk.api.model.VKAttachments.VKAttachment} or {@link com.vk.sdk.api.model.VKPhotoSizes},
* {@link com.vk.sdk.api.model.VKApiModel}s.
*
* 4. Boolean fields defines by vk_int == 1 expression.
*
* @param object object to initialize
* @param source data to read values
* @param <T> type of result
* @return initialized according with given data object
* @throws JSONException if source object structure is invalid
*/
@SuppressWarnings({"rawtypes", "unchecked"})
public static <T> T parseViaReflection(T object, JSONObject source) throws JSONException {
if (source.has("response")) {
source = source.optJSONObject("response");
}
if (source == null) {
return object;
}
for (Field field : object.getClass().getFields()) {
field.setAccessible(true);
String fieldName = field.getName();
Class<?> fieldType = field.getType();
Object value = source.opt(fieldName);
if (value == null) {
continue;
}
try {
if (fieldType.isPrimitive() && value instanceof Number) {
Number number = (Number) value;
if (fieldType.equals(int.class)) {
field.setInt(object, number.intValue());
} else if (fieldType.equals(long.class)) {
field.setLong(object, number.longValue());
} else if (fieldType.equals(float.class)) {
field.setFloat(object, number.floatValue());
} else if (fieldType.equals(double.class)) {
field.setDouble(object, number.doubleValue());
} else if (fieldType.equals(boolean.class)) {
field.setBoolean(object, number.intValue() == 1);
} else if (fieldType.equals(short.class)) {
field.setShort(object, number.shortValue());
} else if (fieldType.equals(byte.class)) {
field.setByte(object, number.byteValue());
}
} else {
Object result = field.get(object);
if (value.getClass().equals(fieldType)) {
result = value;
} else if (fieldType.isArray() && value instanceof JSONArray) {
result = parseArrayViaReflection((JSONArray) value, fieldType);
} else if(VKPhotoSizes.class.isAssignableFrom(fieldType) && value instanceof JSONArray) {
Constructor<?> constructor = fieldType.getConstructor(JSONArray.class);
result = constructor.newInstance((JSONArray) value);
} else if(VKAttachments.class.isAssignableFrom(fieldType) && value instanceof JSONArray) {
Constructor<?> constructor = fieldType.getConstructor(JSONArray.class);
result = constructor.newInstance((JSONArray) value);
} else if(VKList.class.equals(fieldType)) {
ParameterizedType genericTypes = (ParameterizedType) field.getGenericType();
Class<?> genericType = (Class<?>) genericTypes.getActualTypeArguments()[0];
if(VKApiModel.class.isAssignableFrom(genericType) && Parcelable.class.isAssignableFrom(genericType) && Identifiable.class.isAssignableFrom(genericType)) {
if(value instanceof JSONArray) {
result = new VKList((JSONArray) value, genericType);
} else if(value instanceof JSONObject) {
result = new VKList((JSONObject) value, genericType);
}
}
} else if (VKApiModel.class.isAssignableFrom(fieldType) && value instanceof JSONObject) {
result = ((VKApiModel) fieldType.newInstance()).parse((JSONObject) value);
}
field.set(object, result);
}
} catch (InstantiationException e) {
throw new JSONException(e.getMessage());
} catch (IllegalAccessException e) {
throw new JSONException(e.getMessage());
} catch (NoSuchMethodException e) {
throw new JSONException(e.getMessage());
} catch (InvocationTargetException e) {
throw new JSONException(e.getMessage());
} catch (NoSuchMethodError e) {
// Примечание Виталия:
// Вы не поверите, но у некоторых вендоров getFields() вызывает ВОТ ЭТО.
// Иногда я всерьез задумываюсь, правильно ли я поступил, выбрав Android в качестве платформы разработки.
throw new JSONException(e.getMessage());
}
}
return object;
}
/**
* Parses array from given JSONArray.
* Supports parsing of primitive types and {@link com.vk.sdk.api.model.VKApiModel} instances.
* @param array JSONArray to parse
* @param arrayClass type of array field in class.
* @return object to set to array field in class
* @throws JSONException if given array have incompatible type with given field.
*/
private static Object parseArrayViaReflection(JSONArray array, Class arrayClass) throws JSONException {
Object result = Array.newInstance(arrayClass.getComponentType(), array.length());
Class<?> subType = arrayClass.getComponentType();
for (int i = 0; i < array.length(); i++) {
try {
Object item = array.opt(i);
if(VKApiModel.class.isAssignableFrom(subType) && item instanceof JSONObject) {
VKApiModel model = (VKApiModel) subType.newInstance();
item = model.parse((JSONObject) item);
}
Array.set(result, i, item);
} catch (InstantiationException e) {
throw new JSONException(e.getMessage());
} catch (IllegalAccessException e) {
throw new JSONException(e.getMessage());
} catch (IllegalArgumentException e) {
throw new JSONException(e.getMessage());
}
}
return result;
}
}