001/*
002 * The contents of this file are subject to the terms of the Common Development and
003 * Distribution License (the License). You may not use this file except in compliance with the
004 * License.
005 *
006 * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the
007 * specific language governing permission and limitations under the License.
008 *
009 * When distributing Covered Software, include this CDDL Header Notice in each file and include
010 * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL
011 * Header, with the fields enclosed by brackets [] replaced by your own identifying
012 * information: "Portions Copyrighted [year] [name of copyright owner]".
013 *
014 * Copyright © 2010–2011 ApexIdentity Inc. All rights reserved.
015 * Portions Copyrighted 2011-2016 ForgeRock AS.
016 */
017
018package org.forgerock.json;
019
020import static org.forgerock.util.Reject.checkNotNull;
021
022import java.io.File;
023import java.net.MalformedURLException;
024import java.net.URI;
025import java.net.URISyntaxException;
026import java.net.URL;
027import java.nio.charset.Charset;
028import java.nio.charset.IllegalCharsetNameException;
029import java.nio.charset.UnsupportedCharsetException;
030import java.util.ArrayList;
031import java.util.LinkedHashSet;
032import java.util.List;
033import java.util.Set;
034import java.util.UUID;
035import java.util.regex.Pattern;
036import java.util.regex.PatternSyntaxException;
037
038import org.forgerock.util.Function;
039import org.forgerock.util.Utils;
040import org.forgerock.util.time.Duration;
041
042/**
043 * This class contains the utility functions to convert a {@link JsonValue} to another type.
044 */
045public final class JsonValueFunctions {
046
047    private JsonValueFunctions() {
048    }
049
050    private static class TypeFunction<V> implements Function<JsonValue, V, JsonValueException> {
051
052        private final Class<V> type;
053
054        TypeFunction(Class<V> type) {
055            this.type = checkNotNull(type);
056        }
057
058        @Override
059        public V apply(JsonValue value) throws JsonValueException {
060            if (value.isNull()) {
061                return null;
062            }
063
064            Object object = value.getObject();
065            if (type.isInstance(object)) {
066                return type.cast(object);
067            }
068
069            throw new JsonValueException(value, "Expecting an element of type " + type.getName());
070        }
071    }
072
073    //@Checkstyle:off
074    private static final Function<JsonValue, Charset, JsonValueException> TO_CHARSET =
075            new Function<JsonValue, Charset, JsonValueException>() {
076                @Override
077                public Charset apply(JsonValue value) throws JsonValueException {
078                    try {
079                        return value.isNull() ? null : Charset.forName(value.asString());
080                    } catch (final IllegalCharsetNameException | UnsupportedCharsetException e) {
081                        throw new JsonValueException(value, e);
082                    }
083                }
084            };
085
086    private static final Function<JsonValue, Duration, JsonValueException> TO_DURATION =
087            new Function<JsonValue, Duration, JsonValueException>() {
088                @Override
089                public Duration apply(JsonValue value) throws JsonValueException {
090                    try {
091                        return value.isNull() ? null : Duration.duration(value.asString());
092                    } catch (final IllegalArgumentException iae) {
093                        throw new JsonValueException(value, iae);
094                    }
095                }
096            };
097
098    private static final Function<JsonValue, File, JsonValueException> TO_FILE =
099            new Function<JsonValue, File, JsonValueException>() {
100                @Override
101                public File apply(JsonValue value) throws JsonValueException {
102                    return value.isNull() ? null : new File(value.asString());
103                }
104            };
105
106    private static final Function<JsonValue, Pattern, JsonValueException> TO_PATTERN =
107            new Function<JsonValue, Pattern, JsonValueException>() {
108                @Override
109                public Pattern apply(JsonValue value) throws JsonValueException {
110                    try {
111                        return value.isNull() ? null : Pattern.compile(value.asString());
112                    } catch (final PatternSyntaxException pse) {
113                        throw new JsonValueException(value, pse);
114                    }
115                }
116            };
117
118    private static final Function<JsonValue, JsonPointer, JsonValueException> TO_POINTER =
119            new Function<JsonValue, JsonPointer, JsonValueException>() {
120                @Override
121                public JsonPointer apply(JsonValue value) throws JsonValueException {
122                    try {
123                        return value.isNull() ? null : new JsonPointer(value.asString());
124                    } catch (final JsonValueException jve) {
125                        throw jve;
126                    } catch (final JsonException je) {
127                        throw new JsonValueException(value, je);
128                    }
129                }
130            };
131
132    private static final Function<JsonValue, URL, JsonValueException> TO_URL =
133            new Function<JsonValue, URL, JsonValueException>() {
134                @Override
135                public URL apply(JsonValue value) throws JsonValueException {
136                    try {
137                        return value.isNull() ? null : new URL(value.asString());
138                    } catch (final MalformedURLException e) {
139                        throw new JsonValueException(value, e);
140                    }
141                }
142            };
143
144    private static final Function<JsonValue, URI, JsonValueException> TO_URI =
145            new Function<JsonValue, URI, JsonValueException>() {
146                @Override
147                public URI apply(JsonValue value) throws JsonValueException {
148                    try {
149                        return value.isNull() ? null : new URI(value.asString());
150                    } catch (final URISyntaxException use) {
151                        throw new JsonValueException(value, use);
152                    }
153                }
154            };
155
156    private static final Function<JsonValue, UUID, JsonValueException> TO_UUID =
157            new Function<JsonValue, UUID, JsonValueException>() {
158                @Override
159                public UUID apply(JsonValue value) throws JsonValueException {
160                    try {
161                        return value.isNull() ? null : UUID.fromString(value.asString());
162                    } catch (final IllegalArgumentException iae) {
163                        throw new JsonValueException(value, iae);
164                    }
165                }
166            };
167
168    private static final Function<JsonValue, JsonValue, JsonValueException> IDENTITY =
169            new Function<JsonValue, JsonValue, JsonValueException>() {
170                @Override
171                public JsonValue apply(JsonValue value) throws JsonValueException {
172                    return value.copy();
173                }
174            };
175    //@Checkstyle:on
176
177    /**
178     * Returns the JSON string value as a character set used for byte
179     * encoding/decoding. If the JSON value is {@code null}, this function returns
180     * {@code null}.
181     *
182     * @return the character set represented by the string value.
183     * @throws JsonValueException
184     *         if the JSON value is not a string or the character set
185     *         specified is invalid.
186     */
187    public static Function<JsonValue, Charset, JsonValueException> charset() {
188        return TO_CHARSET;
189    }
190
191    /**
192     * Returns the JSON string value as a {@link Duration}. If the JSON value is {@code null}, this method returns
193     * {@code null}.
194     *
195     * @return the duration represented by the string value.
196     * @throws JsonValueException
197     *         if the JSON value is not a string or the duration
198     *         specified is invalid.
199     */
200    public static Function<JsonValue, Duration, JsonValueException> duration() {
201        return TO_DURATION;
202    }
203
204    /**
205     * Returns the JSON string value as an enum constant of the specified enum
206     * type. The string value and enum constants are compared, ignoring case
207     * considerations. If the JSON value is {@code null}, this method returns
208     * {@code null}.
209     *
210     * @param <T>
211     *         the enum type sub-class.
212     * @param type
213     *         the enum type to match constants with the value.
214     * @return the enum constant represented by the string value.
215     * @throws IllegalArgumentException
216     *         if {@code type} does not represent an enum type. or
217     *         if the JSON value does not match any of the enum's constants.
218     * @throws NullPointerException
219     *         if {@code type} is {@code null}.
220     */
221    public static <T extends Enum<T>> Function<JsonValue, T, JsonValueException> enumConstant(final Class<T> type) {
222        return new Function<JsonValue, T, JsonValueException>() {
223            @Override
224            public T apply(JsonValue value) throws JsonValueException {
225                return Utils.asEnum(value.asString(), type);
226            }
227        };
228    }
229
230    /**
231     * Returns the JSON string value as a {@code File} object. If the JSON value
232     * is {@code null}, this method returns {@code null}.
233     *
234     * @return a file represented by the string value.
235     * @throws JsonValueException
236     *         if the JSON value is not a string.
237     */
238    public static Function<JsonValue, File, JsonValueException> file() {
239        return TO_FILE;
240    }
241
242    /**
243     * Returns the JSON string value as a regular expression pattern. If the
244     * JSON value is {@code null}, this method returns {@code null}.
245     *
246     * @return the compiled regular expression pattern.
247     * @throws JsonValueException
248     *         if the pattern is not a string or the value is not a valid
249     *         regular expression pattern.
250     */
251    public static Function<JsonValue, Pattern, JsonValueException> pattern() {
252        return TO_PATTERN;
253    }
254
255    /**
256     * Returns the JSON string value as a JSON pointer. If the JSON value is
257     * {@code null}, this method returns {@code null}.
258     *
259     * @return the JSON pointer represented by the JSON value string.
260     * @throws JsonValueException
261     *         if the JSON value is not a string or valid JSON pointer.
262     */
263    public static Function<JsonValue, JsonPointer, JsonValueException> pointer() {
264        return TO_POINTER;
265    }
266
267    /**
268     * Returns the JSON string value as a uniform resource identifier. If the
269     * JSON value is {@code null}, this method returns {@code null}.
270     *
271     * @return the URI represented by the string value.
272     * @throws JsonValueException
273     *         if the given string violates URI syntax.
274     */
275    public static Function<JsonValue, URI, JsonValueException> uri() {
276        return TO_URI;
277    }
278
279    /**
280     * Returns the JSON string value as a uniform resource locator. If the
281     * JSON value is {@code null}, this method returns {@code null}.
282     *
283     * @return the URL represented by the string value.
284     * @throws JsonValueException
285     *         if the given string violates URL syntax.
286     */
287    public static Function<JsonValue, URL, JsonValueException> url() {
288        return TO_URL;
289    }
290
291    /**
292     * Returns the JSON string value as a universally unique identifier (UUID).
293     * If the JSON value is {@code null}, this method returns {@code null}.
294     *
295     * @return the UUID represented by the JSON value string.
296     * @throws JsonValueException
297     *         if the JSON value is not a string or valid UUID.
298     */
299    public static Function<JsonValue, UUID, JsonValueException> uuid() {
300        return TO_UUID;
301    }
302
303    /**
304     * Returns the JSON value as a {@link List} containing objects whose type
305     * (and value) is specified by a transformation function. If the value is
306     * {@code null}, this method returns {@code null}. It is up to to the
307     * transformation function to transform/enforce source types of the elements
308     * in the Json source collection.  If any of the elements of the list are not of
309     * the appropriate type, or the type-transformation cannot occur,
310     * the exception specified by the transformation function is thrown.
311     *
312     * @param <V>
313     *            the type of elements in this list
314     * @param <E>
315     *            the type of exception thrown by the transformation function
316     * @param transformFunction
317     *            a {@link Function} to transform an element of the JsonValue list
318     *            to the desired type
319     * @return the list value, or {@code null} if no value.
320     * @throws E
321     *             if the JSON value is not a {@code List}, not a {@code Set}, contains an
322     *             unexpected type, or contains an element that cannot be transformed
323     * @throws NullPointerException
324     *             if {@code transformFunction} is {@code null}.
325     */
326    public static <V, E extends Exception> Function<JsonValue, List<V>, E> listOf(
327            final Function<JsonValue, V, E> transformFunction) throws E {
328        return new Function<JsonValue, List<V>, E>() {
329            @Override
330            public List<V> apply(JsonValue value) throws E {
331                if (value.isCollection()) {
332                    final List<V> list = new ArrayList<>(value.size());
333                    for (JsonValue elem : value) {
334                        list.add(elem.as(transformFunction));
335                    }
336                    return list;
337                }
338                return null;
339            }
340        };
341    }
342
343    /**
344     * Returns the JSON value as a {@link Set} containing objects whose type
345     * (and value) is specified by a transformation function. If the value is
346     * {@code null}, this method returns {@code null}. It is up to to the
347     * transformation function to transform/enforce source types of the elements
348     * in the Json source collection.  If called on an object which wraps a List,
349     * this method will drop duplicates performing element comparisons using
350     * equals/hashCode. If any of the elements of the collection are not of
351     * the appropriate type, or the type-transformation cannot occur, the
352     * exception specified by the transformation function is thrown.
353     *
354     * @param <V>
355     *            the type of elements in this set
356     * @param <E>
357     *            the type of exception thrown by the transformation function
358     * @param transformFunction
359     *            a {@link Function} to transform an element of the JsonValue set
360     *            to the desired type
361     * @return the set value, or {@code null} if no value.
362     * @throws E
363     *             if the JSON value is not a {@code Set}, contains an
364     *             unexpected type, or contains an element that cannot be
365     *             transformed
366     * @throws NullPointerException
367     *             if {@code transformFunction} is {@code null}.
368     */
369    public static <V, E extends Exception> Function<JsonValue, Set<V>, E> setOf(
370            final Function<JsonValue, V, E> transformFunction) throws E {
371        return new Function<JsonValue, Set<V>, E>() {
372            @Override
373            public Set<V> apply(JsonValue value) throws E {
374                if (value.isCollection()) {
375                    final Set<V> set = new LinkedHashSet<>(value.size());
376                    for (JsonValue elem : value) {
377                        set.add(elem.as(transformFunction));
378                    }
379                    return set;
380                }
381                return null;
382            }
383        };
384    }
385
386    /**
387     * Returns the JSON value as a {@link Set} containing objects whose type
388     * (and value) is specified by the parameter {@code type}. If the value is
389     * {@code null}, this method returns {@code null}. If called on an object
390     * which wraps a List, this method will drop duplicates performing element
391     * comparisons using equals/hashCode. If any of the elements of the collection
392     * are not of the appropriate type, or the type-transformation cannot occur,
393     * {@link JsonValueException} is thrown.
394     *
395     * @param <V>
396     *            the type of elements in this set
397     * @param type
398     *            a {@link Class} that specifies the desired type of each element
399     *            in the resultant JsonValue set
400     * @return the set value, or {@code null} if no value.
401     * @throws NullPointerException
402     *             if {@code type} is {@code null}.
403     * @throws JsonValueException
404     *             if the elements of the collection cannot be cast as {@code type}.
405     */
406    public static <V> Function<JsonValue, Set<V>, JsonValueException> setOf(final Class<V> type) {
407        return setOf(new TypeFunction<>(type));
408    }
409
410    /**
411     * Returns the JSON value as the result of a deep JsonValue object-traversal,
412     * applying the provided transform {@code function} to each element.
413     *
414     * @param function
415     *            a {@link Function} that applies the desired element transformation
416     *            in the resultant JsonValue set
417     * @return the transformed JsonValue
418     * @throws JsonValueException
419     *             if the elements of the JsonValue cannot be transformed by {@code function}.
420     */
421    public static Function<JsonValue, JsonValue, JsonValueException> deepTransformBy(
422            Function<JsonValue, ?, JsonValueException> function) {
423        return new JsonValueTraverseFunction(function);
424    }
425
426    /**
427     * Returns an identity function that will copy the input {@link JsonValue}.
428     * @return an identity function that will copy the input {@link JsonValue}.
429     * @throws JsonValueException
430     *             if an error occurred while copying the input.
431     */
432    public static Function<JsonValue, JsonValue, JsonValueException> identity() {
433        return IDENTITY;
434    }
435}