Wed, 31 Dec 2014 06:09:35 +0100
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 }