Java tutorial
/* * 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 android.support.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 com.google.common.collect.ImmutableSet; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import android.os.Build; import android.util.JsonReader; import android.util.Log; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import org.json.JSONStringer; import org.json.JSONTokener; import java.io.IOException; import java.io.StringReader; import java.math.BigDecimal; import java.math.BigInteger; import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.CopyOnWriteArrayList; /** * Encodes/Decodes JSON. */ public final class ModelCodec { private static final String TAG = "JS_CODEC"; private static final Collection<Class<?>> VALUEABLE_CLASSES = ImmutableSet.of(Boolean.class, Number.class, String.class, JSONObject.class, JSONArray.class); private static final Collection<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("%: 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; } }