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 java.io.File;
021import java.net.MalformedURLException;
022import java.net.URI;
023import java.net.URISyntaxException;
024import java.net.URL;
025import java.nio.charset.Charset;
026import java.nio.charset.IllegalCharsetNameException;
027import java.nio.charset.UnsupportedCharsetException;
028import java.util.ArrayList;
029import java.util.LinkedHashSet;
030import java.util.List;
031import java.util.Set;
032import java.util.UUID;
033import java.util.regex.Pattern;
034import java.util.regex.PatternSyntaxException;
035
036import org.forgerock.util.Function;
037import org.forgerock.util.Utils;
038import org.forgerock.util.time.Duration;
039
040/**
041 * This class contains the utility functions to convert a {@link JsonValue} to another type.
042 */
043public final class JsonValueFunctions {
044
045    private JsonValueFunctions() {
046    }
047
048    //@Checkstyle:off
049    private static final Function<JsonValue, Charset, JsonValueException> TO_CHARSET =
050            new Function<JsonValue, Charset, JsonValueException>() {
051                @Override
052                public Charset apply(JsonValue value) throws JsonValueException {
053                    try {
054                        return value.isNull() ? null : Charset.forName(value.asString());
055                    } catch (final IllegalCharsetNameException | UnsupportedCharsetException e) {
056                        throw new JsonValueException(value, e);
057                    }
058                }
059            };
060
061    private static final Function<JsonValue, Duration, JsonValueException> TO_DURATION =
062            new Function<JsonValue, Duration, JsonValueException>() {
063                @Override
064                public Duration apply(JsonValue value) throws JsonValueException {
065                    try {
066                        return value.isNull() ? null : Duration.duration(value.asString());
067                    } catch (final IllegalArgumentException iae) {
068                        throw new JsonValueException(value, iae);
069                    }
070                }
071            };
072
073    private static final Function<JsonValue, File, JsonValueException> TO_FILE =
074            new Function<JsonValue, File, JsonValueException>() {
075                @Override
076                public File apply(JsonValue value) throws JsonValueException {
077                    return value.isNull() ? null : new File(value.asString());
078                }
079            };
080
081    private static final Function<JsonValue, Pattern, JsonValueException> TO_PATTERN =
082            new Function<JsonValue, Pattern, JsonValueException>() {
083                @Override
084                public Pattern apply(JsonValue value) throws JsonValueException {
085                    try {
086                        return value.isNull() ? null : Pattern.compile(value.asString());
087                    } catch (final PatternSyntaxException pse) {
088                        throw new JsonValueException(value, pse);
089                    }
090                }
091            };
092
093    private static final Function<JsonValue, JsonPointer, JsonValueException> TO_POINTER =
094            new Function<JsonValue, JsonPointer, JsonValueException>() {
095                @Override
096                public JsonPointer apply(JsonValue value) throws JsonValueException {
097                    try {
098                        return value.isNull() ? null : new JsonPointer(value.asString());
099                    } catch (final JsonValueException jve) {
100                        throw jve;
101                    } catch (final JsonException je) {
102                        throw new JsonValueException(value, je);
103                    }
104                }
105            };
106
107    private static final Function<JsonValue, URL, JsonValueException> TO_URL =
108            new Function<JsonValue, URL, JsonValueException>() {
109                @Override
110                public URL apply(JsonValue value) throws JsonValueException {
111                    try {
112                        return value.isNull() ? null : new URL(value.asString());
113                    } catch (final MalformedURLException e) {
114                        throw new JsonValueException(value, e);
115                    }
116                }
117            };
118
119    private static final Function<JsonValue, URI, JsonValueException> TO_URI =
120            new Function<JsonValue, URI, JsonValueException>() {
121                @Override
122                public URI apply(JsonValue value) throws JsonValueException {
123                    try {
124                        return value.isNull() ? null : new URI(value.asString());
125                    } catch (final URISyntaxException use) {
126                        throw new JsonValueException(value, use);
127                    }
128                }
129            };
130
131    private static final Function<JsonValue, UUID, JsonValueException> TO_UUID =
132            new Function<JsonValue, UUID, JsonValueException>() {
133                @Override
134                public UUID apply(JsonValue value) throws JsonValueException {
135                    try {
136                        return value.isNull() ? null : UUID.fromString(value.asString());
137                    } catch (final IllegalArgumentException iae) {
138                        throw new JsonValueException(value, iae);
139                    }
140                }
141            };
142    //@Checkstyle:on
143
144    /**
145     * Returns the JSON string value as a character set used for byte
146     * encoding/decoding. If the JSON value is {@code null}, this function returns
147     * {@code null}.
148     *
149     * @return the character set represented by the string value.
150     * @throws JsonValueException
151     *         if the JSON value is not a string or the character set
152     *         specified is invalid.
153     */
154    public static Function<JsonValue, Charset, JsonValueException> charset() {
155        return TO_CHARSET;
156    }
157
158    /**
159     * Returns the JSON string value as a {@link Duration}. If the JSON value is {@code null}, this method returns
160     * {@code null}.
161     *
162     * @return the duration represented by the string value.
163     * @throws JsonValueException
164     *         if the JSON value is not a string or the duration
165     *         specified is invalid.
166     */
167    public static Function<JsonValue, Duration, JsonValueException> duration() {
168        return TO_DURATION;
169    }
170
171    /**
172     * Returns the JSON string value as an enum constant of the specified enum
173     * type. The string value and enum constants are compared, ignoring case
174     * considerations. If the JSON value is {@code null}, this method returns
175     * {@code null}.
176     *
177     * @param <T>
178     *         the enum type sub-class.
179     * @param type
180     *         the enum type to match constants with the value.
181     * @return the enum constant represented by the string value.
182     * @throws IllegalArgumentException
183     *         if {@code type} does not represent an enum type. or
184     *         if the JSON value does not match any of the enum's constants.
185     * @throws NullPointerException
186     *         if {@code type} is {@code null}.
187     */
188    public static <T extends Enum<T>> Function<JsonValue, T, JsonValueException> enumConstant(final Class<T> type) {
189        return new Function<JsonValue, T, JsonValueException>() {
190            @Override
191            public T apply(JsonValue value) throws JsonValueException {
192                return Utils.asEnum(value.asString(), type);
193            }
194        };
195    }
196
197    /**
198     * Returns the JSON string value as a {@code File} object. If the JSON value
199     * is {@code null}, this method returns {@code null}.
200     *
201     * @return a file represented by the string value.
202     * @throws JsonValueException
203     *         if the JSON value is not a string.
204     */
205    public static Function<JsonValue, File, JsonValueException> file() {
206        return TO_FILE;
207    }
208
209    /**
210     * Returns the JSON string value as a regular expression pattern. If the
211     * JSON value is {@code null}, this method returns {@code null}.
212     *
213     * @return the compiled regular expression pattern.
214     * @throws JsonValueException
215     *         if the pattern is not a string or the value is not a valid
216     *         regular expression pattern.
217     */
218    public static Function<JsonValue, Pattern, JsonValueException> pattern() {
219        return TO_PATTERN;
220    }
221
222    /**
223     * Returns the JSON string value as a JSON pointer. If the JSON value is
224     * {@code null}, this method returns {@code null}.
225     *
226     * @return the JSON pointer represented by the JSON value string.
227     * @throws JsonValueException
228     *         if the JSON value is not a string or valid JSON pointer.
229     */
230    public static Function<JsonValue, JsonPointer, JsonValueException> pointer() {
231        return TO_POINTER;
232    }
233
234    /**
235     * Returns the JSON string value as a uniform resource identifier. If the
236     * JSON value is {@code null}, this method returns {@code null}.
237     *
238     * @return the URI represented by the string value.
239     * @throws JsonValueException
240     *         if the given string violates URI syntax.
241     */
242    public static Function<JsonValue, URI, JsonValueException> uri() {
243        return TO_URI;
244    }
245
246    /**
247     * Returns the JSON string value as a uniform resource locator. If the
248     * JSON value is {@code null}, this method returns {@code null}.
249     *
250     * @return the URL represented by the string value.
251     * @throws JsonValueException
252     *         if the given string violates URL syntax.
253     */
254    public static Function<JsonValue, URL, JsonValueException> url() {
255        return TO_URL;
256    }
257
258    /**
259     * Returns the JSON string value as a universally unique identifier (UUID).
260     * If the JSON value is {@code null}, this method returns {@code null}.
261     *
262     * @return the UUID represented by the JSON value string.
263     * @throws JsonValueException
264     *         if the JSON value is not a string or valid UUID.
265     */
266    public static Function<JsonValue, UUID, JsonValueException> uuid() {
267        return TO_UUID;
268    }
269
270    /**
271     * Returns the JSON value as a {@link List} containing objects whose type
272     * (and value) is specified by a transformation function. If the value is
273     * {@code null}, this method returns {@code null}. It is up to to the
274     * transformation function to transform/enforce source types of the elements
275     * in the Json source collection.  If any of the elements of the list are not of
276     * the appropriate type, or the type-transformation cannot occur,
277     * the exception specified by the transformation function is thrown.
278     *
279     * @param <V>
280     *            the type of elements in this list
281     * @param <E>
282     *            the type of exception thrown by the transformation function
283     * @param transformFunction
284     *            a {@link Function} to transform an element of the JsonValue list
285     *            to the desired type
286     * @return the list value, or {@code null} if no value.
287     * @throws E
288     *             if the JSON value is not a {@code List}, not a {@code Set}, contains an
289     *             unexpected type, or contains an element that cannot be transformed
290     * @throws NullPointerException
291     *             if {@code transformFunction} is {@code null}.
292     */
293    public static <V, E extends Exception> Function<JsonValue, List<V>, E> listOf(
294            final Function<JsonValue, V, E> transformFunction) throws E {
295        return new Function<JsonValue, List<V>, E>() {
296            @Override
297            public List<V> apply(JsonValue value) throws E {
298                if (value.isCollection()) {
299                    final List<V> list = new ArrayList<>(value.size());
300                    for (JsonValue elem : value) {
301                        list.add(elem.as(transformFunction));
302                    }
303                    return list;
304                }
305                return null;
306            }
307        };
308    }
309
310    /**
311     * Returns the JSON value as a {@link Set} containing objects whose type
312     * (and value) is specified by a transformation function. If the value is
313     * {@code null}, this method returns {@code null}. It is up to to the
314     * transformation function to transform/enforce source types of the elements
315     * in the Json source collection.  If called on an object which wraps a List,
316     * this method will drop duplicates performing element comparisons using
317     * equals/hashCode. If any of the elements of the collection are not of
318     * the appropriate type, or the type-transformation cannot occur, the
319     * exception specified by the transformation function is thrown.
320     *
321     * @param <V>
322     *            the type of elements in this set
323     * @param <E>
324     *            the type of exception thrown by the transformation function
325     * @param transformFunction
326     *            a {@link Function} to transform an element of the JsonValue set
327     *            to the desired type
328     * @return the set value, or {@code null} if no value.
329     * @throws E
330     *             if the JSON value is not a {@code Set}, contains an
331     *             unexpected type, or contains an element that cannot be
332     *             transformed
333     * @throws NullPointerException
334     *             if {@code transformFunction} is {@code null}.
335     */
336    public static <V, E extends Exception> Function<JsonValue, Set<V>, E> setOf(
337            final Function<JsonValue, V, E> transformFunction) throws E {
338        return new Function<JsonValue, Set<V>, E>() {
339            @Override
340            public Set<V> apply(JsonValue value) throws E {
341                if (value.isCollection()) {
342                    final Set<V> set = new LinkedHashSet<>(value.size());
343                    for (JsonValue elem : value) {
344                        set.add(elem.as(transformFunction));
345                    }
346                    return set;
347                }
348                return null;
349            }
350        };
351    }
352}