Adding an Apache-licensed implementation of org.json
Change-Id: I1b67bac70bd25220a619e6ebe61f7f1c6f316faa
Jesse Wilson authored 14 years ago
Thomas Koch committed 11 years ago
0 | /* | |
1 | * Copyright (C) 2010 The Android Open Source Project | |
2 | * | |
3 | * Licensed under the Apache License, Version 2.0 (the "License"); | |
4 | * you may not use this file except in compliance with the License. | |
5 | * You may obtain a copy of the License at | |
6 | * | |
7 | * http://www.apache.org/licenses/LICENSE-2.0 | |
8 | * | |
9 | * Unless required by applicable law or agreed to in writing, software | |
10 | * distributed under the License is distributed on an "AS IS" BASIS, | |
11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
12 | * See the License for the specific language governing permissions and | |
13 | * limitations under the License. | |
14 | */ | |
15 | ||
16 | package org.json; | |
17 | ||
18 | class JSON { | |
19 | /** | |
20 | * Returns the input if it is a JSON-permissable value; throws otherwise. | |
21 | */ | |
22 | static double checkDouble(double d) throws JSONException { | |
23 | if (Double.isInfinite(d) || Double.isNaN(d)) { | |
24 | throw new JSONException("Forbidden numeric value: " + d); | |
25 | } | |
26 | return d; | |
27 | } | |
28 | ||
29 | static Boolean toBoolean(Object value) { | |
30 | if (value instanceof Boolean) { | |
31 | return (Boolean) value; | |
32 | } else if (value instanceof String) { | |
33 | return Boolean.valueOf(((String) value)); | |
34 | } else { | |
35 | return null; | |
36 | } | |
37 | } | |
38 | ||
39 | static Double toDouble(Object value) { | |
40 | if (value instanceof Double) { | |
41 | return (Double) value; | |
42 | } else if (value instanceof Number) { | |
43 | return ((Number) value).doubleValue(); | |
44 | } else if (value instanceof String) { | |
45 | try { | |
46 | return Double.valueOf((String) value); | |
47 | } catch (NumberFormatException e) { | |
48 | } | |
49 | } | |
50 | return null; | |
51 | } | |
52 | ||
53 | static Integer toInteger(Object value) { | |
54 | if (value instanceof Integer) { | |
55 | return (Integer) value; | |
56 | } else if (value instanceof Number) { | |
57 | return ((Number) value).intValue(); | |
58 | } else if (value instanceof String) { | |
59 | try { | |
60 | return Double.valueOf((String) value).intValue(); | |
61 | } catch (NumberFormatException e) { | |
62 | } | |
63 | } | |
64 | return null; | |
65 | } | |
66 | ||
67 | static Long toLong(Object value) { | |
68 | if (value instanceof Long) { | |
69 | return (Long) value; | |
70 | } else if (value instanceof Number) { | |
71 | return ((Number) value).longValue(); | |
72 | } else if (value instanceof String) { | |
73 | try { | |
74 | return Double.valueOf((String) value).longValue(); | |
75 | } catch (NumberFormatException e) { | |
76 | } | |
77 | } | |
78 | return null; | |
79 | } | |
80 | ||
81 | static String toString(Object value) { | |
82 | if (value instanceof String) { | |
83 | return (String) value; | |
84 | } else if (value != null) { | |
85 | return String.valueOf(value); | |
86 | } | |
87 | return null; | |
88 | } | |
89 | ||
90 | public static JSONException typeMismatch(Object indexOrName, Object actual, | |
91 | String requiredType) throws JSONException { | |
92 | if (actual == null) { | |
93 | throw new JSONException("Value at " + indexOrName + " is null."); | |
94 | } else { | |
95 | throw new JSONException("Value " + actual + " at " + indexOrName | |
96 | + " of type " + actual.getClass().getName() | |
97 | + " cannot be converted to " + requiredType); | |
98 | } | |
99 | } | |
100 | ||
101 | public static JSONException typeMismatch(Object actual, String requiredType) | |
102 | throws JSONException { | |
103 | if (actual == null) { | |
104 | throw new JSONException("Value is null."); | |
105 | } else { | |
106 | throw new JSONException("Value " + actual | |
107 | + " of type " + actual.getClass().getName() | |
108 | + " cannot be converted to " + requiredType); | |
109 | } | |
110 | } | |
111 | } |
0 | /* | |
1 | * Copyright (C) 2010 The Android Open Source Project | |
2 | * | |
3 | * Licensed under the Apache License, Version 2.0 (the "License"); | |
4 | * you may not use this file except in compliance with the License. | |
5 | * You may obtain a copy of the License at | |
6 | * | |
7 | * http://www.apache.org/licenses/LICENSE-2.0 | |
8 | * | |
9 | * Unless required by applicable law or agreed to in writing, software | |
10 | * distributed under the License is distributed on an "AS IS" BASIS, | |
11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
12 | * See the License for the specific language governing permissions and | |
13 | * limitations under the License. | |
14 | */ | |
15 | ||
16 | package org.json; | |
17 | ||
18 | import java.util.List; | |
19 | import java.util.ArrayList; | |
20 | import java.util.Collection; | |
21 | ||
22 | // Note: this class was written without inspecting the non-free org.json sourcecode. | |
23 | ||
24 | /** | |
25 | * An indexed sequence of JSON-safe values. | |
26 | */ | |
27 | public class JSONArray { | |
28 | ||
29 | private final List<Object> values; | |
30 | ||
31 | public JSONArray() { | |
32 | values = new ArrayList<Object>(); | |
33 | } | |
34 | ||
35 | /* Accept a raw type for API compatibility */ | |
36 | public JSONArray(Collection copyFrom) { | |
37 | this(); | |
38 | Collection<?> copyFromTyped = (Collection<?>) copyFrom; | |
39 | values.addAll(copyFromTyped); | |
40 | } | |
41 | ||
42 | public JSONArray(JSONTokener readFrom) throws JSONException { | |
43 | /* | |
44 | * Getting the parser to populate this could get tricky. Instead, just | |
45 | * parse to temporary JSONArray and then steal the data from that. | |
46 | */ | |
47 | Object object = readFrom.nextValue(); | |
48 | if (object instanceof JSONArray) { | |
49 | values = ((JSONArray) object).values; | |
50 | } else { | |
51 | throw JSON.typeMismatch(object, "JSONArray"); | |
52 | } | |
53 | } | |
54 | ||
55 | public JSONArray(String json) throws JSONException { | |
56 | this(new JSONTokener(json)); | |
57 | } | |
58 | ||
59 | public int length() { | |
60 | return values.size(); | |
61 | } | |
62 | ||
63 | public JSONArray put(boolean value) { | |
64 | values.add(value); | |
65 | return this; | |
66 | } | |
67 | ||
68 | public JSONArray put(double value) throws JSONException { | |
69 | values.add(JSON.checkDouble(value)); | |
70 | return this; | |
71 | } | |
72 | ||
73 | public JSONArray put(int value) { | |
74 | values.add(value); | |
75 | return this; | |
76 | } | |
77 | ||
78 | public JSONArray put(long value) { | |
79 | values.add(value); | |
80 | return this; | |
81 | } | |
82 | ||
83 | public JSONArray put(Object value) { | |
84 | values.add(value); | |
85 | return this; | |
86 | } | |
87 | ||
88 | public JSONArray put(int index, boolean value) throws JSONException { | |
89 | return put(index, (Boolean) value); | |
90 | } | |
91 | ||
92 | public JSONArray put(int index, double value) throws JSONException { | |
93 | return put(index, (Double) value); | |
94 | } | |
95 | ||
96 | public JSONArray put(int index, int value) throws JSONException { | |
97 | return put(index, (Integer) value); | |
98 | } | |
99 | ||
100 | public JSONArray put(int index, long value) throws JSONException { | |
101 | return put(index, (Long) value); | |
102 | } | |
103 | ||
104 | public JSONArray put(int index, Object value) throws JSONException { | |
105 | if (value instanceof Number) { | |
106 | // deviate from the original by checking all Numbers, not just floats & doubles | |
107 | JSON.checkDouble(((Number) value).doubleValue()); | |
108 | } | |
109 | while (values.size() <= index) { | |
110 | values.add(null); | |
111 | } | |
112 | values.set(index, value); | |
113 | return this; | |
114 | } | |
115 | ||
116 | public boolean isNull(int index) { | |
117 | Object value = opt(index); | |
118 | return value == null || value == JSONObject.NULL; | |
119 | } | |
120 | ||
121 | public Object get(int index) throws JSONException { | |
122 | try { | |
123 | Object value = values.get(index); | |
124 | if (value == null) { | |
125 | throw new JSONException("Value at " + index + " is null."); | |
126 | } | |
127 | return value; | |
128 | } catch (IndexOutOfBoundsException e) { | |
129 | throw new JSONException("Index " + index + " out of range [0.." + values.size() + ")"); | |
130 | } | |
131 | } | |
132 | ||
133 | public Object opt(int index) { | |
134 | if (index < 0 || index >= values.size()) { | |
135 | return null; | |
136 | } | |
137 | return values.get(index); | |
138 | } | |
139 | ||
140 | public boolean getBoolean(int index) throws JSONException { | |
141 | Object object = get(index); | |
142 | Boolean result = JSON.toBoolean(object); | |
143 | if (result == null) { | |
144 | throw JSON.typeMismatch(index, object, "boolean"); | |
145 | } | |
146 | return result; | |
147 | } | |
148 | ||
149 | public boolean optBoolean(int index) { | |
150 | return optBoolean(index, false); | |
151 | } | |
152 | ||
153 | public boolean optBoolean(int index, boolean fallback) { | |
154 | Object object = opt(index); | |
155 | Boolean result = JSON.toBoolean(object); | |
156 | return result != null ? result : fallback; | |
157 | } | |
158 | ||
159 | public double getDouble(int index) throws JSONException { | |
160 | Object object = get(index); | |
161 | Double result = JSON.toDouble(object); | |
162 | if (result == null) { | |
163 | throw JSON.typeMismatch(index, object, "double"); | |
164 | } | |
165 | return result; | |
166 | } | |
167 | ||
168 | public double optDouble(int index) { | |
169 | return optDouble(index, Double.NaN); | |
170 | } | |
171 | ||
172 | public double optDouble(int index, double fallback) { | |
173 | Object object = opt(index); | |
174 | Double result = JSON.toDouble(object); | |
175 | return result != null ? result : fallback; | |
176 | } | |
177 | ||
178 | public int getInt(int index) throws JSONException { | |
179 | Object object = get(index); | |
180 | Integer result = JSON.toInteger(object); | |
181 | if (result == null) { | |
182 | throw JSON.typeMismatch(index, object, "int"); | |
183 | } | |
184 | return result; | |
185 | } | |
186 | ||
187 | public int optInt(int index) { | |
188 | return optInt(index, 0); | |
189 | } | |
190 | ||
191 | public int optInt(int index, int fallback) { | |
192 | Object object = opt(index); | |
193 | Integer result = JSON.toInteger(object); | |
194 | return result != null ? result : fallback; | |
195 | } | |
196 | ||
197 | public long getLong(int index) throws JSONException { | |
198 | Object object = get(index); | |
199 | Long result = JSON.toLong(object); | |
200 | if (result == null) { | |
201 | throw JSON.typeMismatch(index, object, "long"); | |
202 | } | |
203 | return result; | |
204 | } | |
205 | ||
206 | public long optLong(int index) { | |
207 | return optLong(index, 0L); | |
208 | } | |
209 | ||
210 | public long optLong(int index, long fallback) { | |
211 | Object object = opt(index); | |
212 | Long result = JSON.toLong(object); | |
213 | return result != null ? result : fallback; | |
214 | } | |
215 | ||
216 | public String getString(int index) throws JSONException { | |
217 | Object object = get(index); | |
218 | String result = JSON.toString(object); | |
219 | if (result == null) { | |
220 | throw JSON.typeMismatch(index, object, "String"); | |
221 | } | |
222 | return result; | |
223 | } | |
224 | ||
225 | public String optString(int index) { | |
226 | return optString(index, ""); | |
227 | } | |
228 | ||
229 | public String optString(int index, String fallback) { | |
230 | Object object = opt(index); | |
231 | String result = JSON.toString(object); | |
232 | return result != null ? result : fallback; | |
233 | } | |
234 | ||
235 | public JSONArray getJSONArray(int index) throws JSONException { | |
236 | Object object = get(index); | |
237 | if (object instanceof JSONArray) { | |
238 | return (JSONArray) object; | |
239 | } else { | |
240 | throw JSON.typeMismatch(index, object, "JSONArray"); | |
241 | } | |
242 | } | |
243 | ||
244 | public JSONArray optJSONArray(int index) { | |
245 | Object object = opt(index); | |
246 | return object instanceof JSONArray ? (JSONArray) object : null; | |
247 | } | |
248 | ||
249 | public JSONObject getJSONObject(int index) throws JSONException { | |
250 | Object object = get(index); | |
251 | if (object instanceof JSONObject) { | |
252 | return (JSONObject) object; | |
253 | } else { | |
254 | throw JSON.typeMismatch(index, object, "JSONObject"); | |
255 | } | |
256 | } | |
257 | ||
258 | public JSONObject optJSONObject(int index) { | |
259 | Object object = opt(index); | |
260 | return object instanceof JSONObject ? (JSONObject) object : null; | |
261 | } | |
262 | ||
263 | public JSONObject toJSONObject(JSONArray names) throws JSONException { | |
264 | JSONObject result = new JSONObject(); | |
265 | int length = Math.min(names.length(), values.size()); | |
266 | if (length == 0) { | |
267 | return null; | |
268 | } | |
269 | for (int i = 0; i < length; i++) { | |
270 | String name = JSON.toString(names.opt(i)); | |
271 | result.put(name, opt(i)); | |
272 | } | |
273 | return result; | |
274 | } | |
275 | ||
276 | public String join(String separator) throws JSONException { | |
277 | JSONStringer stringer = new JSONStringer(); | |
278 | stringer.open(JSONStringer.Scope.NULL, ""); | |
279 | for (int i = 0, size = values.size(); i < size; i++) { | |
280 | if (i > 0) { | |
281 | stringer.out.append(separator); | |
282 | } | |
283 | stringer.value(values.get(i)); | |
284 | } | |
285 | stringer.close(JSONStringer.Scope.NULL, JSONStringer.Scope.NULL, ""); | |
286 | return stringer.out.toString(); | |
287 | } | |
288 | ||
289 | @Override public String toString() { | |
290 | try { | |
291 | JSONStringer stringer = new JSONStringer(); | |
292 | writeTo(stringer); | |
293 | return stringer.toString(); | |
294 | } catch (JSONException e) { | |
295 | return null; | |
296 | } | |
297 | } | |
298 | ||
299 | public String toString(int indentSpaces) throws JSONException { | |
300 | JSONStringer stringer = new JSONStringer(indentSpaces); | |
301 | writeTo(stringer); | |
302 | return stringer.toString(); | |
303 | } | |
304 | ||
305 | void writeTo(JSONStringer stringer) throws JSONException { | |
306 | stringer.array(); | |
307 | for (Object value : values) { | |
308 | stringer.value(value); | |
309 | } | |
310 | stringer.endArray(); | |
311 | } | |
312 | ||
313 | @Override public boolean equals(Object o) { | |
314 | return o instanceof JSONArray && ((JSONArray) o).values.equals(values); | |
315 | } | |
316 | ||
317 | @Override public int hashCode() { | |
318 | // diverge from the original, which doesn't implement hashCode | |
319 | return values.hashCode(); | |
320 | } | |
321 | } |
0 | /* | |
1 | * Copyright (C) 2010 The Android Open Source Project | |
2 | * | |
3 | * Licensed under the Apache License, Version 2.0 (the "License"); | |
4 | * you may not use this file except in compliance with the License. | |
5 | * You may obtain a copy of the License at | |
6 | * | |
7 | * http://www.apache.org/licenses/LICENSE-2.0 | |
8 | * | |
9 | * Unless required by applicable law or agreed to in writing, software | |
10 | * distributed under the License is distributed on an "AS IS" BASIS, | |
11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
12 | * See the License for the specific language governing permissions and | |
13 | * limitations under the License. | |
14 | */ | |
15 | ||
16 | package org.json; | |
17 | ||
18 | // Note: this class was written without inspecting the non-free org.json sourcecode. | |
19 | ||
20 | /** | |
21 | * Thrown to indicate a problem with the JSON API. Such problems include: | |
22 | * <ul> | |
23 | * <li>Attempts to parse or construct malformed documents | |
24 | * <li>Use of null as a name | |
25 | * <li>Use of numeric types not available to JSON, such as {@link Double#NaN | |
26 | * NaN} or {@link Double#POSITIVE_INFINITY infinity}. | |
27 | * <li>Lookups using an out of range index or nonexistant name | |
28 | * <li>Type mismatches on lookups | |
29 | * </ul> | |
30 | * | |
31 | * <p>Although this is a checked exception, it is rarely recoverable. Most | |
32 | * callers should simply wrap this exception in an unchecked exception and | |
33 | * rethrow: | |
34 | * <pre> public JSONArray toJSONObject() { | |
35 | * try { | |
36 | * JSONObject result = new JSONObject(); | |
37 | * ... | |
38 | * } catch (JSONException e) { | |
39 | * throw new RuntimeException(e); | |
40 | * } | |
41 | * }</pre> | |
42 | */ | |
43 | public class JSONException extends Exception { | |
44 | ||
45 | public JSONException(String s) { | |
46 | super(s); | |
47 | } | |
48 | } |
0 | /* | |
1 | * Copyright (C) 2010 The Android Open Source Project | |
2 | * | |
3 | * Licensed under the Apache License, Version 2.0 (the "License"); | |
4 | * you may not use this file except in compliance with the License. | |
5 | * You may obtain a copy of the License at | |
6 | * | |
7 | * http://www.apache.org/licenses/LICENSE-2.0 | |
8 | * | |
9 | * Unless required by applicable law or agreed to in writing, software | |
10 | * distributed under the License is distributed on an "AS IS" BASIS, | |
11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
12 | * See the License for the specific language governing permissions and | |
13 | * limitations under the License. | |
14 | */ | |
15 | ||
16 | package org.json; | |
17 | ||
18 | import java.util.ArrayList; | |
19 | import java.util.HashMap; | |
20 | import java.util.Iterator; | |
21 | import java.util.Map; | |
22 | ||
23 | // Note: this class was written without inspecting the non-free org.json sourcecode. | |
24 | ||
25 | /** | |
26 | * | |
27 | * | |
28 | * <p>TODO: Note about self-use | |
29 | */ | |
30 | public class JSONObject { | |
31 | ||
32 | private static final Double NEGATIVE_ZERO = -0d; | |
33 | ||
34 | public static final Object NULL = new Object() { | |
35 | @Override public boolean equals(Object o) { | |
36 | return o == this || o == null; // API specifies this broken equals implementation | |
37 | } | |
38 | @Override public String toString() { | |
39 | return "null"; | |
40 | } | |
41 | }; | |
42 | ||
43 | private final Map<String, Object> nameValuePairs; | |
44 | ||
45 | public JSONObject() { | |
46 | nameValuePairs = new HashMap<String, Object>(); | |
47 | } | |
48 | ||
49 | /* Accept a raw type for API compatibility */ | |
50 | public JSONObject(Map copyFrom) { | |
51 | this(); | |
52 | Map<?, ?> contentsTyped = (Map<?, ?>) copyFrom; | |
53 | for (Map.Entry<?, ?> entry : contentsTyped.entrySet()) { | |
54 | /* | |
55 | * Deviate from the original by checking that keys are non-null and | |
56 | * of the proper type. (We still defer validating the values). | |
57 | */ | |
58 | String key = (String) entry.getKey(); | |
59 | if (key == null) { | |
60 | throw new NullPointerException(); | |
61 | } | |
62 | nameValuePairs.put(key, entry.getValue()); | |
63 | } | |
64 | } | |
65 | ||
66 | public JSONObject(JSONTokener readFrom) throws JSONException { | |
67 | /* | |
68 | * Getting the parser to populate this could get tricky. Instead, just | |
69 | * parse to temporary JSONObject and then steal the data from that. | |
70 | */ | |
71 | Object object = readFrom.nextValue(); | |
72 | if (object instanceof JSONObject) { | |
73 | this.nameValuePairs = ((JSONObject) object).nameValuePairs; | |
74 | } else { | |
75 | throw JSON.typeMismatch(object, "JSONObject"); | |
76 | } | |
77 | } | |
78 | ||
79 | public JSONObject(String json) throws JSONException { | |
80 | this(new JSONTokener(json)); | |
81 | } | |
82 | ||
83 | public JSONObject(JSONObject copyFrom, String[] names) throws JSONException { | |
84 | this(); | |
85 | for (String name : names) { | |
86 | Object value = copyFrom.opt(name); | |
87 | if (value != null) { | |
88 | nameValuePairs.put(name, value); | |
89 | } | |
90 | } | |
91 | } | |
92 | ||
93 | public int length() { | |
94 | return nameValuePairs.size(); | |
95 | } | |
96 | ||
97 | public JSONObject put(String name, boolean value) throws JSONException { | |
98 | nameValuePairs.put(checkName(name), value); | |
99 | return this; | |
100 | } | |
101 | ||
102 | public JSONObject put(String name, double value) throws JSONException { | |
103 | nameValuePairs.put(checkName(name), JSON.checkDouble(value)); | |
104 | return this; | |
105 | } | |
106 | ||
107 | public JSONObject put(String name, int value) throws JSONException { | |
108 | nameValuePairs.put(checkName(name), value); | |
109 | return this; | |
110 | } | |
111 | ||
112 | public JSONObject put(String name, long value) throws JSONException { | |
113 | nameValuePairs.put(checkName(name), value); | |
114 | return this; | |
115 | } | |
116 | ||
117 | public JSONObject put(String name, Object value) throws JSONException { | |
118 | if (value == null) { | |
119 | nameValuePairs.remove(name); | |
120 | return this; | |
121 | } | |
122 | if (value instanceof Number) { | |
123 | // deviate from the original by checking all Numbers, not just floats & doubles | |
124 | JSON.checkDouble(((Number) value).doubleValue()); | |
125 | } | |
126 | nameValuePairs.put(checkName(name), value); | |
127 | return this; | |
128 | } | |
129 | ||
130 | public JSONObject putOpt(String name, Object value) throws JSONException { | |
131 | if (name == null || value == null) { | |
132 | return this; | |
133 | } | |
134 | return put(name, value); | |
135 | } | |
136 | ||
137 | public JSONObject accumulate(String name, Object value) throws JSONException { | |
138 | Object current = nameValuePairs.get(checkName(name)); | |
139 | if (current == null) { | |
140 | put(name, value); | |
141 | } else if (current instanceof JSONArray) { | |
142 | JSONArray array = (JSONArray) current; | |
143 | array.put(value); | |
144 | } else { | |
145 | JSONArray array = new JSONArray(); | |
146 | array.put(current); | |
147 | array.put(value); // fails on bogus values | |
148 | nameValuePairs.put(name, array); | |
149 | } | |
150 | return this; | |
151 | } | |
152 | ||
153 | String checkName(String name) throws JSONException { | |
154 | if (name == null) { | |
155 | throw new JSONException("Names must be non-null"); | |
156 | } | |
157 | return name; | |
158 | } | |
159 | ||
160 | public Object remove(String name) { | |
161 | return nameValuePairs.remove(name); | |
162 | } | |
163 | ||
164 | public boolean isNull(String name) { | |
165 | Object value = nameValuePairs.get(name); | |
166 | return value == null || value == NULL; | |
167 | } | |
168 | ||
169 | public boolean has(String name) { | |
170 | return nameValuePairs.containsKey(name); | |
171 | } | |
172 | ||
173 | public Object get(String name) throws JSONException { | |
174 | Object result = nameValuePairs.get(name); | |
175 | if (result == null) { | |
176 | throw new JSONException("No value for " + name); | |
177 | } | |
178 | return result; | |
179 | } | |
180 | ||
181 | public Object opt(String name) { | |
182 | return nameValuePairs.get(name); | |
183 | } | |
184 | ||
185 | public boolean getBoolean(String name) throws JSONException { | |
186 | Object object = get(name); | |
187 | Boolean result = JSON.toBoolean(object); | |
188 | if (result == null) { | |
189 | throw JSON.typeMismatch(name, object, "boolean"); | |
190 | } | |
191 | return result; | |
192 | } | |
193 | ||
194 | public boolean optBoolean(String name) { | |
195 | return optBoolean(name, false); | |
196 | } | |
197 | ||
198 | public boolean optBoolean(String name, boolean fallback) { | |
199 | Object object = opt(name); | |
200 | Boolean result = JSON.toBoolean(object); | |
201 | return result != null ? result : fallback; | |
202 | } | |
203 | ||
204 | public double getDouble(String name) throws JSONException { | |
205 | Object object = get(name); | |
206 | Double result = JSON.toDouble(object); | |
207 | if (result == null) { | |
208 | throw JSON.typeMismatch(name, object, "double"); | |
209 | } | |
210 | return result; | |
211 | } | |
212 | ||
213 | public double optDouble(String name) { | |
214 | return optDouble(name, Double.NaN); | |
215 | } | |
216 | ||
217 | public double optDouble(String name, double fallback) { | |
218 | Object object = opt(name); | |
219 | Double result = JSON.toDouble(object); | |
220 | return result != null ? result : fallback; | |
221 | } | |
222 | ||
223 | public int getInt(String name) throws JSONException { | |
224 | Object object = get(name); | |
225 | Integer result = JSON.toInteger(object); | |
226 | if (result == null) { | |
227 | throw JSON.typeMismatch(name, object, "int"); | |
228 | } | |
229 | return result; | |
230 | } | |
231 | ||
232 | public int optInt(String name) { | |
233 | return optInt(name, 0); | |
234 | } | |
235 | ||
236 | public int optInt(String name, int fallback) { | |
237 | Object object = opt(name); | |
238 | Integer result = JSON.toInteger(object); | |
239 | return result != null ? result : fallback; | |
240 | } | |
241 | ||
242 | public long getLong(String name) throws JSONException { | |
243 | Object object = get(name); | |
244 | Long result = JSON.toLong(object); | |
245 | if (result == null) { | |
246 | throw JSON.typeMismatch(name, object, "long"); | |
247 | } | |
248 | return result; | |
249 | } | |
250 | ||
251 | public long optLong(String name) { | |
252 | return optLong(name, 0L); | |
253 | } | |
254 | ||
255 | public long optLong(String name, long fallback) { | |
256 | Object object = opt(name); | |
257 | Long result = JSON.toLong(object); | |
258 | return result != null ? result : fallback; | |
259 | } | |
260 | ||
261 | public String getString(String name) throws JSONException { | |
262 | Object object = get(name); | |
263 | String result = JSON.toString(object); | |
264 | if (result == null) { | |
265 | throw JSON.typeMismatch(name, object, "String"); | |
266 | } | |
267 | return result; | |
268 | } | |
269 | ||
270 | public String optString(String name) { | |
271 | return optString(name, ""); | |
272 | } | |
273 | ||
274 | public String optString(String name, String fallback) { | |
275 | Object object = opt(name); | |
276 | String result = JSON.toString(object); | |
277 | return result != null ? result : fallback; | |
278 | } | |
279 | ||
280 | public JSONArray getJSONArray(String name) throws JSONException { | |
281 | Object object = get(name); | |
282 | if (object instanceof JSONArray) { | |
283 | return (JSONArray) object; | |
284 | } else { | |
285 | throw JSON.typeMismatch(name, object, "JSONArray"); | |
286 | } | |
287 | } | |
288 | ||
289 | public JSONArray optJSONArray(String name) { | |
290 | Object object = opt(name); | |
291 | return object instanceof JSONArray ? (JSONArray) object : null; | |
292 | } | |
293 | ||
294 | public JSONObject getJSONObject(String name) throws JSONException { | |
295 | Object object = get(name); | |
296 | if (object instanceof JSONObject) { | |
297 | return (JSONObject) object; | |
298 | } else { | |
299 | throw JSON.typeMismatch(name, object, "JSONObject"); | |
300 | } | |
301 | } | |
302 | ||
303 | public JSONObject optJSONObject(String name) { | |
304 | Object object = opt(name); | |
305 | return object instanceof JSONObject ? (JSONObject) object : null; | |
306 | } | |
307 | ||
308 | public JSONArray toJSONArray(JSONArray names) throws JSONException { | |
309 | JSONArray result = new JSONArray(); | |
310 | if (names == null) { | |
311 | return null; | |
312 | } | |
313 | int length = names.length(); | |
314 | if (length == 0) { | |
315 | return null; | |
316 | } | |
317 | for (int i = 0; i < length; i++) { | |
318 | String name = JSON.toString(names.opt(i)); | |
319 | result.put(opt(name)); | |
320 | } | |
321 | return result; | |
322 | } | |
323 | ||
324 | /* Return a raw type for API compatibility */ | |
325 | public Iterator keys() { | |
326 | return nameValuePairs.keySet().iterator(); | |
327 | } | |
328 | ||
329 | public JSONArray names() { | |
330 | return nameValuePairs.isEmpty() | |
331 | ? null | |
332 | : new JSONArray(new ArrayList<String>(nameValuePairs.keySet())); | |
333 | } | |
334 | ||
335 | @Override public String toString() { | |
336 | try { | |
337 | JSONStringer stringer = new JSONStringer(); | |
338 | writeTo(stringer); | |
339 | return stringer.toString(); | |
340 | } catch (JSONException e) { | |
341 | return null; | |
342 | } | |
343 | } | |
344 | ||
345 | public String toString(int indentSpaces) throws JSONException { | |
346 | JSONStringer stringer = new JSONStringer(indentSpaces); | |
347 | writeTo(stringer); | |
348 | return stringer.toString(); | |
349 | } | |
350 | ||
351 | void writeTo(JSONStringer stringer) throws JSONException { | |
352 | stringer.object(); | |
353 | for (Map.Entry<String, Object> entry : nameValuePairs.entrySet()) { | |
354 | stringer.key(entry.getKey()).value(entry.getValue()); | |
355 | } | |
356 | stringer.endObject(); | |
357 | } | |
358 | ||
359 | public static String numberToString(Number number) throws JSONException { | |
360 | if (number == null) { | |
361 | throw new JSONException("Number must be non-null"); | |
362 | } | |
363 | ||
364 | double doubleValue = number.doubleValue(); | |
365 | JSON.checkDouble(doubleValue); | |
366 | ||
367 | // the original returns "-0" instead of "-0.0" for negative zero | |
368 | if (number.equals(NEGATIVE_ZERO)) { | |
369 | return "-0"; | |
370 | } | |
371 | ||
372 | long longValue = number.longValue(); | |
373 | if (doubleValue == (double) longValue) { | |
374 | return Long.toString(longValue); | |
375 | } | |
376 | ||
377 | return number.toString(); | |
378 | } | |
379 | ||
380 | public static String quote(String data) { | |
381 | if (data == null) { | |
382 | return "\"\""; | |
383 | } | |
384 | try { | |
385 | JSONStringer stringer = new JSONStringer(); | |
386 | stringer.open(JSONStringer.Scope.NULL, ""); | |
387 | stringer.value(data); | |
388 | stringer.close(JSONStringer.Scope.NULL, JSONStringer.Scope.NULL, ""); | |
389 | return stringer.toString(); | |
390 | } catch (JSONException e) { | |
391 | throw new AssertionError(); | |
392 | } | |
393 | } | |
394 | } |
0 | /* | |
1 | * Copyright (C) 2010 The Android Open Source Project | |
2 | * | |
3 | * Licensed under the Apache License, Version 2.0 (the "License"); | |
4 | * you may not use this file except in compliance with the License. | |
5 | * You may obtain a copy of the License at | |
6 | * | |
7 | * http://www.apache.org/licenses/LICENSE-2.0 | |
8 | * | |
9 | * Unless required by applicable law or agreed to in writing, software | |
10 | * distributed under the License is distributed on an "AS IS" BASIS, | |
11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
12 | * See the License for the specific language governing permissions and | |
13 | * limitations under the License. | |
14 | */ | |
15 | ||
16 | package org.json; | |
17 | ||
18 | import java.util.ArrayList; | |
19 | import java.util.Arrays; | |
20 | import java.util.List; | |
21 | ||
22 | // Note: this class was written without inspecting the non-free org.json sourcecode. | |
23 | ||
24 | /** | |
25 | * | |
26 | */ | |
27 | public class JSONStringer { | |
28 | ||
29 | /** The output data, containing at most one top-level array or object. */ | |
30 | final StringBuilder out = new StringBuilder(); | |
31 | ||
32 | /** | |
33 | * Lexical scoping elements within this stringer, necessary to insert the | |
34 | * appropriate separator characters (ie. commas and colons) and to detect | |
35 | * nesting errors. | |
36 | */ | |
37 | enum Scope { | |
38 | ||
39 | /** | |
40 | * An array with no elements requires no separators or newlines before | |
41 | * it is closed. | |
42 | */ | |
43 | EMPTY_ARRAY, | |
44 | ||
45 | /** | |
46 | * A array with at least one value requires a comma and newline before | |
47 | * the next element. | |
48 | */ | |
49 | NONEMPTY_ARRAY, | |
50 | ||
51 | /** | |
52 | * An object with no keys or values requires no separators or newlines | |
53 | * before it is closed. | |
54 | */ | |
55 | EMPTY_OBJECT, | |
56 | ||
57 | /** | |
58 | * An object whose most recent element is a key. The next element must | |
59 | * be a value. | |
60 | */ | |
61 | DANGLING_KEY, | |
62 | ||
63 | /** | |
64 | * An object with at least one name/value pair requires a comma and | |
65 | * newline before the next element. | |
66 | */ | |
67 | NONEMPTY_OBJECT, | |
68 | ||
69 | /** | |
70 | * A special bracketless array needed by JSONStringer.join() and | |
71 | * JSONObject.quote() only. Not used for JSON encoding. | |
72 | */ | |
73 | NULL, | |
74 | } | |
75 | ||
76 | /** | |
77 | * Unlike the original implementation, this stack isn't limited to 20 | |
78 | * levels of nesting. | |
79 | */ | |
80 | private final List<Scope> stack = new ArrayList<Scope>(); | |
81 | ||
82 | /** | |
83 | * A string containing a full set of spaces for a single level of | |
84 | * indentation, or null for no pretty printing. | |
85 | */ | |
86 | private final String indent; | |
87 | ||
88 | public JSONStringer() { | |
89 | indent = null; | |
90 | } | |
91 | ||
92 | JSONStringer(int indentSpaces) { | |
93 | char[] indentChars = new char[indentSpaces]; | |
94 | Arrays.fill(indentChars, ' '); | |
95 | indent = new String(indentChars); | |
96 | } | |
97 | ||
98 | public JSONStringer array() throws JSONException { | |
99 | return open(Scope.EMPTY_ARRAY, "["); | |
100 | } | |
101 | ||
102 | public JSONStringer endArray() throws JSONException { | |
103 | return close(Scope.EMPTY_ARRAY, Scope.NONEMPTY_ARRAY, "]"); | |
104 | } | |
105 | ||
106 | public JSONStringer object() throws JSONException { | |
107 | return open(Scope.EMPTY_OBJECT, "{"); | |
108 | } | |
109 | ||
110 | public JSONStringer endObject() throws JSONException { | |
111 | return close(Scope.EMPTY_OBJECT, Scope.NONEMPTY_OBJECT, "}"); | |
112 | } | |
113 | ||
114 | /** | |
115 | * Enters a new scope by appending any necessary whitespace and the given | |
116 | * bracket. | |
117 | */ | |
118 | JSONStringer open(Scope empty, String openBracket) throws JSONException { | |
119 | if (stack.isEmpty() && out.length() > 0) { | |
120 | throw new JSONException("Nesting problem: multiple top-level roots"); | |
121 | } | |
122 | beforeValue(); | |
123 | stack.add(empty); | |
124 | out.append(openBracket); | |
125 | return this; | |
126 | } | |
127 | ||
128 | /** | |
129 | * Closes the current scope by appending any necessary whitespace and the | |
130 | * given bracket. | |
131 | */ | |
132 | JSONStringer close(Scope empty, Scope nonempty, String closeBracket) throws JSONException { | |
133 | Scope context = peek(); | |
134 | if (context != nonempty && context != empty) { | |
135 | throw new JSONException("Nesting problem"); | |
136 | } | |
137 | ||
138 | stack.remove(stack.size() - 1); | |
139 | if (context == nonempty) { | |
140 | newline(); | |
141 | } | |
142 | out.append(closeBracket); | |
143 | return this; | |
144 | } | |
145 | ||
146 | /** | |
147 | * Returns the value on the top of the stack. | |
148 | */ | |
149 | private Scope peek() throws JSONException { | |
150 | if (stack.isEmpty()) { | |
151 | throw new JSONException("Nesting problem"); | |
152 | } | |
153 | return stack.get(stack.size() - 1); | |
154 | } | |
155 | ||
156 | /** | |
157 | * Replace the value on the top of the stack with the given value. | |
158 | */ | |
159 | private void replaceTop(Scope topOfStack) { | |
160 | stack.set(stack.size() - 1, topOfStack); | |
161 | } | |
162 | ||
163 | public JSONStringer value(Object value) throws JSONException { | |
164 | if (stack.isEmpty()) { | |
165 | throw new JSONException("Nesting problem"); | |
166 | } | |
167 | ||
168 | if (value instanceof JSONArray) { | |
169 | ((JSONArray) value).writeTo(this); | |
170 | return this; | |
171 | ||
172 | } else if (value instanceof JSONObject) { | |
173 | ((JSONObject) value).writeTo(this); | |
174 | return this; | |
175 | } | |
176 | ||
177 | beforeValue(); | |
178 | ||
179 | if (value == null | |
180 | || value instanceof Boolean | |
181 | || value == JSONObject.NULL) { | |
182 | out.append(value); | |
183 | ||
184 | } else if (value instanceof Number) { | |
185 | out.append(JSONObject.numberToString((Number) value)); | |
186 | ||
187 | } else { | |
188 | string(value.toString()); | |
189 | } | |
190 | ||
191 | return this; | |
192 | } | |
193 | ||
194 | public JSONStringer value(boolean value) throws JSONException { | |
195 | if (stack.isEmpty()) { | |
196 | throw new JSONException("Nesting problem"); | |
197 | } | |
198 | beforeValue(); | |
199 | out.append(value); | |
200 | return this; | |
201 | } | |
202 | ||
203 | public JSONStringer value(double value) throws JSONException { | |
204 | if (stack.isEmpty()) { | |
205 | throw new JSONException("Nesting problem"); | |
206 | } | |
207 | beforeValue(); | |
208 | out.append(JSONObject.numberToString(value)); | |
209 | return this; | |
210 | } | |
211 | ||
212 | public JSONStringer value(long value) throws JSONException { | |
213 | if (stack.isEmpty()) { | |
214 | throw new JSONException("Nesting problem"); | |
215 | } | |
216 | beforeValue(); | |
217 | out.append(value); | |
218 | return this; | |
219 | } | |
220 | ||
221 | private void string(String value) { | |
222 | out.append("\""); | |
223 | for (int i = 0, length = value.length(); i < length; i++) { | |
224 | char c = value.charAt(i); | |
225 | ||
226 | /* | |
227 | * From RFC 4627, "All Unicode characters may be placed within the | |
228 | * quotation marks except for the characters that must be escaped: | |
229 | * quotation mark, reverse solidus, and the control characters | |
230 | * (U+0000 through U+001F)." | |
231 | */ | |
232 | switch (c) { | |
233 | case '"': | |
234 | case '\\': | |
235 | case '/': | |
236 | out.append('\\').append(c); | |
237 | break; | |
238 | ||
239 | case '\t': | |
240 | out.append("\\t"); | |
241 | break; | |
242 | ||
243 | case '\b': | |
244 | out.append("\\b"); | |
245 | break; | |
246 | ||
247 | case '\n': | |
248 | out.append("\\n"); | |
249 | break; | |
250 | ||
251 | case '\r': | |
252 | out.append("\\r"); | |
253 | break; | |
254 | ||
255 | case '\f': | |
256 | out.append("\\f"); | |
257 | break; | |
258 | ||
259 | default: | |
260 | if (c <= 0x1F) { | |
261 | out.append(String.format("\\u%04x", (int) c)); | |
262 | } else { | |
263 | out.append(c); | |
264 | } | |
265 | break; | |
266 | } | |
267 | ||
268 | } | |
269 | out.append("\""); | |
270 | } | |
271 | ||
272 | private void newline() { | |
273 | if (indent == null) { | |
274 | return; | |
275 | } | |
276 | ||
277 | out.append("\n"); | |
278 | for (int i = 0; i < stack.size(); i++) { | |
279 | out.append(indent); | |
280 | } | |
281 | } | |
282 | ||
283 | public JSONStringer key(String name) throws JSONException { | |
284 | if (name == null) { | |
285 | throw new JSONException("Names must be non-null"); | |
286 | } | |
287 | beforeKey(); | |
288 | string(name); | |
289 | return this; | |
290 | } | |
291 | ||
292 | /** | |
293 | * Inserts any necessary separators and whitespace before a name. Also | |
294 | * adjusts the stack to expect the key's value. | |
295 | */ | |
296 | private void beforeKey() throws JSONException { | |
297 | Scope context = peek(); | |
298 | if (context == Scope.NONEMPTY_OBJECT) { // first in object | |
299 | out.append(','); | |
300 | } else if (context != Scope.EMPTY_OBJECT) { // not in an object! | |
301 | throw new JSONException("Nesting problem"); | |
302 | } | |
303 | newline(); | |
304 | replaceTop(Scope.DANGLING_KEY); | |
305 | } | |
306 | ||
307 | /** | |
308 | * Inserts any necessary separators and whitespace before a literal value, | |
309 | * inline array, or inline object. Also adjusts the stack to expect either a | |
310 | * closing bracket or another element. | |
311 | */ | |
312 | private void beforeValue() throws JSONException { | |
313 | if (stack.isEmpty()) { | |
314 | return; | |
315 | } | |
316 | ||
317 | Scope context = peek(); | |
318 | if (context == Scope.EMPTY_ARRAY) { // first in array | |
319 | replaceTop(Scope.NONEMPTY_ARRAY); | |
320 | newline(); | |
321 | } else if (context == Scope.NONEMPTY_ARRAY) { // another in array | |
322 | out.append(','); | |
323 | newline(); | |
324 | } else if (context == Scope.DANGLING_KEY) { // value for key | |
325 | out.append(indent == null ? ":" : ": "); | |
326 | replaceTop(Scope.NONEMPTY_OBJECT); | |
327 | } else if (context != Scope.NULL) { | |
328 | throw new JSONException("Nesting problem"); | |
329 | } | |
330 | } | |
331 | ||
332 | /** | |
333 | * Although it contradicts the general contract of {@link Object#toString}, | |
334 | * this method returns null if the stringer contains no data. | |
335 | */ | |
336 | @Override public String toString() { | |
337 | return out.length() == 0 ? null : out.toString(); | |
338 | } | |
339 | } |
0 | /* | |
1 | * Copyright (C) 2010 The Android Open Source Project | |
2 | * | |
3 | * Licensed under the Apache License, Version 2.0 (the "License"); | |
4 | * you may not use this file except in compliance with the License. | |
5 | * You may obtain a copy of the License at | |
6 | * | |
7 | * http://www.apache.org/licenses/LICENSE-2.0 | |
8 | * | |
9 | * Unless required by applicable law or agreed to in writing, software | |
10 | * distributed under the License is distributed on an "AS IS" BASIS, | |
11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
12 | * See the License for the specific language governing permissions and | |
13 | * limitations under the License. | |
14 | */ | |
15 | ||
16 | package org.json; | |
17 | ||
18 | // Note: this class was written without inspecting the non-free org.json sourcecode. | |
19 | ||
20 | /** | |
21 | * | |
22 | */ | |
23 | public class JSONTokener { | |
24 | ||
25 | /** The input JSON. */ | |
26 | private final String in; | |
27 | ||
28 | /** | |
29 | * The index of the next character to be returned by {@link #next()}. When | |
30 | * the input is exhausted, this equals the input's length. | |
31 | */ | |
32 | private int pos; | |
33 | ||
34 | public JSONTokener(String in) { | |
35 | this.in = in; | |
36 | } | |
37 | ||
38 | public Object nextValue() throws JSONException { | |
39 | int c = nextCleanInternal(); | |
40 | switch (c) { | |
41 | case -1: | |
42 | throw syntaxError("End of input"); | |
43 | ||
44 | case '{': | |
45 | return readObject(); | |
46 | ||
47 | case '[': | |
48 | return readArray(); | |
49 | ||
50 | case '\'': | |
51 | case '"': | |
52 | return nextString((char) c); | |
53 | ||
54 | default: | |
55 | pos--; | |
56 | return readLiteral(); | |
57 | } | |
58 | } | |
59 | ||
60 | private int nextCleanInternal() throws JSONException { | |
61 | while (pos < in.length()) { | |
62 | int c = in.charAt(pos++); | |
63 | switch (c) { | |
64 | case '\t': | |
65 | case ' ': | |
66 | case '\n': | |
67 | case '\r': | |
68 | continue; | |
69 | ||
70 | case '/': | |
71 | if (pos == in.length()) { | |
72 | return c; | |
73 | } | |
74 | ||
75 | char peek = in.charAt(pos); | |
76 | if (peek != '*' && peek != '/') { | |
77 | return c; | |
78 | } | |
79 | ||
80 | skipComment(); | |
81 | continue; | |
82 | ||
83 | default: | |
84 | return c; | |
85 | } | |
86 | } | |
87 | ||
88 | return -1; | |
89 | } | |
90 | ||
91 | /** | |
92 | * Advances the position until it is beyond the current comment. The opening | |
93 | * slash '/' should have already been read, and character at the current | |
94 | * position be an asterisk '*' for a C-style comment or a slash '/' for an | |
95 | * end-of-line comment. | |
96 | * | |
97 | * @throws JSONException if a C-style comment was not terminated. | |
98 | */ | |
99 | private void skipComment() throws JSONException { | |
100 | if (in.charAt(pos++) == '*') { | |
101 | int commentEnd = in.indexOf("*/", pos); | |
102 | if (commentEnd == -1) { | |
103 | throw syntaxError("Unterminated comment"); | |
104 | } | |
105 | pos = commentEnd + 2; | |
106 | ||
107 | } else { | |
108 | /* | |
109 | * Skip to the next newline character. If the line is terminated by | |
110 | * "\r\n", the '\n' will be consumed as whitespace by the caller. | |
111 | */ | |
112 | for (; pos < in.length(); pos++) { | |
113 | char c = in.charAt(pos); | |
114 | if (c == '\r' || c == '\n') { | |
115 | pos++; | |
116 | break; | |
117 | } | |
118 | } | |
119 | } | |
120 | } | |
121 | ||
122 | /** | |
123 | * | |
124 | * | |
125 | * @throws NumberFormatException if any unicode escape sequences are | |
126 | * malformed. | |
127 | */ | |
128 | public String nextString(char quote) throws JSONException { | |
129 | /* | |
130 | * For strings that are free of escape sequences, we can just extract | |
131 | * the result as a substring of the input. But if we encounter an escape | |
132 | * sequence, we need to use a StringBuilder to compose the result. | |
133 | */ | |
134 | StringBuilder builder = null; | |
135 | ||
136 | /* the index of the first character not yet appended to the builder. */ | |
137 | int start = pos; | |
138 | ||
139 | while (pos < in.length()) { | |
140 | int c = in.charAt(pos++); | |
141 | if (c == quote) { | |
142 | if (builder == null) { | |
143 | // a new string avoids leaking memory | |
144 | return new String(in.substring(start, pos - 1)); | |
145 | } else { | |
146 | builder.append(in, start, pos - 1); | |
147 | return builder.toString(); | |
148 | } | |
149 | } | |
150 | ||
151 | if (c == '\\') { | |
152 | if (pos == in.length()) { | |
153 | throw syntaxError("Unterminated escape sequence"); | |
154 | } | |
155 | if (builder == null) { | |
156 | builder = new StringBuilder(); | |
157 | } | |
158 | builder.append(in, start, pos - 1); | |
159 | builder.append(readEscapeCharacter()); | |
160 | start = pos; | |
161 | } | |
162 | } | |
163 | ||
164 | throw syntaxError("Unterminated string"); | |
165 | } | |
166 | ||
167 | /** | |
168 | * Unescapes the character identified by the character or characters that | |
169 | * immediately follow a backslash. The backslash '\' should have already | |
170 | * been read. This supports both unicode escapes "u000A" and two-character | |
171 | * escapes "\n". | |
172 | * | |
173 | * @throws NumberFormatException if any unicode escape sequences are | |
174 | * malformed. | |
175 | */ | |
176 | private char readEscapeCharacter() throws JSONException { | |
177 | char escaped = in.charAt(pos++); | |
178 | switch (escaped) { | |
179 | case 'u': | |
180 | if (pos + 4 > in.length()) { | |
181 | throw syntaxError("Unterminated escape sequence"); | |
182 | } | |
183 | String hex = in.substring(pos, pos + 4); | |
184 | pos += 4; | |
185 | return (char) Integer.parseInt(hex, 16); | |
186 | ||
187 | case 't': | |
188 | return '\t'; | |
189 | ||
190 | case 'b': | |
191 | return '\b'; | |
192 | ||
193 | case 'n': | |
194 | return '\n'; | |
195 | ||
196 | case 'r': | |
197 | return '\r'; | |
198 | ||
199 | case 'f': | |
200 | return '\f'; | |
201 | ||
202 | case '\'': | |
203 | case '"': | |
204 | case '\\': | |
205 | default: | |
206 | return escaped; | |
207 | } | |
208 | } | |
209 | ||
210 | /** | |
211 | * Reads a null, boolean, numeric or unquoted string literal value. Numeric | |
212 | * values will be returned as an Integer, Long, or Double, in that order of | |
213 | * preference. | |
214 | */ | |
215 | private Object readLiteral() throws JSONException { | |
216 | String literal = nextToInternal("{}[]/\\:,=;# \t\f"); | |
217 | ||
218 | if (literal.length() == 0) { | |
219 | throw syntaxError("Expected literal value"); | |
220 | } else if ("null".equalsIgnoreCase(literal)) { | |
221 | return JSONObject.NULL; | |
222 | } else if ("true".equalsIgnoreCase(literal)) { | |
223 | return Boolean.TRUE; | |
224 | } else if ("false".equalsIgnoreCase(literal)) { | |
225 | return Boolean.FALSE; | |
226 | } | |
227 | ||
228 | /* try to parse as an integral type... */ | |
229 | if (literal.indexOf('.') == -1) { | |
230 | int base = 10; | |
231 | String number = literal; | |
232 | if (number.startsWith("0x") || number.startsWith("0X")) { | |
233 | number = number.substring(2); | |
234 | base = 16; | |
235 | } else if (number.startsWith("0") && number.length() > 1) { | |
236 | number = number.substring(1); | |
237 | base = 8; | |
238 | } | |
239 | try { | |
240 | long longValue = Long.parseLong(number, base); | |
241 | if (longValue <= Integer.MAX_VALUE && longValue >= Integer.MIN_VALUE) { | |
242 | return (int) longValue; | |
243 | } else { | |
244 | return longValue; | |
245 | } | |
246 | } catch (NumberFormatException e) { | |
247 | /* | |
248 | * This only happens for integral numbers greater than | |
249 | * Long.MAX_VALUE, numbers in exponential form (5e-10) and | |
250 | * unquoted strings. Fall through to try floating point. | |
251 | */ | |
252 | } | |
253 | } | |
254 | ||
255 | /* ...next try to parse as a floating point... */ | |
256 | try { | |
257 | return Double.valueOf(literal); | |
258 | } catch (NumberFormatException e) { | |
259 | } | |
260 | ||
261 | /* ... finally give up. We have an unquoted string */ | |
262 | return new String(literal); // a new string avoids leaking memory | |
263 | } | |
264 | ||
265 | /** | |
266 | * Returns text from the current position until the first of any of the | |
267 | * given characters or a newline character, excluding that character. The | |
268 | * position is advanced to the excluded character. | |
269 | */ | |
270 | private String nextToInternal(String excluded) { | |
271 | int start = pos; | |
272 | for (; pos < in.length(); pos++) { | |
273 | char c = in.charAt(pos); | |
274 | if (c == '\r' || c == '\n' || excluded.indexOf(c) != -1) { | |
275 | return in.substring(start, pos); | |
276 | } | |
277 | } | |
278 | return in.substring(start); | |
279 | } | |
280 | ||
281 | /** | |
282 | * Reads a sequence of key/value pairs and the trailing closing brace '}' of | |
283 | * an object. The opening brace '{' should have already been read. | |
284 | */ | |
285 | private JSONObject readObject() throws JSONException { | |
286 | JSONObject result = new JSONObject(); | |
287 | ||
288 | /* Peek to see if this is the empty object. */ | |
289 | int first = nextCleanInternal(); | |
290 | if (first == '}') { | |
291 | return result; | |
292 | } else if (first != -1) { | |
293 | pos--; | |
294 | } | |
295 | ||
296 | while (true) { | |
297 | Object name = nextValue(); | |
298 | if (!(name instanceof String)) { | |
299 | if (name == null) { | |
300 | throw syntaxError("Names cannot be null"); | |
301 | } else { | |
302 | throw syntaxError("Names must be strings, but " + name | |
303 | + " is of type " + name.getClass().getName()); | |
304 | } | |
305 | } | |
306 | ||
307 | /* | |
308 | * Expect the name/value separator to be either a colon ':', an | |
309 | * equals sign '=', or an arrow "=>". The last two are bogus but we | |
310 | * include them because that's what the original implementation did. | |
311 | */ | |
312 | int separator = nextCleanInternal(); | |
313 | if (separator != ':' && separator != '=') { | |
314 | throw syntaxError("Expected ':' after " + name); | |
315 | } | |
316 | if (pos < in.length() && in.charAt(pos) == '>') { | |
317 | pos++; | |
318 | } | |
319 | ||
320 | result.put((String) name, nextValue()); | |
321 | ||
322 | switch (nextCleanInternal()) { | |
323 | case '}': | |
324 | return result; | |
325 | case ';': | |
326 | case ',': | |
327 | continue; | |
328 | default: | |
329 | throw syntaxError("Unterminated object"); | |
330 | } | |
331 | } | |
332 | } | |
333 | ||
334 | /** | |
335 | * Reads a sequence of values and the trailing closing brace ']' of an | |
336 | * array. The opening brace '[' should have already been read. Note that | |
337 | * "[]" yields an empty array, but "[,]" returns a two-element array | |
338 | * equivalent to "[null,null]". | |
339 | */ | |
340 | private JSONArray readArray() throws JSONException { | |
341 | JSONArray result = new JSONArray(); | |
342 | ||
343 | /* to cover input that ends with ",]". */ | |
344 | boolean hasTrailingSeparator = false; | |
345 | ||
346 | while (true) { | |
347 | switch (nextCleanInternal()) { | |
348 | case -1: | |
349 | throw syntaxError("Unterminated array"); | |
350 | case ']': | |
351 | if (hasTrailingSeparator) { | |
352 | result.put(null); | |
353 | } | |
354 | return result; | |
355 | case ',': | |
356 | case ';': | |
357 | /* A separator without a value first means "null". */ | |
358 | result.put(null); | |
359 | hasTrailingSeparator = true; | |
360 | continue; | |
361 | default: | |
362 | pos--; | |
363 | } | |
364 | ||
365 | result.put(nextValue()); | |
366 | ||
367 | switch (nextCleanInternal()) { | |
368 | case ']': | |
369 | return result; | |
370 | case ',': | |
371 | case ';': | |
372 | hasTrailingSeparator = true; | |
373 | continue; | |
374 | default: | |
375 | throw syntaxError("Unterminated array"); | |
376 | } | |
377 | } | |
378 | } | |
379 | ||
380 | public JSONException syntaxError(String text) { | |
381 | return new JSONException(text + this); | |
382 | } | |
383 | ||
384 | @Override public String toString() { | |
385 | // consistent with the original implementation | |
386 | return " at character " + pos + " of " + in; | |
387 | } | |
388 | ||
389 | /* | |
390 | * Legacy APIs. | |
391 | * | |
392 | * None of the methods below are on the critical path of parsing JSON | |
393 | * documents. They exist only because they were exposed by the original | |
394 | * implementation and may be used by some clients. | |
395 | */ | |
396 | ||
397 | public boolean more() { | |
398 | return pos < in.length(); | |
399 | } | |
400 | ||
401 | public char next() { | |
402 | return pos < in.length() ? in.charAt(pos++) : '\0'; | |
403 | } | |
404 | ||
405 | public char next(char c) throws JSONException { | |
406 | char result = next(); | |
407 | if (result != c) { | |
408 | throw syntaxError("Expected " + c + " but was " + result); | |
409 | } | |
410 | return result; | |
411 | } | |
412 | ||
413 | public char nextClean() throws JSONException { | |
414 | int nextCleanInt = nextCleanInternal(); | |
415 | return nextCleanInt == -1 ? '\0' : (char) nextCleanInt; | |
416 | } | |
417 | ||
418 | /** | |
419 | * TODO: note about how this method returns a substring, and could cause a memory leak | |
420 | */ | |
421 | public String next(int length) throws JSONException { | |
422 | if (pos + length > in.length()) { | |
423 | throw syntaxError(length + " is out of bounds"); | |
424 | } | |
425 | String result = in.substring(pos, pos + length); | |
426 | pos += length; | |
427 | return result; | |
428 | } | |
429 | ||
430 | /** | |
431 | * TODO: note about how this method returns a substring, and could cause a memory leak | |
432 | */ | |
433 | public String nextTo(String excluded) { | |
434 | if (excluded == null) { | |
435 | throw new NullPointerException(); | |
436 | } | |
437 | return nextToInternal(excluded).trim(); | |
438 | } | |
439 | ||
440 | /** | |
441 | * TODO: note about how this method returns a substring, and could cause a memory leak | |
442 | */ | |
443 | public String nextTo(char excluded) { | |
444 | return nextToInternal(String.valueOf(excluded)).trim(); | |
445 | } | |
446 | ||
447 | public void skipPast(String thru) { | |
448 | int thruStart = in.indexOf(thru, pos); | |
449 | pos = thruStart == -1 ? in.length() : (thruStart + thru.length()); | |
450 | } | |
451 | ||
452 | public char skipTo(char to) { | |
453 | for (int i = pos, length = in.length(); i < length; i++) { | |
454 | if (in.charAt(i) == to) { | |
455 | pos = i; | |
456 | return to; | |
457 | } | |
458 | } | |
459 | return '\0'; | |
460 | } | |
461 | ||
462 | public void back() { | |
463 | if (--pos == -1) { | |
464 | pos = 0; | |
465 | } | |
466 | } | |
467 | ||
468 | public static int dehexchar(char hex) { | |
469 | if (hex >= '0' && hex <= '9') { | |
470 | return hex - '0'; | |
471 | } else if (hex >= 'A' && hex <= 'F') { | |
472 | return hex - 'A' + 10; | |
473 | } else if (hex >= 'a' && hex <= 'f') { | |
474 | return hex - 'a' + 10; | |
475 | } else { | |
476 | return -1; | |
477 | } | |
478 | } | |
479 | } |
0 | /* | |
1 | * Copyright (C) 2010 The Android Open Source Project | |
2 | * | |
3 | * Licensed under the Apache License, Version 2.0 (the "License"); | |
4 | * you may not use this file except in compliance with the License. | |
5 | * You may obtain a copy of the License at | |
6 | * | |
7 | * http://www.apache.org/licenses/LICENSE-2.0 | |
8 | * | |
9 | * Unless required by applicable law or agreed to in writing, software | |
10 | * distributed under the License is distributed on an "AS IS" BASIS, | |
11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
12 | * See the License for the specific language governing permissions and | |
13 | * limitations under the License. | |
14 | */ | |
15 | ||
16 | package org.json; | |
17 | ||
18 | class JSON { | |
19 | /** | |
20 | * Returns the input if it is a JSON-permissable value; throws otherwise. | |
21 | */ | |
22 | static double checkDouble(double d) throws JSONException { | |
23 | if (Double.isInfinite(d) || Double.isNaN(d)) { | |
24 | throw new JSONException("Forbidden numeric value: " + d); | |
25 | } | |
26 | return d; | |
27 | } | |
28 | ||
29 | static Boolean toBoolean(Object value) { | |
30 | if (value instanceof Boolean) { | |
31 | return (Boolean) value; | |
32 | } else if (value instanceof String) { | |
33 | return Boolean.valueOf(((String) value)); | |
34 | } else { | |
35 | return null; | |
36 | } | |
37 | } | |
38 | ||
39 | static Double toDouble(Object value) { | |
40 | if (value instanceof Double) { | |
41 | return (Double) value; | |
42 | } else if (value instanceof Number) { | |
43 | return ((Number) value).doubleValue(); | |
44 | } else if (value instanceof String) { | |
45 | try { | |
46 | return Double.valueOf((String) value); | |
47 | } catch (NumberFormatException e) { | |
48 | } | |
49 | } | |
50 | return null; | |
51 | } | |
52 | ||
53 | static Integer toInteger(Object value) { | |
54 | if (value instanceof Integer) { | |
55 | return (Integer) value; | |
56 | } else if (value instanceof Number) { | |
57 | return ((Number) value).intValue(); | |
58 | } else if (value instanceof String) { | |
59 | try { | |
60 | return Double.valueOf((String) value).intValue(); | |
61 | } catch (NumberFormatException e) { | |
62 | } | |
63 | } | |
64 | return null; | |
65 | } | |
66 | ||
67 | static Long toLong(Object value) { | |
68 | if (value instanceof Long) { | |
69 | return (Long) value; | |
70 | } else if (value instanceof Number) { | |
71 | return ((Number) value).longValue(); | |
72 | } else if (value instanceof String) { | |
73 | try { | |
74 | return Double.valueOf((String) value).longValue(); | |
75 | } catch (NumberFormatException e) { | |
76 | } | |
77 | } | |
78 | return null; | |
79 | } | |
80 | ||
81 | static String toString(Object value) { | |
82 | if (value instanceof String) { | |
83 | return (String) value; | |
84 | } else if (value != null) { | |
85 | return String.valueOf(value); | |
86 | } | |
87 | return null; | |
88 | } | |
89 | ||
90 | public static JSONException typeMismatch(Object indexOrName, Object actual, | |
91 | String requiredType) throws JSONException { | |
92 | if (actual == null) { | |
93 | throw new JSONException("Value at " + indexOrName + " is null."); | |
94 | } else { | |
95 | throw new JSONException("Value " + actual + " at " + indexOrName | |
96 | + " of type " + actual.getClass().getName() | |
97 | + " cannot be converted to " + requiredType); | |
98 | } | |
99 | } | |
100 | ||
101 | public static JSONException typeMismatch(Object actual, String requiredType) | |
102 | throws JSONException { | |
103 | if (actual == null) { | |
104 | throw new JSONException("Value is null."); | |
105 | } else { | |
106 | throw new JSONException("Value " + actual | |
107 | + " of type " + actual.getClass().getName() | |
108 | + " cannot be converted to " + requiredType); | |
109 | } | |
110 | } | |
111 | } |
0 | /* | |
1 | * Copyright (C) 2010 The Android Open Source Project | |
2 | * | |
3 | * Licensed under the Apache License, Version 2.0 (the "License"); | |
4 | * you may not use this file except in compliance with the License. | |
5 | * You may obtain a copy of the License at | |
6 | * | |
7 | * http://www.apache.org/licenses/LICENSE-2.0 | |
8 | * | |
9 | * Unless required by applicable law or agreed to in writing, software | |
10 | * distributed under the License is distributed on an "AS IS" BASIS, | |
11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
12 | * See the License for the specific language governing permissions and | |
13 | * limitations under the License. | |
14 | */ | |
15 | ||
16 | package org.json; | |
17 | ||
18 | import java.util.List; | |
19 | import java.util.ArrayList; | |
20 | import java.util.Collection; | |
21 | ||
22 | // Note: this class was written without inspecting the non-free org.json sourcecode. | |
23 | ||
24 | /** | |
25 | * An indexed sequence of JSON-safe values. | |
26 | */ | |
27 | public class JSONArray { | |
28 | ||
29 | private final List<Object> values; | |
30 | ||
31 | public JSONArray() { | |
32 | values = new ArrayList<Object>(); | |
33 | } | |
34 | ||
35 | /* Accept a raw type for API compatibility */ | |
36 | public JSONArray(Collection copyFrom) { | |
37 | this(); | |
38 | Collection<?> copyFromTyped = (Collection<?>) copyFrom; | |
39 | values.addAll(copyFromTyped); | |
40 | } | |
41 | ||
42 | public JSONArray(JSONTokener readFrom) throws JSONException { | |
43 | /* | |
44 | * Getting the parser to populate this could get tricky. Instead, just | |
45 | * parse to temporary JSONArray and then steal the data from that. | |
46 | */ | |
47 | Object object = readFrom.nextValue(); | |
48 | if (object instanceof JSONArray) { | |
49 | values = ((JSONArray) object).values; | |
50 | } else { | |
51 | throw JSON.typeMismatch(object, "JSONArray"); | |
52 | } | |
53 | } | |
54 | ||
55 | public JSONArray(String json) throws JSONException { | |
56 | this(new JSONTokener(json)); | |
57 | } | |
58 | ||
59 | public int length() { | |
60 | return values.size(); | |
61 | } | |
62 | ||
63 | public JSONArray put(boolean value) { | |
64 | values.add(value); | |
65 | return this; | |
66 | } | |
67 | ||
68 | public JSONArray put(double value) throws JSONException { | |
69 | values.add(JSON.checkDouble(value)); | |
70 | return this; | |
71 | } | |
72 | ||
73 | public JSONArray put(int value) { | |
74 | values.add(value); | |
75 | return this; | |
76 | } | |
77 | ||
78 | public JSONArray put(long value) { | |
79 | values.add(value); | |
80 | return this; | |
81 | } | |
82 | ||
83 | public JSONArray put(Object value) { | |
84 | values.add(value); | |
85 | return this; | |
86 | } | |
87 | ||
88 | public JSONArray put(int index, boolean value) throws JSONException { | |
89 | return put(index, (Boolean) value); | |
90 | } | |
91 | ||
92 | public JSONArray put(int index, double value) throws JSONException { | |
93 | return put(index, (Double) value); | |
94 | } | |
95 | ||
96 | public JSONArray put(int index, int value) throws JSONException { | |
97 | return put(index, (Integer) value); | |
98 | } | |
99 | ||
100 | public JSONArray put(int index, long value) throws JSONException { | |
101 | return put(index, (Long) value); | |
102 | } | |
103 | ||
104 | public JSONArray put(int index, Object value) throws JSONException { | |
105 | if (value instanceof Number) { | |
106 | // deviate from the original by checking all Numbers, not just floats & doubles | |
107 | JSON.checkDouble(((Number) value).doubleValue()); | |
108 | } | |
109 | while (values.size() <= index) { | |
110 | values.add(null); | |
111 | } | |
112 | values.set(index, value); | |
113 | return this; | |
114 | } | |
115 | ||
116 | public boolean isNull(int index) { | |
117 | Object value = opt(index); | |
118 | return value == null || value == JSONObject.NULL; | |
119 | } | |
120 | ||
121 | public Object get(int index) throws JSONException { | |
122 | try { | |
123 | Object value = values.get(index); | |
124 | if (value == null) { | |
125 | throw new JSONException("Value at " + index + " is null."); | |
126 | } | |
127 | return value; | |
128 | } catch (IndexOutOfBoundsException e) { | |
129 | throw new JSONException("Index " + index + " out of range [0.." + values.size() + ")"); | |
130 | } | |
131 | } | |
132 | ||
133 | public Object opt(int index) { | |
134 | if (index < 0 || index >= values.size()) { | |
135 | return null; | |
136 | } | |
137 | return values.get(index); | |
138 | } | |
139 | ||
140 | public boolean getBoolean(int index) throws JSONException { | |
141 | Object object = get(index); | |
142 | Boolean result = JSON.toBoolean(object); | |
143 | if (result == null) { | |
144 | throw JSON.typeMismatch(index, object, "boolean"); | |
145 | } | |
146 | return result; | |
147 | } | |
148 | ||
149 | public boolean optBoolean(int index) { | |
150 | return optBoolean(index, false); | |
151 | } | |
152 | ||
153 | public boolean optBoolean(int index, boolean fallback) { | |
154 | Object object = opt(index); | |
155 | Boolean result = JSON.toBoolean(object); | |
156 | return result != null ? result : fallback; | |
157 | } | |
158 | ||
159 | public double getDouble(int index) throws JSONException { | |
160 | Object object = get(index); | |
161 | Double result = JSON.toDouble(object); | |
162 | if (result == null) { | |
163 | throw JSON.typeMismatch(index, object, "double"); | |
164 | } | |
165 | return result; | |
166 | } | |
167 | ||
168 | public double optDouble(int index) { | |
169 | return optDouble(index, Double.NaN); | |
170 | } | |
171 | ||
172 | public double optDouble(int index, double fallback) { | |
173 | Object object = opt(index); | |
174 | Double result = JSON.toDouble(object); | |
175 | return result != null ? result : fallback; | |
176 | } | |
177 | ||
178 | public int getInt(int index) throws JSONException { | |
179 | Object object = get(index); | |
180 | Integer result = JSON.toInteger(object); | |
181 | if (result == null) { | |
182 | throw JSON.typeMismatch(index, object, "int"); | |
183 | } | |
184 | return result; | |
185 | } | |
186 | ||
187 | public int optInt(int index) { | |
188 | return optInt(index, 0); | |
189 | } | |
190 | ||
191 | public int optInt(int index, int fallback) { | |
192 | Object object = opt(index); | |
193 | Integer result = JSON.toInteger(object); | |
194 | return result != null ? result : fallback; | |
195 | } | |
196 | ||
197 | public long getLong(int index) throws JSONException { | |
198 | Object object = get(index); | |
199 | Long result = JSON.toLong(object); | |
200 | if (result == null) { | |
201 | throw JSON.typeMismatch(index, object, "long"); | |
202 | } | |
203 | return result; | |
204 | } | |
205 | ||
206 | public long optLong(int index) { | |
207 | return optLong(index, 0L); | |
208 | } | |
209 | ||
210 | public long optLong(int index, long fallback) { | |
211 | Object object = opt(index); | |
212 | Long result = JSON.toLong(object); | |
213 | return result != null ? result : fallback; | |
214 | } | |
215 | ||
216 | public String getString(int index) throws JSONException { | |
217 | Object object = get(index); | |
218 | String result = JSON.toString(object); | |
219 | if (result == null) { | |
220 | throw JSON.typeMismatch(index, object, "String"); | |
221 | } | |
222 | return result; | |
223 | } | |
224 | ||
225 | public String optString(int index) { | |
226 | return optString(index, ""); | |
227 | } | |
228 | ||
229 | public String optString(int index, String fallback) { | |
230 | Object object = opt(index); | |
231 | String result = JSON.toString(object); | |
232 | return result != null ? result : fallback; | |
233 | } | |
234 | ||
235 | public JSONArray getJSONArray(int index) throws JSONException { | |
236 | Object object = get(index); | |
237 | if (object instanceof JSONArray) { | |
238 | return (JSONArray) object; | |
239 | } else { | |
240 | throw JSON.typeMismatch(index, object, "JSONArray"); | |
241 | } | |
242 | } | |
243 | ||
244 | public JSONArray optJSONArray(int index) { | |
245 | Object object = opt(index); | |
246 | return object instanceof JSONArray ? (JSONArray) object : null; | |
247 | } | |
248 | ||
249 | public JSONObject getJSONObject(int index) throws JSONException { | |
250 | Object object = get(index); | |
251 | if (object instanceof JSONObject) { | |
252 | return (JSONObject) object; | |
253 | } else { | |
254 | throw JSON.typeMismatch(index, object, "JSONObject"); | |
255 | } | |
256 | } | |
257 | ||
258 | public JSONObject optJSONObject(int index) { | |
259 | Object object = opt(index); | |
260 | return object instanceof JSONObject ? (JSONObject) object : null; | |
261 | } | |
262 | ||
263 | public JSONObject toJSONObject(JSONArray names) throws JSONException { | |
264 | JSONObject result = new JSONObject(); | |
265 | int length = Math.min(names.length(), values.size()); | |
266 | if (length == 0) { | |
267 | return null; | |
268 | } | |
269 | for (int i = 0; i < length; i++) { | |
270 | String name = JSON.toString(names.opt(i)); | |
271 | result.put(name, opt(i)); | |
272 | } | |
273 | return result; | |
274 | } | |
275 | ||
276 | public String join(String separator) throws JSONException { | |
277 | JSONStringer stringer = new JSONStringer(); | |
278 | stringer.open(JSONStringer.Scope.NULL, ""); | |
279 | for (int i = 0, size = values.size(); i < size; i++) { | |
280 | if (i > 0) { | |
281 | stringer.out.append(separator); | |
282 | } | |
283 | stringer.value(values.get(i)); | |
284 | } | |
285 | stringer.close(JSONStringer.Scope.NULL, JSONStringer.Scope.NULL, ""); | |
286 | return stringer.out.toString(); | |
287 | } | |
288 | ||
289 | @Override public String toString() { | |
290 | try { | |
291 | JSONStringer stringer = new JSONStringer(); | |
292 | writeTo(stringer); | |
293 | return stringer.toString(); | |
294 | } catch (JSONException e) { | |
295 | return null; | |
296 | } | |
297 | } | |
298 | ||
299 | public String toString(int indentSpaces) throws JSONException { | |
300 | JSONStringer stringer = new JSONStringer(indentSpaces); | |
301 | writeTo(stringer); | |
302 | return stringer.toString(); | |
303 | } | |
304 | ||
305 | void writeTo(JSONStringer stringer) throws JSONException { | |
306 | stringer.array(); | |
307 | for (Object value : values) { | |
308 | stringer.value(value); | |
309 | } | |
310 | stringer.endArray(); | |
311 | } | |
312 | ||
313 | @Override public boolean equals(Object o) { | |
314 | return o instanceof JSONArray && ((JSONArray) o).values.equals(values); | |
315 | } | |
316 | ||
317 | @Override public int hashCode() { | |
318 | // diverge from the original, which doesn't implement hashCode | |
319 | return values.hashCode(); | |
320 | } | |
321 | } |
0 | /* | |
1 | * Copyright (C) 2010 The Android Open Source Project | |
2 | * | |
3 | * Licensed under the Apache License, Version 2.0 (the "License"); | |
4 | * you may not use this file except in compliance with the License. | |
5 | * You may obtain a copy of the License at | |
6 | * | |
7 | * http://www.apache.org/licenses/LICENSE-2.0 | |
8 | * | |
9 | * Unless required by applicable law or agreed to in writing, software | |
10 | * distributed under the License is distributed on an "AS IS" BASIS, | |
11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
12 | * See the License for the specific language governing permissions and | |
13 | * limitations under the License. | |
14 | */ | |
15 | ||
16 | package org.json; | |
17 | ||
18 | // Note: this class was written without inspecting the non-free org.json sourcecode. | |
19 | ||
20 | /** | |
21 | * Thrown to indicate a problem with the JSON API. Such problems include: | |
22 | * <ul> | |
23 | * <li>Attempts to parse or construct malformed documents | |
24 | * <li>Use of null as a name | |
25 | * <li>Use of numeric types not available to JSON, such as {@link Double#NaN | |
26 | * NaN} or {@link Double#POSITIVE_INFINITY infinity}. | |
27 | * <li>Lookups using an out of range index or nonexistant name | |
28 | * <li>Type mismatches on lookups | |
29 | * </ul> | |
30 | * | |
31 | * <p>Although this is a checked exception, it is rarely recoverable. Most | |
32 | * callers should simply wrap this exception in an unchecked exception and | |
33 | * rethrow: | |
34 | * <pre> public JSONArray toJSONObject() { | |
35 | * try { | |
36 | * JSONObject result = new JSONObject(); | |
37 | * ... | |
38 | * } catch (JSONException e) { | |
39 | * throw new RuntimeException(e); | |
40 | * } | |
41 | * }</pre> | |
42 | */ | |
43 | public class JSONException extends Exception { | |
44 | ||
45 | public JSONException(String s) { | |
46 | super(s); | |
47 | } | |
48 | } |
0 | /* | |
1 | * Copyright (C) 2010 The Android Open Source Project | |
2 | * | |
3 | * Licensed under the Apache License, Version 2.0 (the "License"); | |
4 | * you may not use this file except in compliance with the License. | |
5 | * You may obtain a copy of the License at | |
6 | * | |
7 | * http://www.apache.org/licenses/LICENSE-2.0 | |
8 | * | |
9 | * Unless required by applicable law or agreed to in writing, software | |
10 | * distributed under the License is distributed on an "AS IS" BASIS, | |
11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
12 | * See the License for the specific language governing permissions and | |
13 | * limitations under the License. | |
14 | */ | |
15 | ||
16 | package org.json; | |
17 | ||
18 | import java.util.ArrayList; | |
19 | import java.util.HashMap; | |
20 | import java.util.Iterator; | |
21 | import java.util.Map; | |
22 | ||
23 | // Note: this class was written without inspecting the non-free org.json sourcecode. | |
24 | ||
25 | /** | |
26 | * | |
27 | * | |
28 | * <p>TODO: Note about self-use | |
29 | */ | |
30 | public class JSONObject { | |
31 | ||
32 | private static final Double NEGATIVE_ZERO = -0d; | |
33 | ||
34 | public static final Object NULL = new Object() { | |
35 | @Override public boolean equals(Object o) { | |
36 | return o == this || o == null; // API specifies this broken equals implementation | |
37 | } | |
38 | @Override public String toString() { | |
39 | return "null"; | |
40 | } | |
41 | }; | |
42 | ||
43 | private final Map<String, Object> nameValuePairs; | |
44 | ||
45 | public JSONObject() { | |
46 | nameValuePairs = new HashMap<String, Object>(); | |
47 | } | |
48 | ||
49 | /* Accept a raw type for API compatibility */ | |
50 | public JSONObject(Map copyFrom) { | |
51 | this(); | |
52 | Map<?, ?> contentsTyped = (Map<?, ?>) copyFrom; | |
53 | for (Map.Entry<?, ?> entry : contentsTyped.entrySet()) { | |
54 | /* | |
55 | * Deviate from the original by checking that keys are non-null and | |
56 | * of the proper type. (We still defer validating the values). | |
57 | */ | |
58 | String key = (String) entry.getKey(); | |
59 | if (key == null) { | |
60 | throw new NullPointerException(); | |
61 | } | |
62 | nameValuePairs.put(key, entry.getValue()); | |
63 | } | |
64 | } | |
65 | ||
66 | public JSONObject(JSONTokener readFrom) throws JSONException { | |
67 | /* | |
68 | * Getting the parser to populate this could get tricky. Instead, just | |
69 | * parse to temporary JSONObject and then steal the data from that. | |
70 | */ | |
71 | Object object = readFrom.nextValue(); | |
72 | if (object instanceof JSONObject) { | |
73 | this.nameValuePairs = ((JSONObject) object).nameValuePairs; | |
74 | } else { | |
75 | throw JSON.typeMismatch(object, "JSONObject"); | |
76 | } | |
77 | } | |
78 | ||
79 | public JSONObject(String json) throws JSONException { | |
80 | this(new JSONTokener(json)); | |
81 | } | |
82 | ||
83 | public JSONObject(JSONObject copyFrom, String[] names) throws JSONException { | |
84 | this(); | |
85 | for (String name : names) { | |
86 | Object value = copyFrom.opt(name); | |
87 | if (value != null) { | |
88 | nameValuePairs.put(name, value); | |
89 | } | |
90 | } | |
91 | } | |
92 | ||
93 | public int length() { | |
94 | return nameValuePairs.size(); | |
95 | } | |
96 | ||
97 | public JSONObject put(String name, boolean value) throws JSONException { | |
98 | nameValuePairs.put(checkName(name), value); | |
99 | return this; | |
100 | } | |
101 | ||
102 | public JSONObject put(String name, double value) throws JSONException { | |
103 | nameValuePairs.put(checkName(name), JSON.checkDouble(value)); | |
104 | return this; | |
105 | } | |
106 | ||
107 | public JSONObject put(String name, int value) throws JSONException { | |
108 | nameValuePairs.put(checkName(name), value); | |
109 | return this; | |
110 | } | |
111 | ||
112 | public JSONObject put(String name, long value) throws JSONException { | |
113 | nameValuePairs.put(checkName(name), value); | |
114 | return this; | |
115 | } | |
116 | ||
117 | public JSONObject put(String name, Object value) throws JSONException { | |
118 | if (value == null) { | |
119 | nameValuePairs.remove(name); | |
120 | return this; | |
121 | } | |
122 | if (value instanceof Number) { | |
123 | // deviate from the original by checking all Numbers, not just floats & doubles | |
124 | JSON.checkDouble(((Number) value).doubleValue()); | |
125 | } | |
126 | nameValuePairs.put(checkName(name), value); | |
127 | return this; | |
128 | } | |
129 | ||
130 | public JSONObject putOpt(String name, Object value) throws JSONException { | |
131 | if (name == null || value == null) { | |
132 | return this; | |
133 | } | |
134 | return put(name, value); | |
135 | } | |
136 | ||
137 | public JSONObject accumulate(String name, Object value) throws JSONException { | |
138 | Object current = nameValuePairs.get(checkName(name)); | |
139 | if (current == null) { | |
140 | put(name, value); | |
141 | } else if (current instanceof JSONArray) { | |
142 | JSONArray array = (JSONArray) current; | |
143 | array.put(value); | |
144 | } else { | |
145 | JSONArray array = new JSONArray(); | |
146 | array.put(current); | |
147 | array.put(value); // fails on bogus values | |
148 | nameValuePairs.put(name, array); | |
149 | } | |
150 | return this; | |
151 | } | |
152 | ||
153 | String checkName(String name) throws JSONException { | |
154 | if (name == null) { | |
155 | throw new JSONException("Names must be non-null"); | |
156 | } | |
157 | return name; | |
158 | } | |
159 | ||
160 | public Object remove(String name) { | |
161 | return nameValuePairs.remove(name); | |
162 | } | |
163 | ||
164 | public boolean isNull(String name) { | |
165 | Object value = nameValuePairs.get(name); | |
166 | return value == null || value == NULL; | |
167 | } | |
168 | ||
169 | public boolean has(String name) { | |
170 | return nameValuePairs.containsKey(name); | |
171 | } | |
172 | ||
173 | public Object get(String name) throws JSONException { | |
174 | Object result = nameValuePairs.get(name); | |
175 | if (result == null) { | |
176 | throw new JSONException("No value for " + name); | |
177 | } | |
178 | return result; | |
179 | } | |
180 | ||
181 | public Object opt(String name) { | |
182 | return nameValuePairs.get(name); | |
183 | } | |
184 | ||
185 | public boolean getBoolean(String name) throws JSONException { | |
186 | Object object = get(name); | |
187 | Boolean result = JSON.toBoolean(object); | |
188 | if (result == null) { | |
189 | throw JSON.typeMismatch(name, object, "boolean"); | |
190 | } | |
191 | return result; | |
192 | } | |
193 | ||
194 | public boolean optBoolean(String name) { | |
195 | return optBoolean(name, false); | |
196 | } | |
197 | ||
198 | public boolean optBoolean(String name, boolean fallback) { | |
199 | Object object = opt(name); | |
200 | Boolean result = JSON.toBoolean(object); | |
201 | return result != null ? result : fallback; | |
202 | } | |
203 | ||
204 | public double getDouble(String name) throws JSONException { | |
205 | Object object = get(name); | |
206 | Double result = JSON.toDouble(object); | |
207 | if (result == null) { | |
208 | throw JSON.typeMismatch(name, object, "double"); | |
209 | } | |
210 | return result; | |
211 | } | |
212 | ||
213 | public double optDouble(String name) { | |
214 | return optDouble(name, Double.NaN); | |
215 | } | |
216 | ||
217 | public double optDouble(String name, double fallback) { | |
218 | Object object = opt(name); | |
219 | Double result = JSON.toDouble(object); | |
220 | return result != null ? result : fallback; | |
221 | } | |
222 | ||
223 | public int getInt(String name) throws JSONException { | |
224 | Object object = get(name); | |
225 | Integer result = JSON.toInteger(object); | |
226 | if (result == null) { | |
227 | throw JSON.typeMismatch(name, object, "int"); | |
228 | } | |
229 | return result; | |
230 | } | |
231 | ||
232 | public int optInt(String name) { | |
233 | return optInt(name, 0); | |
234 | } | |
235 | ||
236 | public int optInt(String name, int fallback) { | |
237 | Object object = opt(name); | |
238 | Integer result = JSON.toInteger(object); | |
239 | return result != null ? result : fallback; | |
240 | } | |
241 | ||
242 | public long getLong(String name) throws JSONException { | |
243 | Object object = get(name); | |
244 | Long result = JSON.toLong(object); | |
245 | if (result == null) { | |
246 | throw JSON.typeMismatch(name, object, "long"); | |
247 | } | |
248 | return result; | |
249 | } | |
250 | ||
251 | public long optLong(String name) { | |
252 | return optLong(name, 0L); | |
253 | } | |
254 | ||
255 | public long optLong(String name, long fallback) { | |
256 | Object object = opt(name); | |
257 | Long result = JSON.toLong(object); | |
258 | return result != null ? result : fallback; | |
259 | } | |
260 | ||
261 | public String getString(String name) throws JSONException { | |
262 | Object object = get(name); | |
263 | String result = JSON.toString(object); | |
264 | if (result == null) { | |
265 | throw JSON.typeMismatch(name, object, "String"); | |
266 | } | |
267 | return result; | |
268 | } | |
269 | ||
270 | public String optString(String name) { | |
271 | return optString(name, ""); | |
272 | } | |
273 | ||
274 | public String optString(String name, String fallback) { | |
275 | Object object = opt(name); | |
276 | String result = JSON.toString(object); | |
277 | return result != null ? result : fallback; | |
278 | } | |
279 | ||
280 | public JSONArray getJSONArray(String name) throws JSONException { | |
281 | Object object = get(name); | |
282 | if (object instanceof JSONArray) { | |
283 | return (JSONArray) object; | |
284 | } else { | |
285 | throw JSON.typeMismatch(name, object, "JSONArray"); | |
286 | } | |
287 | } | |
288 | ||
289 | public JSONArray optJSONArray(String name) { | |
290 | Object object = opt(name); | |
291 | return object instanceof JSONArray ? (JSONArray) object : null; | |
292 | } | |
293 | ||
294 | public JSONObject getJSONObject(String name) throws JSONException { | |
295 | Object object = get(name); | |
296 | if (object instanceof JSONObject) { | |
297 | return (JSONObject) object; | |
298 | } else { | |
299 | throw JSON.typeMismatch(name, object, "JSONObject"); | |
300 | } | |
301 | } | |
302 | ||
303 | public JSONObject optJSONObject(String name) { | |
304 | Object object = opt(name); | |
305 | return object instanceof JSONObject ? (JSONObject) object : null; | |
306 | } | |
307 | ||
308 | public JSONArray toJSONArray(JSONArray names) throws JSONException { | |
309 | JSONArray result = new JSONArray(); | |
310 | if (names == null) { | |
311 | return null; | |
312 | } | |
313 | int length = names.length(); | |
314 | if (length == 0) { | |
315 | return null; | |
316 | } | |
317 | for (int i = 0; i < length; i++) { | |
318 | String name = JSON.toString(names.opt(i)); | |
319 | result.put(opt(name)); | |
320 | } | |
321 | return result; | |
322 | } | |
323 | ||
324 | /* Return a raw type for API compatibility */ | |
325 | public Iterator keys() { | |
326 | return nameValuePairs.keySet().iterator(); | |
327 | } | |
328 | ||
329 | public JSONArray names() { | |
330 | return nameValuePairs.isEmpty() | |
331 | ? null | |
332 | : new JSONArray(new ArrayList<String>(nameValuePairs.keySet())); | |
333 | } | |
334 | ||
335 | @Override public String toString() { | |
336 | try { | |
337 | JSONStringer stringer = new JSONStringer(); | |
338 | writeTo(stringer); | |
339 | return stringer.toString(); | |
340 | } catch (JSONException e) { | |
341 | return null; | |
342 | } | |
343 | } | |
344 | ||
345 | public String toString(int indentSpaces) throws JSONException { | |
346 | JSONStringer stringer = new JSONStringer(indentSpaces); | |
347 | writeTo(stringer); | |
348 | return stringer.toString(); | |
349 | } | |
350 | ||
351 | void writeTo(JSONStringer stringer) throws JSONException { | |
352 | stringer.object(); | |
353 | for (Map.Entry<String, Object> entry : nameValuePairs.entrySet()) { | |
354 | stringer.key(entry.getKey()).value(entry.getValue()); | |
355 | } | |
356 | stringer.endObject(); | |
357 | } | |
358 | ||
359 | public static String numberToString(Number number) throws JSONException { | |
360 | if (number == null) { | |
361 | throw new JSONException("Number must be non-null"); | |
362 | } | |
363 | ||
364 | double doubleValue = number.doubleValue(); | |
365 | JSON.checkDouble(doubleValue); | |
366 | ||
367 | // the original returns "-0" instead of "-0.0" for negative zero | |
368 | if (number.equals(NEGATIVE_ZERO)) { | |
369 | return "-0"; | |
370 | } | |
371 | ||
372 | long longValue = number.longValue(); | |
373 | if (doubleValue == (double) longValue) { | |
374 | return Long.toString(longValue); | |
375 | } | |
376 | ||
377 | return number.toString(); | |
378 | } | |
379 | ||
380 | public static String quote(String data) { | |
381 | if (data == null) { | |
382 | return "\"\""; | |
383 | } | |
384 | try { | |
385 | JSONStringer stringer = new JSONStringer(); | |
386 | stringer.open(JSONStringer.Scope.NULL, ""); | |
387 | stringer.value(data); | |
388 | stringer.close(JSONStringer.Scope.NULL, JSONStringer.Scope.NULL, ""); | |
389 | return stringer.toString(); | |
390 | } catch (JSONException e) { | |
391 | throw new AssertionError(); | |
392 | } | |
393 | } | |
394 | } |
0 | /* | |
1 | * Copyright (C) 2010 The Android Open Source Project | |
2 | * | |
3 | * Licensed under the Apache License, Version 2.0 (the "License"); | |
4 | * you may not use this file except in compliance with the License. | |
5 | * You may obtain a copy of the License at | |
6 | * | |
7 | * http://www.apache.org/licenses/LICENSE-2.0 | |
8 | * | |
9 | * Unless required by applicable law or agreed to in writing, software | |
10 | * distributed under the License is distributed on an "AS IS" BASIS, | |
11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
12 | * See the License for the specific language governing permissions and | |
13 | * limitations under the License. | |
14 | */ | |
15 | ||
16 | package org.json; | |
17 | ||
18 | import java.util.ArrayList; | |
19 | import java.util.Arrays; | |
20 | import java.util.List; | |
21 | ||
22 | // Note: this class was written without inspecting the non-free org.json sourcecode. | |
23 | ||
24 | /** | |
25 | * | |
26 | */ | |
27 | public class JSONStringer { | |
28 | ||
29 | /** The output data, containing at most one top-level array or object. */ | |
30 | final StringBuilder out = new StringBuilder(); | |
31 | ||
32 | /** | |
33 | * Lexical scoping elements within this stringer, necessary to insert the | |
34 | * appropriate separator characters (ie. commas and colons) and to detect | |
35 | * nesting errors. | |
36 | */ | |
37 | enum Scope { | |
38 | ||
39 | /** | |
40 | * An array with no elements requires no separators or newlines before | |
41 | * it is closed. | |
42 | */ | |
43 | EMPTY_ARRAY, | |
44 | ||
45 | /** | |
46 | * A array with at least one value requires a comma and newline before | |
47 | * the next element. | |
48 | */ | |
49 | NONEMPTY_ARRAY, | |
50 | ||
51 | /** | |
52 | * An object with no keys or values requires no separators or newlines | |
53 | * before it is closed. | |
54 | */ | |
55 | EMPTY_OBJECT, | |
56 | ||
57 | /** | |
58 | * An object whose most recent element is a key. The next element must | |
59 | * be a value. | |
60 | */ | |
61 | DANGLING_KEY, | |
62 | ||
63 | /** | |
64 | * An object with at least one name/value pair requires a comma and | |
65 | * newline before the next element. | |
66 | */ | |
67 | NONEMPTY_OBJECT, | |
68 | ||
69 | /** | |
70 | * A special bracketless array needed by JSONStringer.join() and | |
71 | * JSONObject.quote() only. Not used for JSON encoding. | |
72 | */ | |
73 | NULL, | |
74 | } | |
75 | ||
76 | /** | |
77 | * Unlike the original implementation, this stack isn't limited to 20 | |
78 | * levels of nesting. | |
79 | */ | |
80 | private final List<Scope> stack = new ArrayList<Scope>(); | |
81 | ||
82 | /** | |
83 | * A string containing a full set of spaces for a single level of | |
84 | * indentation, or null for no pretty printing. | |
85 | */ | |
86 | private final String indent; | |
87 | ||
88 | public JSONStringer() { | |
89 | indent = null; | |
90 | } | |
91 | ||
92 | JSONStringer(int indentSpaces) { | |
93 | char[] indentChars = new char[indentSpaces]; | |
94 | Arrays.fill(indentChars, ' '); | |
95 | indent = new String(indentChars); | |
96 | } | |
97 | ||
98 | public JSONStringer array() throws JSONException { | |
99 | return open(Scope.EMPTY_ARRAY, "["); | |
100 | } | |
101 | ||
102 | public JSONStringer endArray() throws JSONException { | |
103 | return close(Scope.EMPTY_ARRAY, Scope.NONEMPTY_ARRAY, "]"); | |
104 | } | |
105 | ||
106 | public JSONStringer object() throws JSONException { | |
107 | return open(Scope.EMPTY_OBJECT, "{"); | |
108 | } | |
109 | ||
110 | public JSONStringer endObject() throws JSONException { | |
111 | return close(Scope.EMPTY_OBJECT, Scope.NONEMPTY_OBJECT, "}"); | |
112 | } | |
113 | ||
114 | /** | |
115 | * Enters a new scope by appending any necessary whitespace and the given | |
116 | * bracket. | |
117 | */ | |
118 | JSONStringer open(Scope empty, String openBracket) throws JSONException { | |
119 | if (stack.isEmpty() && out.length() > 0) { | |
120 | throw new JSONException("Nesting problem: multiple top-level roots"); | |
121 | } | |
122 | beforeValue(); | |
123 | stack.add(empty); | |
124 | out.append(openBracket); | |
125 | return this; | |
126 | } | |
127 | ||
128 | /** | |
129 | * Closes the current scope by appending any necessary whitespace and the | |
130 | * given bracket. | |
131 | */ | |
132 | JSONStringer close(Scope empty, Scope nonempty, String closeBracket) throws JSONException { | |
133 | Scope context = peek(); | |
134 | if (context != nonempty && context != empty) { | |
135 | throw new JSONException("Nesting problem"); | |
136 | } | |
137 | ||
138 | stack.remove(stack.size() - 1); | |
139 | if (context == nonempty) { | |
140 | newline(); | |
141 | } | |
142 | out.append(closeBracket); | |
143 | return this; | |
144 | } | |
145 | ||
146 | /** | |
147 | * Returns the value on the top of the stack. | |
148 | */ | |
149 | private Scope peek() throws JSONException { | |
150 | if (stack.isEmpty()) { | |
151 | throw new JSONException("Nesting problem"); | |
152 | } | |
153 | return stack.get(stack.size() - 1); | |
154 | } | |
155 | ||
156 | /** | |
157 | * Replace the value on the top of the stack with the given value. | |
158 | */ | |
159 | private void replaceTop(Scope topOfStack) { | |
160 | stack.set(stack.size() - 1, topOfStack); | |
161 | } | |
162 | ||
163 | public JSONStringer value(Object value) throws JSONException { | |
164 | if (stack.isEmpty()) { | |
165 | throw new JSONException("Nesting problem"); | |
166 | } | |
167 | ||
168 | if (value instanceof JSONArray) { | |
169 | ((JSONArray) value).writeTo(this); | |
170 | return this; | |
171 | ||
172 | } else if (value instanceof JSONObject) { | |
173 | ((JSONObject) value).writeTo(this); | |
174 | return this; | |
175 | } | |
176 | ||
177 | beforeValue(); | |
178 | ||
179 | if (value == null | |
180 | || value instanceof Boolean | |
181 | || value == JSONObject.NULL) { | |
182 | out.append(value); | |
183 | ||
184 | } else if (value instanceof Number) { | |
185 | out.append(JSONObject.numberToString((Number) value)); | |
186 | ||
187 | } else { | |
188 | string(value.toString()); | |
189 | } | |
190 | ||
191 | return this; | |
192 | } | |
193 | ||
194 | public JSONStringer value(boolean value) throws JSONException { | |
195 | if (stack.isEmpty()) { | |
196 | throw new JSONException("Nesting problem"); | |
197 | } | |
198 | beforeValue(); | |
199 | out.append(value); | |
200 | return this; | |
201 | } | |
202 | ||
203 | public JSONStringer value(double value) throws JSONException { | |
204 | if (stack.isEmpty()) { | |
205 | throw new JSONException("Nesting problem"); | |
206 | } | |
207 | beforeValue(); | |
208 | out.append(JSONObject.numberToString(value)); | |
209 | return this; | |
210 | } | |
211 | ||
212 | public JSONStringer value(long value) throws JSONException { | |
213 | if (stack.isEmpty()) { | |
214 | throw new JSONException("Nesting problem"); | |
215 | } | |
216 | beforeValue(); | |
217 | out.append(value); | |
218 | return this; | |
219 | } | |
220 | ||
221 | private void string(String value) { | |
222 | out.append("\""); | |
223 | for (int i = 0, length = value.length(); i < length; i++) { | |
224 | char c = value.charAt(i); | |
225 | ||
226 | /* | |
227 | * From RFC 4627, "All Unicode characters may be placed within the | |
228 | * quotation marks except for the characters that must be escaped: | |
229 | * quotation mark, reverse solidus, and the control characters | |
230 | * (U+0000 through U+001F)." | |
231 | */ | |
232 | switch (c) { | |
233 | case '"': | |
234 | case '\\': | |
235 | case '/': | |
236 | out.append('\\').append(c); | |
237 | break; | |
238 | ||
239 | case '\t': | |
240 | out.append("\\t"); | |
241 | break; | |
242 | ||
243 | case '\b': | |
244 | out.append("\\b"); | |
245 | break; | |
246 | ||
247 | case '\n': | |
248 | out.append("\\n"); | |
249 | break; | |
250 | ||
251 | case '\r': | |
252 | out.append("\\r"); | |
253 | break; | |
254 | ||
255 | case '\f': | |
256 | out.append("\\f"); | |
257 | break; | |
258 | ||
259 | default: | |
260 | if (c <= 0x1F) { | |
261 | out.append(String.format("\\u%04x", (int) c)); | |
262 | } else { | |
263 | out.append(c); | |
264 | } | |
265 | break; | |
266 | } | |
267 | ||
268 | } | |
269 | out.append("\""); | |
270 | } | |
271 | ||
272 | private void newline() { | |
273 | if (indent == null) { | |
274 | return; | |
275 | } | |
276 | ||
277 | out.append("\n"); | |
278 | for (int i = 0; i < stack.size(); i++) { | |
279 | out.append(indent); | |
280 | } | |
281 | } | |
282 | ||
283 | public JSONStringer key(String name) throws JSONException { | |
284 | if (name == null) { | |
285 | throw new JSONException("Names must be non-null"); | |
286 | } | |
287 | beforeKey(); | |
288 | string(name); | |
289 | return this; | |
290 | } | |
291 | ||
292 | /** | |
293 | * Inserts any necessary separators and whitespace before a name. Also | |
294 | * adjusts the stack to expect the key's value. | |
295 | */ | |
296 | private void beforeKey() throws JSONException { | |
297 | Scope context = peek(); | |
298 | if (context == Scope.NONEMPTY_OBJECT) { // first in object | |
299 | out.append(','); | |
300 | } else if (context != Scope.EMPTY_OBJECT) { // not in an object! | |
301 | throw new JSONException("Nesting problem"); | |
302 | } | |
303 | newline(); | |
304 | replaceTop(Scope.DANGLING_KEY); | |
305 | } | |
306 | ||
307 | /** | |
308 | * Inserts any necessary separators and whitespace before a literal value, | |
309 | * inline array, or inline object. Also adjusts the stack to expect either a | |
310 | * closing bracket or another element. | |
311 | */ | |
312 | private void beforeValue() throws JSONException { | |
313 | if (stack.isEmpty()) { | |
314 | return; | |
315 | } | |
316 | ||
317 | Scope context = peek(); | |
318 | if (context == Scope.EMPTY_ARRAY) { // first in array | |
319 | replaceTop(Scope.NONEMPTY_ARRAY); | |
320 | newline(); | |
321 | } else if (context == Scope.NONEMPTY_ARRAY) { // another in array | |
322 | out.append(','); | |
323 | newline(); | |
324 | } else if (context == Scope.DANGLING_KEY) { // value for key | |
325 | out.append(indent == null ? ":" : ": "); | |
326 | replaceTop(Scope.NONEMPTY_OBJECT); | |
327 | } else if (context != Scope.NULL) { | |
328 | throw new JSONException("Nesting problem"); | |
329 | } | |
330 | } | |
331 | ||
332 | /** | |
333 | * Although it contradicts the general contract of {@link Object#toString}, | |
334 | * this method returns null if the stringer contains no data. | |
335 | */ | |
336 | @Override public String toString() { | |
337 | return out.length() == 0 ? null : out.toString(); | |
338 | } | |
339 | } |
0 | /* | |
1 | * Copyright (C) 2010 The Android Open Source Project | |
2 | * | |
3 | * Licensed under the Apache License, Version 2.0 (the "License"); | |
4 | * you may not use this file except in compliance with the License. | |
5 | * You may obtain a copy of the License at | |
6 | * | |
7 | * http://www.apache.org/licenses/LICENSE-2.0 | |
8 | * | |
9 | * Unless required by applicable law or agreed to in writing, software | |
10 | * distributed under the License is distributed on an "AS IS" BASIS, | |
11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
12 | * See the License for the specific language governing permissions and | |
13 | * limitations under the License. | |
14 | */ | |
15 | ||
16 | package org.json; | |
17 | ||
18 | // Note: this class was written without inspecting the non-free org.json sourcecode. | |
19 | ||
20 | /** | |
21 | * | |
22 | */ | |
23 | public class JSONTokener { | |
24 | ||
25 | /** The input JSON. */ | |
26 | private final String in; | |
27 | ||
28 | /** | |
29 | * The index of the next character to be returned by {@link #next()}. When | |
30 | * the input is exhausted, this equals the input's length. | |
31 | */ | |
32 | private int pos; | |
33 | ||
34 | public JSONTokener(String in) { | |
35 | this.in = in; | |
36 | } | |
37 | ||
38 | public Object nextValue() throws JSONException { | |
39 | int c = nextCleanInternal(); | |
40 | switch (c) { | |
41 | case -1: | |
42 | throw syntaxError("End of input"); | |
43 | ||
44 | case '{': | |
45 | return readObject(); | |
46 | ||
47 | case '[': | |
48 | return readArray(); | |
49 | ||
50 | case '\'': | |
51 | case '"': | |
52 | return nextString((char) c); | |
53 | ||
54 | default: | |
55 | pos--; | |
56 | return readLiteral(); | |
57 | } | |
58 | } | |
59 | ||
60 | private int nextCleanInternal() throws JSONException { | |
61 | while (pos < in.length()) { | |
62 | int c = in.charAt(pos++); | |
63 | switch (c) { | |
64 | case '\t': | |
65 | case ' ': | |
66 | case '\n': | |
67 | case '\r': | |
68 | continue; | |
69 | ||
70 | case '/': | |
71 | if (pos == in.length()) { | |
72 | return c; | |
73 | } | |
74 | ||
75 | char peek = in.charAt(pos); | |
76 | if (peek != '*' && peek != '/') { | |
77 | return c; | |
78 | } | |
79 | ||
80 | skipComment(); | |
81 | continue; | |
82 | ||
83 | default: | |
84 | return c; | |
85 | } | |
86 | } | |
87 | ||
88 | return -1; | |
89 | } | |
90 | ||
91 | /** | |
92 | * Advances the position until it is beyond the current comment. The opening | |
93 | * slash '/' should have already been read, and character at the current | |
94 | * position be an asterisk '*' for a C-style comment or a slash '/' for an | |
95 | * end-of-line comment. | |
96 | * | |
97 | * @throws JSONException if a C-style comment was not terminated. | |
98 | */ | |
99 | private void skipComment() throws JSONException { | |
100 | if (in.charAt(pos++) == '*') { | |
101 | int commentEnd = in.indexOf("*/", pos); | |
102 | if (commentEnd == -1) { | |
103 | throw syntaxError("Unterminated comment"); | |
104 | } | |
105 | pos = commentEnd + 2; | |
106 | ||
107 | } else { | |
108 | /* | |
109 | * Skip to the next newline character. If the line is terminated by | |
110 | * "\r\n", the '\n' will be consumed as whitespace by the caller. | |
111 | */ | |
112 | for (; pos < in.length(); pos++) { | |
113 | char c = in.charAt(pos); | |
114 | if (c == '\r' || c == '\n') { | |
115 | pos++; | |
116 | break; | |
117 | } | |
118 | } | |
119 | } | |
120 | } | |
121 | ||
122 | /** | |
123 | * | |
124 | * | |
125 | * @throws NumberFormatException if any unicode escape sequences are | |
126 | * malformed. | |
127 | */ | |
128 | public String nextString(char quote) throws JSONException { | |
129 | /* | |
130 | * For strings that are free of escape sequences, we can just extract | |
131 | * the result as a substring of the input. But if we encounter an escape | |
132 | * sequence, we need to use a StringBuilder to compose the result. | |
133 | */ | |
134 | StringBuilder builder = null; | |
135 | ||
136 | /* the index of the first character not yet appended to the builder. */ | |
137 | int start = pos; | |
138 | ||
139 | while (pos < in.length()) { | |
140 | int c = in.charAt(pos++); | |
141 | if (c == quote) { | |
142 | if (builder == null) { | |
143 | // a new string avoids leaking memory | |
144 | return new String(in.substring(start, pos - 1)); | |
145 | } else { | |
146 | builder.append(in, start, pos - 1); | |
147 | return builder.toString(); | |
148 | } | |
149 | } | |
150 | ||
151 | if (c == '\\') { | |
152 | if (pos == in.length()) { | |
153 | throw syntaxError("Unterminated escape sequence"); | |
154 | } | |
155 | if (builder == null) { | |
156 | builder = new StringBuilder(); | |
157 | } | |
158 | builder.append(in, start, pos - 1); | |
159 | builder.append(readEscapeCharacter()); | |
160 | start = pos; | |
161 | } | |
162 | } | |
163 | ||
164 | throw syntaxError("Unterminated string"); | |
165 | } | |
166 | ||
167 | /** | |
168 | * Unescapes the character identified by the character or characters that | |
169 | * immediately follow a backslash. The backslash '\' should have already | |
170 | * been read. This supports both unicode escapes "u000A" and two-character | |
171 | * escapes "\n". | |
172 | * | |
173 | * @throws NumberFormatException if any unicode escape sequences are | |
174 | * malformed. | |
175 | */ | |
176 | private char readEscapeCharacter() throws JSONException { | |
177 | char escaped = in.charAt(pos++); | |
178 | switch (escaped) { | |
179 | case 'u': | |
180 | if (pos + 4 > in.length()) { | |
181 | throw syntaxError("Unterminated escape sequence"); | |
182 | } | |
183 | String hex = in.substring(pos, pos + 4); | |
184 | pos += 4; | |
185 | return (char) Integer.parseInt(hex, 16); | |
186 | ||
187 | case 't': | |
188 | return '\t'; | |
189 | ||
190 | case 'b': | |
191 | return '\b'; | |
192 | ||
193 | case 'n': | |
194 | return '\n'; | |
195 | ||
196 | case 'r': | |
197 | return '\r'; | |
198 | ||
199 | case 'f': | |
200 | return '\f'; | |
201 | ||
202 | case '\'': | |
203 | case '"': | |
204 | case '\\': | |
205 | default: | |
206 | return escaped; | |
207 | } | |
208 | } | |
209 | ||
210 | /** | |
211 | * Reads a null, boolean, numeric or unquoted string literal value. Numeric | |
212 | * values will be returned as an Integer, Long, or Double, in that order of | |
213 | * preference. | |
214 | */ | |
215 | private Object readLiteral() throws JSONException { | |
216 | String literal = nextToInternal("{}[]/\\:,=;# \t\f"); | |
217 | ||
218 | if (literal.length() == 0) { | |
219 | throw syntaxError("Expected literal value"); | |
220 | } else if ("null".equalsIgnoreCase(literal)) { | |
221 | return JSONObject.NULL; | |
222 | } else if ("true".equalsIgnoreCase(literal)) { | |
223 | return Boolean.TRUE; | |
224 | } else if ("false".equalsIgnoreCase(literal)) { | |
225 | return Boolean.FALSE; | |
226 | } | |
227 | ||
228 | /* try to parse as an integral type... */ | |
229 | if (literal.indexOf('.') == -1) { | |
230 | int base = 10; | |
231 | String number = literal; | |
232 | if (number.startsWith("0x") || number.startsWith("0X")) { | |
233 | number = number.substring(2); | |
234 | base = 16; | |
235 | } else if (number.startsWith("0") && number.length() > 1) { | |
236 | number = number.substring(1); | |
237 | base = 8; | |
238 | } | |
239 | try { | |
240 | long longValue = Long.parseLong(number, base); | |
241 | if (longValue <= Integer.MAX_VALUE && longValue >= Integer.MIN_VALUE) { | |
242 | return (int) longValue; | |
243 | } else { | |
244 | return longValue; | |
245 | } | |
246 | } catch (NumberFormatException e) { | |
247 | /* | |
248 | * This only happens for integral numbers greater than | |
249 | * Long.MAX_VALUE, numbers in exponential form (5e-10) and | |
250 | * unquoted strings. Fall through to try floating point. | |
251 | */ | |
252 | } | |
253 | } | |
254 | ||
255 | /* ...next try to parse as a floating point... */ | |
256 | try { | |
257 | return Double.valueOf(literal); | |
258 | } catch (NumberFormatException e) { | |
259 | } | |
260 | ||
261 | /* ... finally give up. We have an unquoted string */ | |
262 | return new String(literal); // a new string avoids leaking memory | |
263 | } | |
264 | ||
265 | /** | |
266 | * Returns text from the current position until the first of any of the | |
267 | * given characters or a newline character, excluding that character. The | |
268 | * position is advanced to the excluded character. | |
269 | */ | |
270 | private String nextToInternal(String excluded) { | |
271 | int start = pos; | |
272 | for (; pos < in.length(); pos++) { | |
273 | char c = in.charAt(pos); | |
274 | if (c == '\r' || c == '\n' || excluded.indexOf(c) != -1) { | |
275 | return in.substring(start, pos); | |
276 | } | |
277 | } | |
278 | return in.substring(start); | |
279 | } | |
280 | ||
281 | /** | |
282 | * Reads a sequence of key/value pairs and the trailing closing brace '}' of | |
283 | * an object. The opening brace '{' should have already been read. | |
284 | */ | |
285 | private JSONObject readObject() throws JSONException { | |
286 | JSONObject result = new JSONObject(); | |
287 | ||
288 | /* Peek to see if this is the empty object. */ | |
289 | int first = nextCleanInternal(); | |
290 | if (first == '}') { | |
291 | return result; | |
292 | } else if (first != -1) { | |
293 | pos--; | |
294 | } | |
295 | ||
296 | while (true) { | |
297 | Object name = nextValue(); | |
298 | if (!(name instanceof String)) { | |
299 | if (name == null) { | |
300 | throw syntaxError("Names cannot be null"); | |
301 | } else { | |
302 | throw syntaxError("Names must be strings, but " + name | |
303 | + " is of type " + name.getClass().getName()); | |
304 | } | |
305 | } | |
306 | ||
307 | /* | |
308 | * Expect the name/value separator to be either a colon ':', an | |
309 | * equals sign '=', or an arrow "=>". The last two are bogus but we | |
310 | * include them because that's what the original implementation did. | |
311 | */ | |
312 | int separator = nextCleanInternal(); | |
313 | if (separator != ':' && separator != '=') { | |
314 | throw syntaxError("Expected ':' after " + name); | |
315 | } | |
316 | if (pos < in.length() && in.charAt(pos) == '>') { | |
317 | pos++; | |
318 | } | |
319 | ||
320 | result.put((String) name, nextValue()); | |
321 | ||
322 | switch (nextCleanInternal()) { | |
323 | case '}': | |
324 | return result; | |
325 | case ';': | |
326 | case ',': | |
327 | continue; | |
328 | default: | |
329 | throw syntaxError("Unterminated object"); | |
330 | } | |
331 | } | |
332 | } | |
333 | ||
334 | /** | |
335 | * Reads a sequence of values and the trailing closing brace ']' of an | |
336 | * array. The opening brace '[' should have already been read. Note that | |
337 | * "[]" yields an empty array, but "[,]" returns a two-element array | |
338 | * equivalent to "[null,null]". | |
339 | */ | |
340 | private JSONArray readArray() throws JSONException { | |
341 | JSONArray result = new JSONArray(); | |
342 | ||
343 | /* to cover input that ends with ",]". */ | |
344 | boolean hasTrailingSeparator = false; | |
345 | ||
346 | while (true) { | |
347 | switch (nextCleanInternal()) { | |
348 | case -1: | |
349 | throw syntaxError("Unterminated array"); | |
350 | case ']': | |
351 | if (hasTrailingSeparator) { | |
352 | result.put(null); | |
353 | } | |
354 | return result; | |
355 | case ',': | |
356 | case ';': | |
357 | /* A separator without a value first means "null". */ | |
358 | result.put(null); | |
359 | hasTrailingSeparator = true; | |
360 | continue; | |
361 | default: | |
362 | pos--; | |
363 | } | |
364 | ||
365 | result.put(nextValue()); | |
366 | ||
367 | switch (nextCleanInternal()) { | |
368 | case ']': | |
369 | return result; | |
370 | case ',': | |
371 | case ';': | |
372 | hasTrailingSeparator = true; | |
373 | continue; | |
374 | default: | |
375 | throw syntaxError("Unterminated array"); | |
376 | } | |
377 | } | |
378 | } | |
379 | ||
380 | public JSONException syntaxError(String text) { | |
381 | return new JSONException(text + this); | |
382 | } | |
383 | ||
384 | @Override public String toString() { | |
385 | // consistent with the original implementation | |
386 | return " at character " + pos + " of " + in; | |
387 | } | |
388 | ||
389 | /* | |
390 | * Legacy APIs. | |
391 | * | |
392 | * None of the methods below are on the critical path of parsing JSON | |
393 | * documents. They exist only because they were exposed by the original | |
394 | * implementation and may be used by some clients. | |
395 | */ | |
396 | ||
397 | public boolean more() { | |
398 | return pos < in.length(); | |
399 | } | |
400 | ||
401 | public char next() { | |
402 | return pos < in.length() ? in.charAt(pos++) : '\0'; | |
403 | } | |
404 | ||
405 | public char next(char c) throws JSONException { | |
406 | char result = next(); | |
407 | if (result != c) { | |
408 | throw syntaxError("Expected " + c + " but was " + result); | |
409 | } | |
410 | return result; | |
411 | } | |
412 | ||
413 | public char nextClean() throws JSONException { | |
414 | int nextCleanInt = nextCleanInternal(); | |
415 | return nextCleanInt == -1 ? '\0' : (char) nextCleanInt; | |
416 | } | |
417 | ||
418 | /** | |
419 | * TODO: note about how this method returns a substring, and could cause a memory leak | |
420 | */ | |
421 | public String next(int length) throws JSONException { | |
422 | if (pos + length > in.length()) { | |
423 | throw syntaxError(length + " is out of bounds"); | |
424 | } | |
425 | String result = in.substring(pos, pos + length); | |
426 | pos += length; | |
427 | return result; | |
428 | } | |
429 | ||
430 | /** | |
431 | * TODO: note about how this method returns a substring, and could cause a memory leak | |
432 | */ | |
433 | public String nextTo(String excluded) { | |
434 | if (excluded == null) { | |
435 | throw new NullPointerException(); | |
436 | } | |
437 | return nextToInternal(excluded).trim(); | |
438 | } | |
439 | ||
440 | /** | |
441 | * TODO: note about how this method returns a substring, and could cause a memory leak | |
442 | */ | |
443 | public String nextTo(char excluded) { | |
444 | return nextToInternal(String.valueOf(excluded)).trim(); | |
445 | } | |
446 | ||
447 | public void skipPast(String thru) { | |
448 | int thruStart = in.indexOf(thru, pos); | |
449 | pos = thruStart == -1 ? in.length() : (thruStart + thru.length()); | |
450 | } | |
451 | ||
452 | public char skipTo(char to) { | |
453 | for (int i = pos, length = in.length(); i < length; i++) { | |
454 | if (in.charAt(i) == to) { | |
455 | pos = i; | |
456 | return to; | |
457 | } | |
458 | } | |
459 | return '\0'; | |
460 | } | |
461 | ||
462 | public void back() { | |
463 | if (--pos == -1) { | |
464 | pos = 0; | |
465 | } | |
466 | } | |
467 | ||
468 | public static int dehexchar(char hex) { | |
469 | if (hex >= '0' && hex <= '9') { | |
470 | return hex - '0'; | |
471 | } else if (hex >= 'A' && hex <= 'F') { | |
472 | return hex - 'A' + 10; | |
473 | } else if (hex >= 'a' && hex <= 'f') { | |
474 | return hex - 'a' + 10; | |
475 | } else { | |
476 | return -1; | |
477 | } | |
478 | } | |
479 | } |