001    package com.nimbusds.oauth2.sdk.util;
002    
003    
004    import java.net.MalformedURLException;
005    import java.net.URL;
006    
007    import java.util.List;
008    
009    import javax.mail.internet.AddressException;
010    import javax.mail.internet.InternetAddress;
011    
012    import net.minidev.json.JSONArray;
013    import net.minidev.json.JSONObject;
014    
015    import net.minidev.json.parser.JSONParser;
016    
017    import com.nimbusds.oauth2.sdk.ParseException;
018    
019    
020    /**
021     * JSON object helper methods for parsing and typed retrieval of member values.
022     *
023     * @author Vladimir Dzhuvinov
024     * @version $version$ (2013-01-28)
025     */
026    public class JSONObjectUtils {
027            
028            
029            /**
030             * Returns {@code true} if the JSON object is defined and contains the 
031             * specified key.
032             *
033             * @param jsonObject The JSON object to check. May be {@code null}.
034             * @param key        The key to check. Must not be {@code null}.
035             *
036             * @return {@code true} if the JSON object is defined and contains the
037             *         specified key, else {@code false}.
038             */
039            public static boolean containsKey(final JSONObject jsonObject, final String key) {
040            
041                    if (jsonObject != null && jsonObject.containsKey(key))
042                            return true;
043                    else 
044                            return false;
045            }
046            
047            
048            /**
049             * Parses a JSON object.
050             *
051             * <p>Specific JSON to Java entity mapping (as per JSON Simple):
052             *
053             * <ul>
054             *     <li>JSON numbers mapped to {@code java.lang.Number}.
055             *     <li>JSON integer numbers mapped to {@code long}.
056             *     <li>JSON fraction numbers mapped to {@code double}.
057             * </ul>
058             *
059             * @param s The JSON object string to parse. Must not be {@code null}.
060             *
061             * @return The JSON object.
062             *
063             * @throws ParseException If the string cannot be parsed to a JSON 
064             *                        object.
065             */
066            public static JSONObject parseJSONObject(final String s) 
067                    throws ParseException {
068                    
069                    Object o = null;
070                    
071                    try {
072                            o = new JSONParser(JSONParser.USE_HI_PRECISION_FLOAT).parse(s);
073                            
074                    } catch (net.minidev.json.parser.ParseException e) {
075                            
076                            throw new ParseException("Invalid JSON: " + e.getMessage(), e);
077                    }
078                    
079                    if (o instanceof JSONObject)
080                            return (JSONObject)o;
081                    else
082                            throw new ParseException("JSON entity is not an object");
083            }
084            
085            
086            /**
087             * Gets a generic member of a JSON object.
088             *
089             * @param o     The JSON object. Must not be {@code null}.
090             * @param key   The JSON object member key. Must not be {@code null}.
091             * @param clazz The expected class of the JSON object member value. Must
092             *              not be {@code null}.
093             *
094             * @return The JSON object member value.
095             *
096             * @throws ParseException If the value is missing, {@code null} or not
097             *                        of the expected type.
098             */
099            @SuppressWarnings("unchecked")
100            private static <T> T getGeneric(final JSONObject o, final String key, final Class<T> clazz)
101                    throws ParseException {
102            
103                    if (! o.containsKey(key))
104                            throw new ParseException("Missing JSON object member with key \"" + key + "\"");
105                    
106                    if (o.get(key) == null)
107                            throw new ParseException("JSON object member with key \"" + key + "\" has null value");
108                    
109                    Object value = o.get(key);
110                    
111                    if (! clazz.isAssignableFrom(value.getClass()))
112                            throw new ParseException("Unexpected type of JSON object member with key \"" + key + "\"");
113                    
114                    return (T)value;
115            }
116    
117    
118            /**
119             * Gets a boolean member of a JSON object.
120             *
121             * @param o   The JSON object. Must not be {@code null}.
122             * @param key The JSON object member key. Must not be {@code null}.
123             *
124             * @return The member value.
125             *
126             * @throws ParseException If the value is missing, {@code null} or not
127             *                        of the expected type.
128             */
129            public static boolean getBoolean(final JSONObject o, final String key)
130                    throws ParseException {
131                    
132                    return getGeneric(o, key, Boolean.class);
133            }
134            
135            
136            /**
137             * Gets an number member of a JSON object as {@code int}.
138             *
139             * @param o   The JSON object. Must not be {@code null}.
140             * @param key The JSON object member key. Must not be {@code null}.
141             *
142             * @return The member value.
143             *
144             * @throws ParseException If the value is missing, {@code null} or not
145             *                        of the expected type.
146             */
147            public static int getInt(final JSONObject o, final String key)
148                    throws ParseException {
149                    
150                    return getGeneric(o, key, Number.class).intValue();     
151            }
152            
153            
154            /**
155             * Gets a number member of a JSON object as {@code long}.
156             *
157             * @param o   The JSON object. Must not be {@code null}.
158             * @param key The JSON object member key. Must not be {@code null}.
159             *
160             * @return The member value.
161             *
162             * @throws ParseException If the value is missing, {@code null} or not
163             *                        of the expected type.
164             */
165            public static long getLong(final JSONObject o, final String key)
166                    throws ParseException {
167                    
168                    return getGeneric(o, key, Number.class).longValue();
169            }
170            
171            
172            /**
173             * Gets a number member of a JSON object {@code float}.
174             *
175             * @param o   The JSON object. Must not be {@code null}.
176             * @param key The JSON object member key. Must not be {@code null}.
177             *
178             * @return The member value.
179             *
180             * @throws ParseException If the value is missing, {@code null} or not
181             *                        of the expected type.
182             */
183            public static float getFloat(final JSONObject o, final String key)
184                    throws ParseException {
185                    
186                    return getGeneric(o, key, Number.class).floatValue();
187            }
188            
189            
190            /**
191             * Gets a number member of a JSON object as {@code double}.
192             *
193             * @param o   The JSON object. Must not be {@code null}.
194             * @param key The JSON object member key. Must not be {@code null}.
195             *
196             * @return The member value.
197             *
198             * @throws ParseException If the value is missing, {@code null} or not
199             *                        of the expected type.
200             */
201            public static double getDouble(final JSONObject o, final String key)
202                    throws ParseException {
203                    
204                    return getGeneric(o, key, Number.class).doubleValue();
205            }
206            
207            
208            /**
209             * Gets a string member of a JSON object.
210             *
211             * @param o   The JSON object. Must not be {@code null}.
212             * @param key The JSON object member key. Must not be {@code null}.
213             *
214             * @return The member value.
215             *
216             * @throws ParseException If the value is missing, {@code null} or not
217             *                        of the expected type.
218             */
219            public static String getString(final JSONObject o, final String key)
220                    throws ParseException {
221                    
222                    return getGeneric(o, key, String.class);
223            }
224    
225    
226            /**
227             * Gets a string member of a JSON object as an enumerated object.
228             *
229             * @param o         The JSON object. Must not be {@code null}.
230             * @param key       The JSON object member key. Must not be
231             *                  {@code null}.
232             * @param enumClass The enumeration class. Must not be {@code null}.
233             *
234             * @return The member value.
235             *
236             * @throws ParseException If the value is missing, {@code null} or not
237             *                        of the expected type.
238             */
239            public static <T extends Enum<T>> T getEnum(final JSONObject o, 
240                                                        final String key,
241                                                        final Class<T> enumClass)
242                    throws ParseException {
243    
244                    String value = getString(o, key);
245    
246                    for (T en: enumClass.getEnumConstants()) {
247                                   
248                            if (en.toString().equalsIgnoreCase(value))
249                                    return en;
250                    }
251    
252                    throw new ParseException("Unexpected value of JSON object member with key \"" + key + "\"");
253            }
254            
255            
256            /**
257             * Gets a string member of a JSON object as {@code java.net.URL}.
258             *
259             * @param o   The JSON object. Must not be {@code null}.
260             * @param key The JSON object member key. Must not be {@code null}.
261             *
262             * @return The member value.
263             *
264             * @throws ParseException If the value is missing, {@code null} or not
265             *                        of the expected type.
266             */
267            public static URL getURL(final JSONObject o, final String key)
268                    throws ParseException {
269                    
270                    try {
271                            return new URL(getGeneric(o, key, String.class));
272                            
273                    } catch (MalformedURLException e) {
274                    
275                            throw new ParseException(e.getMessage(), e);
276                    }
277            }
278            
279            
280            /**
281             * Gets a string member of a JSON object as 
282             * {@code javax.mail.internet.InternetAddress}.
283             *
284             * @param o   The JSON object. Must not be {@code null}.
285             * @param key The JSON object member key. Must not be {@code null}.
286             *
287             * @return The member value.
288             *
289             * @throws ParseException If the value is missing, {@code null} or not
290             *                        of the expected type.
291             */
292            public static InternetAddress getEmail(final JSONObject o, final String key)
293                    throws ParseException {
294                    
295                    try {
296                            final boolean strict = true;
297                            
298                            return new InternetAddress(getGeneric(o, key, String.class), strict);
299                            
300                    } catch (AddressException e) {
301                    
302                            throw new ParseException(e.getMessage(), e);
303                    }
304            }
305            
306            
307            /**
308             * Gets a JSON array member of a JSON object.
309             *
310             * @param o   The JSON object. Must not be {@code null}.
311             * @param key The JSON object member key. Must not be {@code null}.
312             *
313             * @return The member value.
314             *
315             * @throws ParseException If the value is missing, {@code null} or not
316             *                        of the expected type.
317             */
318            public static JSONArray getJSONArray(final JSONObject o, final String key)
319                    throws ParseException {
320                    
321                    return getGeneric(o, key, JSONArray.class);
322            }
323    
324    
325            /**
326             * Gets a list member of a JSON object.
327             *
328             * @param o   The JSON object. Must not be {@code null}.
329             * @param key The JSON object member key. Must not be {@code null}.
330             *
331             * @return The member value.
332             *
333             * @throws ParseException If the value is missing, {@code null} or not
334             *                        of the expected type.
335             */
336            @SuppressWarnings("unchecked")
337            public static List<Object> getList(final JSONObject o, final String key)
338                    throws ParseException {
339                    
340                    return getGeneric(o, key, List.class);
341            }
342    
343    
344            /**
345             * Gets a string array member of a JSON object.
346             *
347             * @param o   The JSON object. Must not be {@code null}.
348             * @param key The JSON object member key. Must not be {@code null}.
349             *
350             * @return The member value.
351             *
352             * @throws ParseException If the value is missing, {@code null} or not
353             *                        of the expected type.
354             */
355            public static String[] getStringArray(final JSONObject o, final String key)
356                    throws ParseException {
357    
358                    List<Object> list = getList(o, key);
359    
360                    try {
361                            return list.toArray(new String[0]);
362    
363                    } catch (ArrayStoreException e) {
364    
365                            throw new ParseException("JSON object member with key \"" + key + "\" is not an array of strings");
366                    }
367            }
368            
369            
370            /**
371             * Gets a JSON object member of a JSON object.
372             *
373             * @param o   The JSON object. Must not be {@code null}.
374             * @param key The JSON object member key. Must not be {@code null}.
375             *
376             * @return The member value.
377             *
378             * @throws ParseException If the value is missing, {@code null} or not
379             *                        of the expected type.
380             */
381            public static JSONObject getJSONObject(final JSONObject o, final String key)
382                    throws ParseException {
383                    
384                    return getGeneric(o, key, JSONObject.class);
385            }
386            
387    
388            /**
389             * Prevents instantiation.
390             */
391            private JSONObjectUtils() {
392            
393                    // Nothing to do
394            }
395    }
396