blob: 913b50dcde2106532e06ed4f41250387553cad1b [file] [log] [blame]
/*
* Copyright 2010 Google Inc.
*
* 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 com.google.web.bindery.autobean.vm.impl;
import com.google.gwt.core.client.impl.WeakMapping;
import com.google.web.bindery.autobean.shared.Splittable;
import com.google.web.bindery.autobean.shared.impl.HasSplittable;
import com.google.web.bindery.autobean.shared.impl.StringQuoter;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
/**
* Uses the org.json packages to slice and dice request payloads.
*/
public class JsonSplittable implements Splittable, HasSplittable {
public static JsonSplittable create() {
return new JsonSplittable(new JSONObject());
}
public static Splittable create(String payload) {
try {
switch (payload.charAt(0)) {
case '{':
return new JsonSplittable(new JSONObject(payload));
case '[':
return new JsonSplittable(new JSONArray(payload));
case '"':
return new JsonSplittable(new JSONArray("[" + payload + "]").getString(0));
case '-':
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
return new JsonSplittable(Double.parseDouble(payload));
case 't':
case 'f':
return new JsonSplittable(Boolean.parseBoolean(payload));
case 'n':
return null;
default:
throw new RuntimeException("Could not parse payload: payload[0] = " + payload.charAt(0));
}
} catch (JSONException e) {
throw new RuntimeException("Could not parse payload", e);
}
}
public static Splittable createIndexed() {
return new JsonSplittable(new JSONArray());
}
public static Splittable createNull() {
return new JsonSplittable();
}
/**
* Private equivalent of org.json.JSONObject.getNames(JSONObject) since that
* method is not available in Android 2.2. Used to represent a null value.
*/
private static String[] getNames(JSONObject json) {
int length = json.length();
if (length == 0) {
return null;
}
String[] names = new String[length];
Iterator<?> i = json.keys();
int j = 0;
while (i.hasNext()) {
names[j++] = (String) i.next();
}
return names;
}
private JSONArray array;
private Boolean bool;
/**
* Used to represent a null value.
*/
private boolean isNull;
private Double number;
private JSONObject obj;
private String string;
private final Map<String, Object> reified = new HashMap<String, Object>();
/**
* Constructor for a null value.
*/
private JsonSplittable() {
isNull = true;
}
private JsonSplittable(boolean value) {
this.bool = value;
}
private JsonSplittable(double value) {
this.number = value;
}
private JsonSplittable(JSONArray array) {
this.array = array;
}
private JsonSplittable(JSONObject obj) {
this.obj = obj;
}
private JsonSplittable(String string) {
this.array = null;
this.obj = null;
this.string = string;
}
public boolean asBoolean() {
return bool;
}
public double asNumber() {
return number;
}
public void assign(Splittable parent, int index) {
try {
((JsonSplittable) parent).array.put(index, value());
} catch (JSONException e) {
throw new RuntimeException(e);
}
}
public void assign(Splittable parent, String propertyName) {
try {
((JsonSplittable) parent).obj.put(propertyName, value());
} catch (JSONException e) {
throw new RuntimeException(e);
}
}
public String asString() {
return string;
}
public Splittable deepCopy() {
return create(getPayload());
}
public Splittable get(int index) {
try {
return makeSplittable(array.get(index));
} catch (JSONException e) {
throw new RuntimeException(e);
}
}
public Splittable get(String key) {
try {
return makeSplittable(obj.get(key));
} catch (JSONException e) {
throw new RuntimeException(key, e);
}
}
public String getPayload() {
if (isNull) {
return "null";
}
if (obj != null) {
return obj.toString();
}
if (array != null) {
return array.toString();
}
if (string != null) {
return StringQuoter.quote(string);
}
if (number != null) {
return String.valueOf(number);
}
if (bool != null) {
return String.valueOf(bool);
}
throw new RuntimeException("No data in this JsonSplittable");
}
public List<String> getPropertyKeys() {
String[] names = getNames(obj);
if (names == null) {
return Collections.emptyList();
} else {
return Collections.unmodifiableList(Arrays.asList(names));
}
}
public Object getReified(String key) {
return reified.get(key);
}
public Splittable getSplittable() {
return this;
}
public boolean isBoolean() {
return bool != null;
}
public boolean isIndexed() {
return array != null;
}
public boolean isKeyed() {
return obj != null;
}
public boolean isNull(int index) {
return array.isNull(index);
}
public boolean isNull(String key) {
// Treat undefined and null as the same
return !obj.has(key) || obj.isNull(key);
}
public boolean isNumber() {
return number != null;
}
public boolean isReified(String key) {
return reified.containsKey(key);
}
public boolean isString() {
return string != null;
}
public boolean isUndefined(String key) {
return !obj.has(key);
}
public void setReified(String key, Object object) {
reified.put(key, object);
}
public void setSize(int size) {
// This is terrible, but there's no API support for resizing or splicing
JSONArray newArray = new JSONArray();
for (int i = 0; i < size; i++) {
try {
newArray.put(i, array.get(i));
} catch (JSONException e) {
throw new RuntimeException(e);
}
}
array = newArray;
}
public int size() {
return array.length();
}
/**
* For debugging use only.
*/
@Override
public String toString() {
return getPayload();
}
private synchronized JsonSplittable makeSplittable(Object object) {
if (JSONObject.NULL.equals(object)) {
return null;
}
/*
* Maintain a 1:1 mapping between object instances and JsonSplittables.
* Doing this with a WeakHashMap doesn't work on Android, since its org.json
* arrays appear to have value-based equality.
*/
JsonSplittable seen = (JsonSplittable) WeakMapping.get(object, JsonSplittable.class.getName());
if (seen == null) {
if (object instanceof JSONObject) {
seen = new JsonSplittable((JSONObject) object);
WeakMapping.setWeak(object, JsonSplittable.class.getName(), seen);
} else if (object instanceof JSONArray) {
seen = new JsonSplittable((JSONArray) object);
WeakMapping.setWeak(object, JsonSplittable.class.getName(), seen);
} else if (object instanceof String) {
seen = new JsonSplittable(object.toString());
} else if (object instanceof Number) {
seen = new JsonSplittable(((Number) object).doubleValue());
} else if (object instanceof Boolean) {
seen = new JsonSplittable((Boolean) object);
} else {
throw new RuntimeException("Unhandled type " + object.getClass());
}
}
return seen;
}
private Object value() {
if (isNull) {
return null;
}
if (obj != null) {
return obj;
}
if (array != null) {
return array;
}
if (string != null) {
return string;
}
if (number != null) {
return number;
}
if (bool != null) {
return bool;
}
throw new RuntimeException("No data");
}
}