content: //
  //  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.*;
  
  /**
   * 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;
      }
  }
  