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}