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.6.1'

  • groupId: androidx.test.espresso
  • artifactId: espresso-web
  • version: 3.6.1

Artifact androidx.test.espresso:espresso-web:3.6.1 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 androidx.test.internal.util.Checks.checkArgument;
import static androidx.test.internal.util.Checks.checkNotNull;
import static androidx.test.internal.util.Checks.checkState;
import static java.util.Collections.unmodifiableSet;

import android.util.JsonReader;
import android.util.Log;
import java.io.IOException;
import java.io.StringReader;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
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;

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

  private static final Set<Class<?>> VALUEABLE_CLASSES =
      unmodifiableSet(
          new LinkedHashSet<>(
              Arrays.asList(
                  Boolean.class, Number.class, String.class, JSONObject.class, JSONArray.class)));

  private static final Set<Class<?>> TOP_LEVEL_CLASSES =
      unmodifiableSet(
          new LinkedHashSet<>(
              Arrays.asList(
                  JSONObject.class,
                  JSONArray.class,
                  Iterable.class,
                  Object[].class,
                  Map.class,
                  JSONAble.class)));

  private static final CopyOnWriteArrayList<JSONAble.DeJSONFactory> DEJSONIZERS =
      new CopyOnWriteArrayList<>(
          Arrays.asList(
              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 {
      return decodeViaJSONReader(json);
    } catch (IOException ioe) {
      throw new RuntimeException(String.format("Could not parse: %s", json), ioe);
    }
  }

  private static List<Object> decodeArray(JSONArray array) throws JSONException {
    List<Object> data = new ArrayList<>();
    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 = new ArrayList<>();
    Map<String, Object> obj = new LinkedHashMap<>();
    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 = new ArrayList<>();
    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 = new LinkedHashMap<>();
    List<String> nullKeys = new ArrayList<>();
    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;
  }
}