| /* |
| * 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); |
| } |
| |
| @Override |
| public void removeReified(String key) { |
| reified.remove(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(); |
| } |
| |
| 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"); |
| } |
| } |