public final class

ModelCodec

extends java.lang.Object

 java.lang.Object

↳androidx.test.espresso.web.model.ModelCodec

Gradle dependencies

compile group: 'androidx.test.espresso', name: 'espresso-web', version: '3.5.0-alpha06'

  • groupId: androidx.test.espresso
  • artifactId: espresso-web
  • version: 3.5.0-alpha06

Artifact androidx.test.espresso:espresso-web:3.5.0-alpha06 it located at Google repository (https://maven.google.com/)

Androidx artifact mapping:

androidx.test.espresso:espresso-web com.android.support.test.espresso:espresso-web

Androidx class mapping:

androidx.test.espresso.web.model.ModelCodec android.support.test.espresso.web.model.ModelCodec

Overview

Encodes/Decodes JSON.

Summary

Methods
public static voidaddDeJSONFactory(JSONAble.DeJSONFactory dejson)

Adds a DeJSONFactory to intercept JSONObjects and replace them with more suitable types.

public static EvaluationdecodeEvaluation(java.lang.String json)

Transforms a JSON string to an evaluation.

public static java.lang.Stringencode(java.lang.Object javaObject)

Encodes a Java Object into a JSON string.

public static voidremoveDeJSONFactory(JSONAble.DeJSONFactory dejson)

Removes a DeJSONFactory from the list of factories that transform JSONObjects to java objects.

from java.lang.Objectclone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait

Methods

public static Evaluation decodeEvaluation(java.lang.String json)

Transforms a JSON string to an evaluation.

public static java.lang.String encode(java.lang.Object javaObject)

Encodes a Java Object into a JSON string.

public static void removeDeJSONFactory(JSONAble.DeJSONFactory dejson)

Removes a DeJSONFactory from the list of factories that transform JSONObjects to java objects.

public static void addDeJSONFactory(JSONAble.DeJSONFactory dejson)

Adds a DeJSONFactory to intercept JSONObjects and replace them with more suitable types.

Source

/*
 * Copyright (C) 2015 The Android Open Source Project
 *
 * 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 androidx.test.espresso.web.model;

import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;

import android.os.Build;
import android.util.JsonReader;
import android.util.Log;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import java.io.IOException;
import java.io.StringReader;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.json.JSONStringer;
import org.json.JSONTokener;

/** Encodes/Decodes JSON. */
public final class ModelCodec {
  private static final String TAG = "JS_CODEC";

  private static final ImmutableSet<Class<?>> VALUEABLE_CLASSES =
      ImmutableSet.of(Boolean.class, Number.class, String.class, JSONObject.class, JSONArray.class);

  private static final ImmutableSet<Class<?>> TOP_LEVEL_CLASSES =
      ImmutableSet.of(
          JSONObject.class,
          JSONArray.class,
          Iterable.class,
          Object[].class,
          Map.class,
          JSONAble.class);

  private static final CopyOnWriteArrayList<JSONAble.DeJSONFactory> DEJSONIZERS =
      new CopyOnWriteArrayList<JSONAble.DeJSONFactory>(
          Lists.newArrayList(
              Evaluation.DEJSONIZER, WindowReference.DEJSONIZER, ElementReference.DEJSONIZER));

  private ModelCodec() {}

  /** Transforms a JSON string to an evaluation. */
  public static Evaluation decodeEvaluation(String json) {
    Object obj = decode(json);
    if (obj instanceof Evaluation) {
      return (Evaluation) obj;
    } else {
      throw new IllegalArgumentException(
          String.format(
              "Document: \"%s\" did not decode to an evaluation. Instead: \"%s\"", json, obj));
    }
  }

  /** Encodes a Java Object into a JSON string. */
  public static String encode(Object javaObject) {
    checkNotNull(javaObject);
    try {
      if (javaObject instanceof JSONObject) {
        return javaObject.toString();
      } else if (javaObject instanceof JSONArray) {
        return javaObject.toString();
      } else if (javaObject instanceof JSONAble) {
        return new JSONObject(((JSONAble) javaObject).toJSONString()).toString();
      } else if ((javaObject instanceof Iterable)
          || (javaObject instanceof Map)
          || (javaObject instanceof Object[])) {
        JSONStringer stringer = new JSONStringer();
        return encodeHelper(javaObject, stringer).toString();
      }
      throw new IllegalArgumentException(
          String.format(
              "%s: not a valid top level class. Want one of: %s",
              javaObject.getClass(), TOP_LEVEL_CLASSES));
    } catch (JSONException je) {
      throw new RuntimeException("Encode failed: " + javaObject, je);
    }
  }

  /**
   * Removes a DeJSONFactory from the list of factories that transform JSONObjects to java objects.
   */
  public static void removeDeJSONFactory(JSONAble.DeJSONFactory dejson) {
    DEJSONIZERS.remove(dejson);
  }

  /** Adds a DeJSONFactory to intercept JSONObjects and replace them with more suitable types. */
  public static void addDeJSONFactory(JSONAble.DeJSONFactory dejson) {
    DEJSONIZERS.add(checkNotNull(dejson));
  }

  static Object decode(String json) {
    checkNotNull(json);
    checkArgument(!"".equals(json), "Empty docs not supported.");

    try {
      if (Build.VERSION.SDK_INT < 13) {
        // After API 13, there is the JSONReader API - which is nicer to work with.
        return decodeViaJSONObject(json);
      } else {
        return decodeViaJSONReader(json);
      }
    } catch (JSONException je) {
      throw new RuntimeException(String.format("Could not parse: %s", json), je);
    } catch (IOException ioe) {
      throw new RuntimeException(String.format("Could not parse: %s", json), ioe);
    }
  }

  private static Object decodeViaJSONObject(String json) throws JSONException {
    JSONTokener tokener = new JSONTokener(json);
    Object value = tokener.nextValue();
    if (value instanceof JSONArray) {
      return decodeArray((JSONArray) value);
    } else if (value instanceof JSONObject) {
      return decodeObject((JSONObject) value);
    } else {
      throw new IllegalArgumentException("No top level object or array: " + json);
    }
  }

  private static List<Object> decodeArray(JSONArray array) throws JSONException {
    List<Object> data = Lists.newArrayList();
    for (int i = 0; i < array.length(); i++) {
      if (array.isNull(i)) {
        data.add(null);
      } else {
        Object value = array.get(i);
        if (value instanceof JSONObject) {
          data.add(decodeObject((JSONObject) value));
        } else if (value instanceof JSONArray) {
          data.add(decodeArray((JSONArray) value));
        } else {
          // boolean / string / or number.
          data.add(value);
        }
      }
    }
    return data;
  }

  private static Object decodeObject(JSONObject jsonObject) throws JSONException {
    List<String> nullKeys = Lists.newArrayList();
    Map<String, Object> obj = Maps.newHashMap();
    Iterator<String> keys = jsonObject.keys();
    while (keys.hasNext()) {
      String key = keys.next();
      if (jsonObject.isNull(key)) {
        nullKeys.add(key);
        obj.put(key, JSONObject.NULL);
      } else {
        Object value = jsonObject.get(key);
        if (value instanceof JSONObject) {
          obj.put(key, decodeObject((JSONObject) value));
        } else if (value instanceof JSONArray) {
          obj.put(key, decodeArray((JSONArray) value));
        } else {
          // boolean / string / or number.
          obj.put(key, value);
        }
      }
    }
    Object replacement = maybeReplaceMap(obj);
    if (replacement != null) {
      return replacement;
    } else {
      for (String key : nullKeys) {
        obj.remove(key);
      }

      return obj;
    }
  }

  private static Object decodeViaJSONReader(String json) throws IOException {
    JsonReader reader = null;
    try {
      reader = new JsonReader(new StringReader(json));
      while (true) {
        switch (reader.peek()) {
          case BEGIN_OBJECT:
            return decodeObject(reader);
          case BEGIN_ARRAY:
            return decodeArray(reader);
          default:
            throw new IllegalStateException("Bogus document: " + json);
        }
      }
    } finally {
      if (null != reader) {
        try {
          reader.close();
        } catch (IOException ioe) {
          Log.i(TAG, "json reader - close exception", ioe);
        }
      }
    }
  }

  private static List<Object> decodeArray(JsonReader reader) throws IOException {
    List<Object> array = Lists.newArrayList();
    reader.beginArray();
    while (reader.hasNext()) {
      switch (reader.peek()) {
        case BEGIN_OBJECT:
          array.add(decodeObject(reader));
          break;
        case NULL:
          reader.nextNull();
          array.add(null);
          break;
        case STRING:
          array.add(reader.nextString());
          break;
        case BOOLEAN:
          array.add(reader.nextBoolean());
          break;
        case BEGIN_ARRAY:
          array.add(decodeArray(reader));
          break;
        case NUMBER:
          array.add(decodeNumber(reader.nextString()));
          break;
        default:
          throw new IllegalStateException(String.format("%s: bogus token", reader.peek()));
      }
    }

    reader.endArray();
    return array;
  }

  private static Number decodeNumber(String value) {
    try {
      return Integer.valueOf(value);
    } catch (NumberFormatException i) {
      try {
        return Long.valueOf(value);
      } catch (NumberFormatException i2) {
        try {
          return Double.valueOf(value);
        } catch (NumberFormatException i3) {
          try {
            return new BigInteger(value);
          } catch (NumberFormatException i4) {
            return new BigDecimal(value);
          }
        }
      }
    }
  }

  private static Object decodeObject(JsonReader reader) throws IOException {
    Map<String, Object> obj = Maps.newHashMap();
    List<String> nullKeys = Lists.newArrayList();
    reader.beginObject();
    while (reader.hasNext()) {
      String key = reader.nextName();
      Object value = null;
      switch (reader.peek()) {
        case BEGIN_OBJECT:
          obj.put(key, decodeObject(reader));
          break;
        case NULL:
          reader.nextNull();
          nullKeys.add(key);
          obj.put(key, JSONObject.NULL);
          break;
        case STRING:
          obj.put(key, reader.nextString());
          break;
        case BOOLEAN:
          obj.put(key, reader.nextBoolean());
          break;
        case NUMBER:
          obj.put(key, decodeNumber(reader.nextString()));
          break;
        case BEGIN_ARRAY:
          obj.put(key, decodeArray(reader));
          break;
        default:
          throw new IllegalStateException(String.format("%s: bogus token.", reader.peek()));
      }
    }
    reader.endObject();
    Object replacement = maybeReplaceMap(obj);
    if (null != replacement) {
      return replacement;
    } else {
      for (String key : nullKeys) {
        obj.remove(key);
      }
    }
    return obj;
  }

  private static Object maybeReplaceMap(Map<String, Object> obj) {
    for (JSONAble.DeJSONFactory dejsonizer : DEJSONIZERS) {
      Object maybe = dejsonizer.attemptDeJSONize(obj);
      if (null != maybe) {
        return maybe;
      }
    }
    return null;
  }

  private static JSONStringer encodeHelper(Object javaObject, JSONStringer stringer)
      throws JSONException {
    if (null == javaObject) {
      stringer.value(javaObject);
    } else if (javaObject instanceof Map) {
      stringer.object();
      Set<Map.Entry> entries = ((Map) javaObject).entrySet();
      for (Map.Entry entry : entries) {
        stringer.key(entry.getKey().toString());
        encodeHelper(entry.getValue(), stringer);
      }
      stringer.endObject();
    } else if (javaObject instanceof Iterable) {
      stringer.array();
      for (Object obj : ((Iterable) javaObject)) {
        encodeHelper(obj, stringer);
      }
      stringer.endArray();
    } else if (javaObject instanceof Object[]) {
      stringer.array();
      for (Object obj : ((Object[]) javaObject)) {
        encodeHelper(obj, stringer);
      }
      stringer.endArray();
    } else if (javaObject instanceof JSONAble) {
      JSONObject jsonObj = new JSONObject(((JSONAble) javaObject).toJSONString());
      stringer.value(jsonObj);
    } else {
      boolean converted = false;
      for (Class valuableClazz : VALUEABLE_CLASSES) {
        if (valuableClazz.isAssignableFrom(javaObject.getClass())) {
          converted = true;
          stringer.value(javaObject);
        }
      }
      checkState(
          converted,
          "%s: not encodable. Want one of: %s",
          javaObject.getClass(),
          VALUEABLE_CLASSES);
    }
    return stringer;
  }
}