mobile/android/base/sync/ExtendedJSONObject.java

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

     1 /* This Source Code Form is subject to the terms of the Mozilla Public
     2  * License, v. 2.0. If a copy of the MPL was not distributed with this
     3  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     5 package org.mozilla.gecko.sync;
     7 import java.io.IOException;
     8 import java.io.Reader;
     9 import java.io.StringReader;
    10 import java.util.Map;
    11 import java.util.Map.Entry;
    12 import java.util.Set;
    14 import org.json.simple.JSONArray;
    15 import org.json.simple.JSONObject;
    16 import org.json.simple.parser.JSONParser;
    17 import org.json.simple.parser.ParseException;
    18 import org.mozilla.apache.commons.codec.binary.Base64;
    19 import org.mozilla.gecko.sync.UnexpectedJSONException.BadRequiredFieldJSONException;
    21 /**
    22  * Extend JSONObject to do little things, like, y'know, accessing members.
    23  *
    24  * @author rnewman
    25  *
    26  */
    27 public class ExtendedJSONObject {
    29   public JSONObject object;
    31   /**
    32    * Return a <code>JSONParser</code> instance for immediate use.
    33    * <p>
    34    * <code>JSONParser</code> is not thread-safe, so we return a new instance
    35    * each call. This is extremely inefficient in execution time and especially
    36    * memory use -- each instance allocates a 16kb temporary buffer -- and we
    37    * hope to improve matters eventually.
    38    */
    39   protected static JSONParser getJSONParser() {
    40     return new JSONParser();
    41   }
    43   /**
    44    * Parse a JSON encoded string.
    45    *
    46    * @param in <code>Reader</code> over a JSON-encoded input to parse; not
    47    *            necessarily a JSON object.
    48    * @return a regular Java <code>Object</code>.
    49    * @throws ParseException
    50    * @throws IOException
    51    */
    52   protected static Object parseRaw(Reader in) throws ParseException, IOException {
    53     try {
    54       return getJSONParser().parse(in);
    55     } catch (Error e) {
    56       // Don't be stupid, org.json.simple. Bug 1042929.
    57       throw new ParseException(ParseException.ERROR_UNEXPECTED_EXCEPTION);
    58     }
    59   }
    61   /**
    62    * Parse a JSON encoded string.
    63    * <p>
    64    * You should prefer the streaming interface {@link #parseRaw(Reader)}.
    65    *
    66    * @param input JSON-encoded input string to parse; not necessarily a JSON object.
    67    * @return a regular Java <code>Object</code>.
    68    * @throws ParseException
    69    */
    70   protected static Object parseRaw(String input) throws ParseException {
    71     try {
    72       return getJSONParser().parse(input);
    73     } catch (Error e) {
    74       // Don't be stupid, org.json.simple. Bug 1042929.
    75       throw new ParseException(ParseException.ERROR_UNEXPECTED_EXCEPTION);
    76     }
    77   }
    79   /**
    80    * Helper method to get a JSON array from a stream.
    81    *
    82    * @param in <code>Reader</code> over a JSON-encoded array to parse.
    83    * @throws ParseException
    84    * @throws IOException
    85    * @throws NonArrayJSONException if the object is valid JSON, but not an array.
    86    */
    87   public static JSONArray parseJSONArray(Reader in)
    88       throws IOException, ParseException, NonArrayJSONException {
    89     Object o = parseRaw(in);
    91     if (o == null) {
    92       return null;
    93     }
    95     if (o instanceof JSONArray) {
    96       return (JSONArray) o;
    97     }
    99     throw new NonArrayJSONException("value must be a JSON array");
   100   }
   102   /**
   103    * Helper method to get a JSON array from a string.
   104    * <p>
   105    * You should prefer the stream interface {@link #parseJSONArray(Reader)}.
   106    *
   107    * @param jsonString input.
   108    * @throws ParseException
   109    * @throws IOException
   110    * @throws NonArrayJSONException if the object is valid JSON, but not an array.
   111    */
   112   public static JSONArray parseJSONArray(String jsonString)
   113       throws IOException, ParseException, NonArrayJSONException {
   114     Object o = parseRaw(jsonString);
   116     if (o == null) {
   117       return null;
   118     }
   120     if (o instanceof JSONArray) {
   121       return (JSONArray) o;
   122     }
   124     throw new NonArrayJSONException("value must be a JSON array");
   125   }
   127   /**
   128    * Helper method to get a JSON object from a stream.
   129    *
   130    * @param in input {@link Reader}.
   131    * @throws ParseException
   132    * @throws IOException
   133    * @throws NonArrayJSONException if the object is valid JSON, but not an object.
   134    */
   135   public static ExtendedJSONObject parseJSONObject(Reader in)
   136       throws IOException, ParseException, NonObjectJSONException {
   137     return new ExtendedJSONObject(in);
   138   }
   140   /**
   141    * Helper method to get a JSON object from a string.
   142    * <p>
   143    * You should prefer the stream interface {@link #parseJSONObject(Reader)}.
   144    *
   145    * @param jsonString input.
   146    * @throws ParseException
   147    * @throws IOException
   148    * @throws NonObjectJSONException if the object is valid JSON, but not an object.
   149    */
   150   public static ExtendedJSONObject parseJSONObject(String jsonString)
   151       throws IOException, ParseException, NonObjectJSONException {
   152     return new ExtendedJSONObject(jsonString);
   153   }
   155   /**
   156    * Helper method to get a JSON object from a UTF-8 byte array.
   157    *
   158    * @param in UTF-8 bytes.
   159    * @throws ParseException
   160    * @throws NonObjectJSONException if the object is valid JSON, but not an object.
   161    * @throws IOException
   162    */
   163   public static ExtendedJSONObject parseUTF8AsJSONObject(byte[] in)
   164       throws ParseException, NonObjectJSONException, IOException {
   165     return parseJSONObject(new String(in, "UTF-8"));
   166   }
   168   public ExtendedJSONObject() {
   169     this.object = new JSONObject();
   170   }
   172   public ExtendedJSONObject(JSONObject o) {
   173     this.object = o;
   174   }
   176   public ExtendedJSONObject(Reader in) throws IOException, ParseException, NonObjectJSONException {
   177     if (in == null) {
   178       this.object = new JSONObject();
   179       return;
   180     }
   182     Object obj = parseRaw(in);
   183     if (obj instanceof JSONObject) {
   184       this.object = ((JSONObject) obj);
   185     } else {
   186       throw new NonObjectJSONException("value must be a JSON object");
   187     }
   188   }
   190   public ExtendedJSONObject(String jsonString) throws IOException, ParseException, NonObjectJSONException {
   191     this(jsonString == null ? null : new StringReader(jsonString));
   192   }
   194   // Passthrough methods.
   195   public Object get(String key) {
   196     return this.object.get(key);
   197   }
   199   public Long getLong(String key) {
   200     return (Long) this.get(key);
   201   }
   203   public String getString(String key) {
   204     return (String) this.get(key);
   205   }
   207   public Boolean getBoolean(String key) {
   208     return (Boolean) this.get(key);
   209   }
   211   /**
   212    * Return an Integer if the value for this key is an Integer, Long, or String
   213    * that can be parsed as a base 10 Integer.
   214    * Passes through null.
   215    *
   216    * @throws NumberFormatException
   217    */
   218   public Integer getIntegerSafely(String key) throws NumberFormatException {
   219     Object val = this.object.get(key);
   220     if (val == null) {
   221       return null;
   222     }
   223     if (val instanceof Integer) {
   224       return (Integer) val;
   225     }
   226     if (val instanceof Long) {
   227       return Integer.valueOf(((Long) val).intValue());
   228     }
   229     if (val instanceof String) {
   230       return Integer.parseInt((String) val, 10);
   231     }
   232     throw new NumberFormatException("Expecting Integer, got " + val.getClass());
   233   }
   235   /**
   236    * Return a server timestamp value as milliseconds since epoch.
   237    *
   238    * @param key
   239    * @return A Long, or null if the value is non-numeric or doesn't exist.
   240    */
   241   public Long getTimestamp(String key) {
   242     Object val = this.object.get(key);
   244     // This is absurd.
   245     if (val instanceof Double) {
   246       double millis = ((Double) val).doubleValue() * 1000;
   247       return Double.valueOf(millis).longValue();
   248     }
   249     if (val instanceof Float) {
   250       double millis = ((Float) val).doubleValue() * 1000;
   251       return Double.valueOf(millis).longValue();
   252     }
   253     if (val instanceof Number) {
   254       // Must be an integral number.
   255       return ((Number) val).longValue() * 1000;
   256     }
   258     return null;
   259   }
   261   public boolean containsKey(String key) {
   262     return this.object.containsKey(key);
   263   }
   265   public String toJSONString() {
   266     return this.object.toJSONString();
   267   }
   269   public String toString() {
   270     return this.object.toString();
   271   }
   273   public void put(String key, Object value) {
   274     @SuppressWarnings("unchecked")
   275     Map<Object, Object> map = this.object;
   276     map.put(key, value);
   277   }
   279   @SuppressWarnings({ "unchecked", "rawtypes" })
   280   public void putAll(Map map) {
   281     this.object.putAll(map);
   282   }
   284   /**
   285    * Remove key-value pair from JSONObject.
   286    *
   287    * @param key
   288    *          to be removed.
   289    * @return true if key exists and was removed, false otherwise.
   290    */
   291   public boolean remove(String key) {
   292     Object res = this.object.remove(key);
   293     return (res != null);
   294   }
   296   public ExtendedJSONObject getObject(String key) throws NonObjectJSONException {
   297     Object o = this.object.get(key);
   298     if (o == null) {
   299       return null;
   300     }
   301     if (o instanceof ExtendedJSONObject) {
   302       return (ExtendedJSONObject) o;
   303     }
   304     if (o instanceof JSONObject) {
   305       return new ExtendedJSONObject((JSONObject) o);
   306     }
   307     throw new NonObjectJSONException("key must be a JSON object: " + key);
   308   }
   310   @SuppressWarnings("unchecked")
   311   public Set<Entry<String, Object>> entrySet() {
   312     return this.object.entrySet();
   313   }
   315   @SuppressWarnings("unchecked")
   316   public Set<String> keySet() {
   317     return this.object.keySet();
   318   }
   320   public org.json.simple.JSONArray getArray(String key) throws NonArrayJSONException {
   321     Object o = this.object.get(key);
   322     if (o == null) {
   323       return null;
   324     }
   325     if (o instanceof JSONArray) {
   326       return (JSONArray) o;
   327     }
   328     throw new NonArrayJSONException("key must be a JSON array: " + key);
   329   }
   331   public int size() {
   332     return this.object.size();
   333   }
   335   @Override
   336   public int hashCode() {
   337     if (this.object == null) {
   338       return getClass().hashCode();
   339     }
   340     return this.object.hashCode() ^ getClass().hashCode();
   341   }
   343   @Override
   344   public boolean equals(Object o) {
   345     if (o == null || !(o instanceof ExtendedJSONObject)) {
   346       return false;
   347     }
   348     if (o == this) {
   349       return true;
   350     }
   351     ExtendedJSONObject other = (ExtendedJSONObject) o;
   352     if (this.object == null) {
   353       return other.object == null;
   354     }
   355     return this.object.equals(other.object);
   356   }
   358   /**
   359    * Throw if keys are missing or values have wrong types.
   360    *
   361    * @param requiredFields list of required keys.
   362    * @param requiredFieldClass class that values must be coercable to; may be null, which means don't check.
   363    * @throws UnexpectedJSONException
   364    */
   365   public void throwIfFieldsMissingOrMisTyped(String[] requiredFields, Class<?> requiredFieldClass) throws BadRequiredFieldJSONException {
   366     // Defensive as possible: verify object has expected key(s) with string value.
   367     for (String k : requiredFields) {
   368       Object value = get(k);
   369       if (value == null) {
   370         throw new BadRequiredFieldJSONException("Expected key not present in result: " + k);
   371       }
   372       if (requiredFieldClass != null && !(requiredFieldClass.isInstance(value))) {
   373         throw new BadRequiredFieldJSONException("Value for key not an instance of " + requiredFieldClass + ": " + k);
   374       }
   375     }
   376   }
   378   /**
   379    * Return a base64-encoded string value as a byte array.
   380    */
   381   public byte[] getByteArrayBase64(String key) {
   382     String s = (String) this.object.get(key);
   383     if (s == null) {
   384       return null;
   385     }
   386     return Base64.decodeBase64(s);
   387   }
   389   /**
   390    * Return a hex-encoded string value as a byte array.
   391    */
   392   public byte[] getByteArrayHex(String key) {
   393     String s = (String) this.object.get(key);
   394     if (s == null) {
   395       return null;
   396     }
   397     return Utils.hex2Byte(s);
   398   }
   399 }

mercurial