001// *************************************************************************************************************************** 002// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements. See the NOTICE file * 003// * distributed with this work for additional information regarding copyright ownership. The ASF licenses this file * 004// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance * 005// * with the License. You may obtain a copy of the License at * 006// * * 007// * http://www.apache.org/licenses/LICENSE-2.0 * 008// * * 009// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an * 010// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * 011// * specific language governing permissions and limitations under the License. * 012// *************************************************************************************************************************** 013package org.apache.juneau.collections; 014 015import static org.apache.juneau.common.internal.StringUtils.*; 016import static org.apache.juneau.internal.CollectionUtils.*; 017import static org.apache.juneau.internal.ConsumerUtils.*; 018 019import java.io.*; 020import java.lang.reflect.*; 021import java.util.*; 022import java.util.function.*; 023 024import org.apache.juneau.*; 025import org.apache.juneau.common.internal.*; 026import org.apache.juneau.internal.*; 027import org.apache.juneau.json.*; 028import org.apache.juneau.marshaller.*; 029import org.apache.juneau.objecttools.*; 030import org.apache.juneau.parser.*; 031import org.apache.juneau.serializer.*; 032import org.apache.juneau.swap.*; 033 034/** 035 * Java implementation of a JSON object. 036 * 037 * <p> 038 * An extension of {@link LinkedHashMap}, so all methods available in that class are also available to this class. 039 * 040 * <p> 041 * Note that the use of this class is optional for generating JSON. The serializers will accept any objects that implement the 042 * {@link java.util.Map} interface. But this class provides some useful additional functionality when working with 043 * JSON models constructed from Java Collections Framework objects. For example, a constructor is provided for 044 * converting a JSON object string directly into a {@link Map}. It also contains accessor methods for to avoid common 045 * typecasting when accessing elements in a list. 046 * 047 * <h5 class='section'>Example:</h5> 048 * <p class='bjava'> 049 * <jc>// Construct an empty Map</jc> 050 * JsonMap <jv>map</jv> = JsonMap.<jsm>of</jsm>(); 051 * 052 * <jc>// Construct a Map from JSON</jc> 053 * <jv>map</jv> = JsonMap.<jsm>ofJson</jsm>(<js>"{a:'A',b:{c:'C',d:123}}"</js>); 054 * 055 * <jc>// Construct a Map using the append method</jc> 056 * <jv>map</jv> = JsonMap.<jsm>of</jsm>().a(<js>"foo"</js>,<js>"x"</js>).a(<js>"bar"</js>,123).a(<js>"baz"</js>,<jk>true</jk>); 057 * 058 * <jc>// Construct a Map from XML generated by XmlSerializer</jc> 059 * String <jv>xml</jv> = <js>"<object><a type='string'>A</a><b type='object'><c type='string'>C</c><d type='number'>123</d></b></object>"</js>; 060 * <jv>map</jv> = JsonMap.<jsm>of</jsm>(<jv>xml</jv>, XmlParser.<jsf>DEFAULT</jsf>); 061 * 062 * <jc>// Construct a Map from a URL GET parameter string generated by UrlEncodingParser</jc> 063 * String <jv>urlParams</jv> = <js>"?a='A'&b={c:'C',d:123}"</js>; 064 * <jv>map</jv> = JsonMap.<jsm>of</jsm>(<jv>urlParams</jv>, UrlEncodingParser.<jsf>DEFAULT</jsf>); 065 * 066 * <jc>// Construct JSON from JsonMap</jc> 067 * <jv>map</jv> = JsonMap.<jsm>ofJson</jsm>(<js>"{foo:'bar'},{baz:[123,true]}"</js>); 068 * String <jv>json</jv> = <jv>map</jv>.toString(); <jc>// Produces "{foo:'bar'},{baz:[123,true]}"</jc> 069 * <jv>json</jv> = <jv>map</jv>.toString(JsonSerializer.<jsf>DEFAULT</jsf>); <jc>// Equivalent</jc> 070 * <jv>json</jv> = JsonSerializer.<jsf>DEFAULT</jsf>.serialize(<jv>map</jv>); <jc>// Equivalent</jc> 071 * 072 * <jc>// Get a map entry as an Integer</jc> 073 * <jv>map</jv> = JsonMap.<jsm>ofJson</jsm>(<js>"{foo:123}"</js>); 074 * Integer <jv>integer</jv> = <jv>map</jv>.getInt(<js>"foo"</js>); 075 * <jv>integer</jv> = <jv>map</jv>.get(Integer.<jk>class</jk>, <js>"foo"</js>); <jc>// Equivalent</jc> 076 * 077 * <jc>// Get a map entry as a Float</jc> 078 * <jv>map</jv> = JsonMap.<jsm>ofJson</jsm>(<js>"{foo:123}"</js>); 079 * Float <jv>_float</jv> = <jv>map</jv>.getFloat(<js>"foo"</js>); 080 * <jv>_float</jv> = <jv>map</jv>.get(Float.<jk>class</jk>, <js>"foo"</js>); <jc>// Equivalent</jc> 081 * 082 * <jc>// Same as above, except converted to a String</jc> 083 * <jv>map</jv> = JsonMap.<jsm>ofJson</jsm>(<js>"{foo:123}"</js>); 084 * String <jv>string</jv> = <jv>map</jv>.getString(<js>"foo"</js>); <jc>// Returns "123"</jc> 085 * <jv>string</jv> = <jv>map</jv>.get(String.<jk>class</jk>, <js>"foo"</js>); <jc>// Equivalent</jc> 086 * 087 * <jc>// Get one of the entries in the list as a bean (converted to a bean if it isn't already one)</jc> 088 * <jv>map</jv> = JsonMap.<jsm>ofJson</jsm>(<js>"{person:{name:'John Smith',age:45}}"</js>); 089 * Person <jv>person</jv> = <jv>map</jv>.get(Person.<jk>class</jk>, <js>"person"</js>); 090 * 091 * <jc>// Add an inner map</jc> 092 * JsonMap <jv>map1</jv> = JsonMap.<jsm>ofJson</jsm>(<js>"{a:1}"</js>); 093 * JsonMap <jv>map2</jv> = JsonMap.<jsm>ofJson</jsm>(<js>"{b:2}"</js>).setInner(<jv>map1</jv>); 094 * <jk>int</jk> <jv>_int</jv> = <jv>map2</jv>.getInt(<js>"a"</js>); <jc>// a == 1 </jc> 095 * </p> 096 * 097 * <h5 class='section'>Notes:</h5><ul> 098 * <li class='warn'>This class is not thread safe. 099 * </ul> 100 */ 101public class JsonMap extends LinkedHashMap<String,Object> { 102 103 //------------------------------------------------------------------------------------------------------------------ 104 // Static 105 //------------------------------------------------------------------------------------------------------------------ 106 107 private static final long serialVersionUID = 1L; 108 109 /** 110 * An empty read-only JsonMap. 111 * 112 * @serial exclude 113 */ 114 public static final JsonMap EMPTY_MAP = new JsonMap() { 115 116 private static final long serialVersionUID = 1L; 117 118 @Override /* Map */ 119 public Set<Map.Entry<String,Object>> entrySet() { 120 return Collections.<String,Object>emptyMap().entrySet(); 121 } 122 123 @Override /* Map */ 124 public Set<String> keySet() { 125 return Collections.<String,Object>emptyMap().keySet(); 126 } 127 128 @Override /* Map */ 129 public Object put(String key, Object value) { 130 throw new UnsupportedOperationException("Not supported on read-only object."); 131 } 132 133 @Override /* Map */ 134 public Object remove(Object key) { 135 throw new UnsupportedOperationException("Not supported on read-only object."); 136 } 137 138 @Override /* Map */ 139 public Collection<Object> values() { 140 return Collections.emptyMap().values(); 141 } 142 }; 143 144 /** 145 * Construct an empty map. 146 * 147 * @return An empty map. 148 */ 149 public static JsonMap create() { 150 return new JsonMap(); 151 } 152 153 /** 154 * Construct an empty map. 155 * 156 * @return An empty map. 157 */ 158 public static JsonMap filteredMap() { 159 return create().filtered(); 160 } 161 162 /** 163 * Construct a map initialized with the specified map. 164 * 165 * @param values 166 * The map to copy. 167 * <br>Can be <jk>null</jk>. 168 * <br>Keys will be converted to strings using {@link Object#toString()}. 169 * @return A new map or <jk>null</jk> if the map was <jk>null</jk>. 170 */ 171 public static JsonMap of(Map<?,?> values) { 172 return values == null ? null : new JsonMap(values); 173 } 174 175 /** 176 * Construct a map initialized with the specified JSON string. 177 * 178 * @param json 179 * The JSON text to parse. 180 * <br>Can be normal or simplified JSON. 181 * @return A new map or <jk>null</jk> if the string was null. 182 * @throws ParseException Malformed input encountered. 183 */ 184 public static JsonMap ofJson(CharSequence json) throws ParseException { 185 return json == null ? null : new JsonMap(json); 186 } 187 188 /** 189 * Construct a map initialized with the specified string. 190 * 191 * @param in 192 * The input being parsed. 193 * <br>Can be <jk>null</jk>. 194 * @param p 195 * The parser to use to parse the input. 196 * <br>If <jk>null</jk>, uses {@link JsonParser}. 197 * @return A new map or <jk>null</jk> if the input was <jk>null</jk>. 198 * @throws ParseException Malformed input encountered. 199 */ 200 public static JsonMap ofText(CharSequence in, Parser p) throws ParseException { 201 return in == null ? null : new JsonMap(in, p); 202 } 203 204 /** 205 * Construct a map initialized with the specified reader containing JSON. 206 * 207 * @param json 208 * The reader containing JSON text to parse. 209 * <br>Can contain normal or simplified JSON. 210 * @return A new map or <jk>null</jk> if the input was <jk>null</jk>. 211 * @throws ParseException Malformed input encountered. 212 */ 213 public static JsonMap ofJson(Reader json) throws ParseException { 214 return json == null ? null : new JsonMap(json); 215 } 216 217 /** 218 * Construct a map initialized with the specified string. 219 * 220 * @param in 221 * The reader containing the input being parsed. 222 * <br>Can contain normal or simplified JSON. 223 * @param p 224 * The parser to use to parse the input. 225 * <br>If <jk>null</jk>, uses {@link JsonParser}. 226 * @return A new map or <jk>null</jk> if the input was <jk>null</jk>. 227 * @throws ParseException Malformed input encountered. 228 */ 229 public static JsonMap ofText(Reader in, Parser p) throws ParseException { 230 return in == null ? null : new JsonMap(in); 231 } 232 233 /** 234 * Construct a map initialized with the specified key/value pairs. 235 * 236 * <h5 class='section'>Examples:</h5> 237 * <p class='bjava'> 238 * JsonMap <jv>map</jv> = <jk>new</jk> JsonMap(<js>"key1"</js>,<js>"val1"</js>,<js>"key2"</js>,<js>"val2"</js>); 239 * </p> 240 * 241 * @param keyValuePairs A list of key/value pairs to add to this map. 242 * @return A new map, never <jk>null</jk>. 243 */ 244 public static JsonMap of(Object... keyValuePairs) { 245 return new JsonMap(keyValuePairs); 246 } 247 248 /** 249 * Construct a map initialized with the specified key/value pairs. 250 * 251 * <p> 252 * Same as {@link #of(Object...)} but calls {@link #filtered()} on the created map. 253 * 254 * <h5 class='section'>Examples:</h5> 255 * <p class='bjava'> 256 * JsonMap <jv>map</jv> = <jk>new</jk> JsonMap(<js>"key1"</js>,<js>"val1"</js>,<js>"key2"</js>,<js>"val2"</js>); 257 * </p> 258 * 259 * @param keyValuePairs A list of key/value pairs to add to this map. 260 * @return A new map, never <jk>null</jk>. 261 */ 262 public static JsonMap filteredMap(Object... keyValuePairs) { 263 return new JsonMap(keyValuePairs).filtered(); 264 } 265 266 //------------------------------------------------------------------------------------------------------------------ 267 // Instance 268 //------------------------------------------------------------------------------------------------------------------ 269 270 private transient BeanSession session; 271 private Map<String,Object> inner; 272 private transient ObjectRest objectRest; 273 private transient Predicate<Object> valueFilter = x -> true; 274 275 /** 276 * Construct an empty map. 277 */ 278 public JsonMap() {} 279 280 /** 281 * Construct an empty map with the specified bean context. 282 * 283 * @param session The bean session to use for creating beans. 284 */ 285 public JsonMap(BeanSession session) { 286 this.session = session; 287 } 288 289 /** 290 * Construct a map initialized with the specified map. 291 * 292 * @param in 293 * The map to copy. 294 * <br>Can be <jk>null</jk>. 295 * <br>Keys will be converted to strings using {@link Object#toString()}. 296 */ 297 public JsonMap(Map<?,?> in) { 298 this(); 299 if (in != null) 300 in.forEach((k,v) -> put(k.toString(), v)); 301 } 302 303 /** 304 * Construct a map initialized with the specified JSON. 305 * 306 * @param json 307 * The JSON text to parse. 308 * <br>Can be normal or simplified JSON. 309 * @throws ParseException Malformed input encountered. 310 */ 311 public JsonMap(CharSequence json) throws ParseException { 312 this(json, JsonParser.DEFAULT); 313 } 314 315 /** 316 * Construct a map initialized with the specified string. 317 * 318 * @param in 319 * The input being parsed. 320 * <br>Can be <jk>null</jk>. 321 * @param p 322 * The parser to use to parse the input. 323 * <br>If <jk>null</jk>, uses {@link JsonParser}. 324 * @throws ParseException Malformed input encountered. 325 */ 326 public JsonMap(CharSequence in, Parser p) throws ParseException { 327 this(p == null ? BeanContext.DEFAULT_SESSION : p.getBeanContext().getSession()); 328 if (p == null) 329 p = JsonParser.DEFAULT; 330 if (! StringUtils.isEmpty(in)) 331 p.parseIntoMap(in, this, bs().string(), bs().object()); 332 } 333 334 /** 335 * Construct a map initialized with the specified reader containing JSON. 336 * 337 * @param json 338 * The reader containing JSON text to parse. 339 * <br>Can contain normal or simplified JSON. 340 * @throws ParseException Malformed input encountered. 341 */ 342 public JsonMap(Reader json) throws ParseException { 343 parse(json, JsonParser.DEFAULT); 344 } 345 346 /** 347 * Construct a map initialized with the specified string. 348 * 349 * @param in 350 * The reader containing the input being parsed. 351 * <br>Can contain normal or simplified JSON. 352 * @param p 353 * The parser to use to parse the input. 354 * <br>If <jk>null</jk>, uses {@link JsonParser}. 355 * @throws ParseException Malformed input encountered. 356 */ 357 public JsonMap(Reader in, Parser p) throws ParseException { 358 this(p == null ? BeanContext.DEFAULT_SESSION : p.getBeanContext().getSession()); 359 parse(in, p); 360 } 361 362 /** 363 * Construct a map initialized with the specified key/value pairs. 364 * 365 * <h5 class='section'>Examples:</h5> 366 * <p class='bjava'> 367 * JsonMap <jv>map</jv> = <jk>new</jk> JsonMap(<js>"key1"</js>,<js>"val1"</js>,<js>"key2"</js>,<js>"val2"</js>); 368 * </p> 369 * 370 * @param keyValuePairs A list of key/value pairs to add to this map. 371 */ 372 public JsonMap(Object... keyValuePairs) { 373 if (keyValuePairs.length % 2 != 0) 374 throw new RuntimeException("Odd number of parameters passed into JsonMap(Object...)"); 375 for (int i = 0; i < keyValuePairs.length; i+=2) 376 put(stringify(keyValuePairs[i]), keyValuePairs[i+1]); 377 } 378 379 //------------------------------------------------------------------------------------------------------------------ 380 // Initializers 381 //------------------------------------------------------------------------------------------------------------------ 382 383 /** 384 * Set an inner map in this map to allow for chained get calls. 385 * 386 * <p> 387 * If {@link #get(Object)} returns <jk>null</jk>, then {@link #get(Object)} will be called on the inner map. 388 * 389 * <p> 390 * In addition to providing the ability to chain maps, this method also provides the ability to wrap an existing map 391 * inside another map so that you can add entries to the outer map without affecting the values on the inner map. 392 * 393 * <p class='bjava'> 394 * JsonMap <jv>map1</jv> = JsonMap.<jsm>ofJson</jsm>(<js>"{foo:1}"</js>); 395 * JsonMap <jv>map2</jv> = JsonMap.<jsm>of</jsm>().setInner(<jv>map1</jv>); 396 * <jv>map2</jv>.put(<js>"foo"</js>, 2); <jc>// Overwrite the entry</jc> 397 * <jk>int</jk> <jv>foo1</jv> = <jv>map1</jv>.getInt(<js>"foo"</js>); <jc>// foo1 == 1 </jc> 398 * <jk>int</jk> <jv>foo2</jv> = <jv>map2</jv>.getInt(<js>"foo"</js>); <jc>// foo2 == 2 </jc> 399 * </p> 400 * 401 * @param inner 402 * The inner map. 403 * Can be <jk>null</jk> to remove the inner map from an existing map. 404 * @return This object. 405 */ 406 public JsonMap inner(Map<String,Object> inner) { 407 this.inner = inner; 408 return this; 409 } 410 411 /** 412 * Override the default bean session used for converting POJOs. 413 * 414 * <p> 415 * Default is {@link BeanContext#DEFAULT}, which is sufficient in most cases. 416 * 417 * <p> 418 * Useful if you're serializing/parsing beans with transforms defined. 419 * 420 * @param session The new bean session. 421 * @return This object. 422 */ 423 public JsonMap session(BeanSession session) { 424 this.session = session; 425 return this; 426 } 427 428 //------------------------------------------------------------------------------------------------------------------ 429 // Appenders 430 //------------------------------------------------------------------------------------------------------------------ 431 432 /** 433 * Adds an entry to this map. 434 * 435 * @param key The key. 436 * @param value The value. 437 * @return This object. 438 */ 439 public JsonMap append(String key, Object value) { 440 put(key, value); 441 return this; 442 } 443 444 /** 445 * Appends all the entries in the specified map to this map. 446 * 447 * @param values The map to copy. Can be <jk>null</jk>. 448 * @return This object. 449 */ 450 public JsonMap append(Map<String,Object> values) { 451 if (values != null) 452 super.putAll(values); 453 return this; 454 } 455 456 /** 457 * Add if flag is <jk>true</jk>. 458 * 459 * @param flag The flag to check. 460 * @param key The key. 461 * @param value The value. 462 * @return This object. 463 */ 464 public JsonMap appendIf(boolean flag, String key, Object value) { 465 if (flag) 466 append(key, value); 467 return this; 468 } 469 470 /** 471 * Add if predicate matches value. 472 * 473 * @param <T> The value type. 474 * @param test The predicate to match against. 475 * @param key The key. 476 * @param value The value. 477 * @return This object. 478 */ 479 public <T> JsonMap appendIf(Predicate<T> test, String key, T value) { 480 return appendIf(test(test, value), key, value); 481 } 482 483 /** 484 * Adds the first value that matches the specified predicate. 485 * 486 * @param <T> The value types. 487 * @param test The predicate to match against. 488 * @param key The key. 489 * @param values The values to test. 490 * @return This object. 491 */ 492 @SafeVarargs 493 public final <T> JsonMap appendFirst(Predicate<T> test, String key, T...values) { 494 for (T v : values) 495 if (test(test, v)) 496 return append(key, v); 497 return this; 498 } 499 500 /** 501 * Adds a value in this map if the entry does not exist or the current value is <jk>null</jk>. 502 * 503 * @param key The map key. 504 * @param value The value to set if the current value does not exist or is <jk>null</jk>. 505 * @return This object. 506 */ 507 public JsonMap appendIfAbsent(String key, Object value) { 508 return appendIfAbsentIf(x -> true, key, value); 509 } 510 511 /** 512 * Adds a value in this map if the entry does not exist or the current value is <jk>null</jk> and the value matches the specified predicate. 513 * 514 * @param <T> The value type. 515 * @param predicate The predicate to test the value with. 516 * @param key The map key. 517 * @param value The value to set if the current value does not exist or is <jk>null</jk>. 518 * @return This object. 519 */ 520 public <T> JsonMap appendIfAbsentIf(Predicate<T> predicate, String key, T value) { 521 Object o = get(key); 522 if (o == null && predicate.test(value)) 523 put(key, value); 524 return this; 525 } 526 527 /** 528 * Enables filtering based on default values. 529 * 530 * <p> 531 * Any of the following types will be ignored when set as values in this map: 532 * <ul> 533 * <li><jk>null</jk> 534 * <li><jk>false</jk> 535 * <li><c>-1</c> (any Number type) 536 * <li>Empty arrays/collections/maps. 537 * </ul> 538 * @return This object. 539 */ 540 public JsonMap filtered() { 541 return filtered(x -> ! ( 542 x == null 543 || (x instanceof Boolean && x.equals(false)) 544 || (x instanceof Number && ((Number)x).intValue() == -1) 545 || (x.getClass().isArray() && Array.getLength(x) == 0) 546 || (x instanceof Map && ((Map<?,?>)x).isEmpty()) 547 || (x instanceof Collection && ((Collection<?>)x).isEmpty()) 548 )); 549 } 550 551 /** 552 * Enables filtering based on a predicate test. 553 * 554 * <p> 555 * If the predicate evaluates to <jk>false</jk> on values added to this map, the entry will be skipped. 556 * 557 * @param value The value tester predicate. 558 * @return This object. 559 */ 560 public JsonMap filtered(Predicate<Object> value) { 561 valueFilter = value; 562 return this; 563 } 564 565 //------------------------------------------------------------------------------------------------------------------ 566 // Retrievers 567 //------------------------------------------------------------------------------------------------------------------ 568 569 /** 570 * Same as {@link Map#get(Object) get()}, but casts or converts the value to the specified class type. 571 * 572 * <p> 573 * This is the preferred get method for simple types. 574 * 575 * <h5 class='section'>Examples:</h5> 576 * <p class='bjava'> 577 * JsonMap <jv>map</jv> = JsonMap.<jsm>ofJson</jsm>(<js>"..."</js>); 578 * 579 * <jc>// Value converted to a string.</jc> 580 * String <jv>string</jv> = <jv>map</jv>.get(<js>"key1"</js>, String.<jk>class</jk>); 581 * 582 * <jc>// Value converted to a bean.</jc> 583 * MyBean <jv>bean</jv> = <jv>map</jv>.get(<js>"key2"</js>, MyBean.<jk>class</jk>); 584 * 585 * <jc>// Value converted to a bean array.</jc> 586 * MyBean[] <jv>beanArray</jv> = <jv>map</jv>.get(<js>"key3"</js>, MyBean[].<jk>class</jk>); 587 * 588 * <jc>// Value converted to a linked-list of objects.</jc> 589 * List <jv>list</jv> = <jv>map</jv>.get(<js>"key4"</js>, LinkedList.<jk>class</jk>); 590 * 591 * <jc>// Value converted to a map of object keys/values.</jc> 592 * Map <jv>map2</jv> = <jv>map</jv>.get(<js>"key5"</js>, TreeMap.<jk>class</jk>); 593 * </p> 594 * 595 * <p> 596 * See {@link BeanSession#convertToType(Object, ClassMeta)} for the list of valid data conversions. 597 * 598 * @param key The key. 599 * @param <T> The class type returned. 600 * @param type The class type returned. 601 * @return The value, or <jk>null</jk> if the entry doesn't exist. 602 */ 603 public <T> T get(String key, Class<T> type) { 604 return getWithDefault(key, (T)null, type); 605 } 606 607 /** 608 * Same as {@link #get(String,Class)}, but allows for complex data types consisting of collections or maps. 609 * 610 * <p> 611 * The type can be a simple type (e.g. beans, strings, numbers) or parameterized type (collections/maps). 612 * 613 * <h5 class='section'>Examples:</h5> 614 * <p class='bjava'> 615 * JsonMap <jv>map</jv> = JsonMap.<jsm>ofJson</jsm>(<js>"..."</js>); 616 * 617 * <jc>// Value converted to a linked-list of strings.</jc> 618 * List<String> <jv>list1</jv> = <jv>map</jv>.get(<js>"key1"</js>, LinkedList.<jk>class</jk>, String.<jk>class</jk>); 619 * 620 * <jc>// Value converted to a linked-list of beans.</jc> 621 * List<MyBean> <jv>list2</jv> = <jv>map</jv>.get(<js>"key2"</js>, LinkedList.<jk>class</jk>, MyBean.<jk>class</jk>); 622 * 623 * <jc>// Value converted to a linked-list of linked-lists of strings.</jc> 624 * List<List<String>> <jv>list3</jv> = <jv>map</jv>.get(<js>"key3"</js>, LinkedList.<jk>class</jk>, LinkedList.<jk>class</jk>, String.<jk>class</jk>); 625 * 626 * <jc>// Value converted to a map of string keys/values.</jc> 627 * Map<String,String> <jv>map1</jv> = <jv>map</jv>.get(<js>"key4"</js>, TreeMap.<jk>class</jk>, String.<jk>class</jk>, String.<jk>class</jk>); 628 * 629 * <jc>// Value converted to a map containing string keys and values of lists containing beans.</jc> 630 * Map<String,List<MyBean>> <jv>map2</jv> = <jv>map</jv>.get(<js>"key5"</js>, TreeMap.<jk>class</jk>, String.<jk>class</jk>, List.<jk>class</jk>, MyBean.<jk>class</jk>); 631 * </p> 632 * 633 * <p> 634 * <c>Collection</c> classes are assumed to be followed by zero or one objects indicating the element type. 635 * 636 * <p> 637 * <c>Map</c> classes are assumed to be followed by zero or two meta objects indicating the key and value types. 638 * 639 * <p> 640 * The array can be arbitrarily long to indicate arbitrarily complex data structures. 641 * 642 * <p> 643 * See {@link BeanSession#convertToType(Object, ClassMeta)} for the list of valid data conversions. 644 * 645 * <h5 class='section'>Notes:</h5><ul> 646 * <li class='note'> 647 * Use the {@link #get(String, Class)} method instead if you don't need a parameterized map/collection. 648 * </ul> 649 * 650 * @param key The key. 651 * @param <T> The class type returned. 652 * @param type The class type returned. 653 * @param args The class type parameters. 654 * @return The value, or <jk>null</jk> if the entry doesn't exist. 655 */ 656 public <T> T get(String key, Type type, Type...args) { 657 return getWithDefault(key, null, type, args); 658 } 659 660 /** 661 * Same as {@link Map#get(Object) get()}, but returns the default value if the key could not be found. 662 * 663 * @param key The key. 664 * @param def The default value if the entry doesn't exist. 665 * @return The value, or the default value if the entry doesn't exist. 666 */ 667 public Object getWithDefault(String key, Object def) { 668 Object o = get(key); 669 return (o == null ? def : o); 670 } 671 672 /** 673 * Same as {@link #get(String,Class)} but returns a default value if the value does not exist. 674 * 675 * @param key The key. 676 * @param def The default value. Can be <jk>null</jk>. 677 * @param <T> The class type returned. 678 * @param type The class type returned. 679 * @return The value, or <jk>null</jk> if the entry doesn't exist. 680 */ 681 public <T> T getWithDefault(String key, T def, Class<T> type) { 682 return getWithDefault(key, def, type, new Type[0]); 683 } 684 685 /** 686 * Same as {@link #get(String,Type,Type...)} but returns a default value if the value does not exist. 687 * 688 * @param key The key. 689 * @param def The default value. Can be <jk>null</jk>. 690 * @param <T> The class type returned. 691 * @param type The class type returned. 692 * @param args The class type parameters. 693 * @return The value, or <jk>null</jk> if the entry doesn't exist. 694 */ 695 public <T> T getWithDefault(String key, T def, Type type, Type...args) { 696 Object o = get(key); 697 if (o == null) 698 return def; 699 T t = bs().convertToType(o, type, args); 700 return t == null ? def : t; 701 } 702 703 /** 704 * Searches for the specified key in this map ignoring case. 705 * 706 * @param key 707 * The key to search for. 708 * For performance reasons, it's preferable that the key be all lowercase. 709 * @return The key, or <jk>null</jk> if map does not contain this key. 710 */ 711 public String findKeyIgnoreCase(String key) { 712 for (String k : keySet()) 713 if (key.equalsIgnoreCase(k)) 714 return k; 715 return null; 716 } 717 718 /** 719 * Same as {@link Map#get(Object) get()}, but converts the raw value to the specified class type using the specified 720 * POJO swap. 721 * 722 * @param key The key. 723 * @param objectSwap The swap class used to convert the raw type to a transformed type. 724 * @param <T> The transformed class type. 725 * @return The value, or <jk>null</jk> if the entry doesn't exist. 726 * @throws ParseException Malformed input encountered. 727 */ 728 @SuppressWarnings({ "rawtypes", "unchecked" }) 729 public <T> T getSwapped(String key, ObjectSwap<T,?> objectSwap) throws ParseException { 730 try { 731 Object o = super.get(key); 732 if (o == null) 733 return null; 734 ObjectSwap swap = objectSwap; 735 return (T) swap.unswap(bs(), o, null); 736 } catch (ParseException e) { 737 throw e; 738 } catch (Exception e) { 739 throw new ParseException(e); 740 } 741 } 742 743 /** 744 * Returns the value for the first key in the list that has an entry in this map. 745 * 746 * @param keys The keys to look up in order. 747 * @return The value of the first entry whose key exists, or <jk>null</jk> if none of the keys exist in this map. 748 */ 749 public Object find(String...keys) { 750 for (String key : keys) 751 if (containsKey(key)) 752 return get(key); 753 return null; 754 } 755 756 /** 757 * Returns the value for the first key in the list that has an entry in this map. 758 * 759 * <p> 760 * Casts or converts the value to the specified class type. 761 * 762 * <p> 763 * See {@link BeanSession#convertToType(Object, ClassMeta)} for the list of valid data conversions. 764 * 765 * @param type The class type to convert the value to. 766 * @param <T> The class type to convert the value to. 767 * @param keys The keys to look up in order. 768 * @return The value of the first entry whose key exists, or <jk>null</jk> if none of the keys exist in this map. 769 */ 770 public <T> T find(Class<T> type, String...keys) { 771 for (String key : keys) 772 if (containsKey(key)) 773 return get(key, type); 774 return null; 775 } 776 777 /** 778 * Returns the specified entry value converted to a {@link String}. 779 * 780 * <p> 781 * Shortcut for <code>get(key, String.<jk>class</jk>)</code>. 782 * 783 * @param key The key. 784 * @return The converted value, or <jk>null</jk> if the map contains no mapping for this key. 785 */ 786 public String getString(String key) { 787 return get(key, String.class); 788 } 789 790 /** 791 * Returns the specified entry value converted to a {@link String}. 792 * 793 * <p> 794 * Shortcut for <code>get(key, String[].<jk>class</jk>)</code>. 795 * 796 * @param key The key. 797 * @return The converted value, or <jk>null</jk> if the map contains no mapping for this key. 798 */ 799 public String[] getStringArray(String key) { 800 return getStringArray(key, null); 801 } 802 803 /** 804 * Same as {@link #getStringArray(String)} but returns a default value if the value cannot be found. 805 * 806 * @param key The map key. 807 * @param def The default value if value is not found. 808 * @return The value converted to a string array. 809 */ 810 public String[] getStringArray(String key, String[] def) { 811 Object s = get(key, Object.class); 812 if (s == null) 813 return def; 814 String[] r = null; 815 if (s instanceof Collection) 816 r = ArrayUtils.toStringArray((Collection<?>)s); 817 else if (s instanceof String[]) 818 r = (String[])s; 819 else if (s instanceof Object[]) 820 r = ArrayUtils.toStringArray(alist((Object[])s)); 821 else 822 r = split(stringify(s)); 823 return (r.length == 0 ? def : r); 824 } 825 826 /** 827 * Returns the specified entry value converted to a {@link String}. 828 * 829 * <p> 830 * Shortcut for <code>getWithDefault(key, defVal, String.<jk>class</jk>)</code>. 831 * 832 * @param key The key. 833 * @param defVal The default value if the map doesn't contain the specified mapping. 834 * @return The converted value, or the default value if the map contains no mapping for this key. 835 */ 836 public String getString(String key, String defVal) { 837 return getWithDefault(key, defVal, String.class); 838 } 839 840 /** 841 * Returns the specified entry value converted to an {@link Integer}. 842 * 843 * <p> 844 * Shortcut for <code>get(key, Integer.<jk>class</jk>)</code>. 845 * 846 * @param key The key. 847 * @return The converted value, or <jk>null</jk> if the map contains no mapping for this key. 848 * @throws InvalidDataConversionException If value cannot be converted. 849 */ 850 public Integer getInt(String key) { 851 return get(key, Integer.class); 852 } 853 854 /** 855 * Returns the specified entry value converted to an {@link Integer}. 856 * 857 * <p> 858 * Shortcut for <code>getWithDefault(key, defVal, Integer.<jk>class</jk>)</code>. 859 * 860 * @param key The key. 861 * @param defVal The default value if the map doesn't contain the specified mapping. 862 * @return The converted value, or the default value if the map contains no mapping for this key. 863 * @throws InvalidDataConversionException If value cannot be converted. 864 */ 865 public Integer getInt(String key, Integer defVal) { 866 return getWithDefault(key, defVal, Integer.class); 867 } 868 869 /** 870 * Returns the specified entry value converted to a {@link Long}. 871 * 872 * <p> 873 * Shortcut for <code>get(key, Long.<jk>class</jk>)</code>. 874 * 875 * @param key The key. 876 * @return The converted value, or <jk>null</jk> if the map contains no mapping for this key. 877 * @throws InvalidDataConversionException If value cannot be converted. 878 */ 879 public Long getLong(String key) { 880 return get(key, Long.class); 881 } 882 883 /** 884 * Returns the specified entry value converted to a {@link Long}. 885 * 886 * <p> 887 * Shortcut for <code>getWithDefault(key, defVal, Long.<jk>class</jk>)</code>. 888 * 889 * @param key The key. 890 * @param defVal The default value if the map doesn't contain the specified mapping. 891 * @return The converted value, or the default value if the map contains no mapping for this key. 892 * @throws InvalidDataConversionException If value cannot be converted. 893 */ 894 public Long getLong(String key, Long defVal) { 895 return getWithDefault(key, defVal, Long.class); 896 } 897 898 /** 899 * Returns the specified entry value converted to a {@link Boolean}. 900 * 901 * <p> 902 * Shortcut for <code>get(key, Boolean.<jk>class</jk>)</code>. 903 * 904 * @param key The key. 905 * @return The converted value, or <jk>null</jk> if the map contains no mapping for this key. 906 * @throws InvalidDataConversionException If value cannot be converted. 907 */ 908 public Boolean getBoolean(String key) { 909 return get(key, Boolean.class); 910 } 911 912 /** 913 * Returns the specified entry value converted to a {@link Boolean}. 914 * 915 * <p> 916 * Shortcut for <code>getWithDefault(key, defVal, Boolean.<jk>class</jk>)</code>. 917 * 918 * @param key The key. 919 * @param defVal The default value if the map doesn't contain the specified mapping. 920 * @return The converted value, or the default value if the map contains no mapping for this key. 921 * @throws InvalidDataConversionException If value cannot be converted. 922 */ 923 public Boolean getBoolean(String key, Boolean defVal) { 924 return getWithDefault(key, defVal, Boolean.class); 925 } 926 927 /** 928 * Returns the specified entry value converted to a {@link Map}. 929 * 930 * <p> 931 * Shortcut for <code>get(key, JsonMap.<jk>class</jk>)</code>. 932 * 933 * @param key The key. 934 * @return The converted value, or <jk>null</jk> if the map contains no mapping for this key. 935 * @throws InvalidDataConversionException If value cannot be converted. 936 */ 937 public JsonMap getMap(String key) { 938 return get(key, JsonMap.class); 939 } 940 941 /** 942 * Returns the specified entry value converted to a {@link JsonMap}. 943 * 944 * <p> 945 * Shortcut for <code>getWithDefault(key, defVal, JsonMap.<jk>class</jk>)</code>. 946 * 947 * @param key The key. 948 * @param defVal The default value if the map doesn't contain the specified mapping. 949 * @return The converted value, or the default value if the map contains no mapping for this key. 950 * @throws InvalidDataConversionException If value cannot be converted. 951 */ 952 public JsonMap getMap(String key, JsonMap defVal) { 953 return getWithDefault(key, defVal, JsonMap.class); 954 } 955 956 /** 957 * Same as {@link #getMap(String)} but creates a new empty {@link JsonMap} if it doesn't already exist. 958 * 959 * @param key The key. 960 * @param createIfNotExists If mapping doesn't already exist, create one with an empty {@link JsonMap}. 961 * @return The converted value, or an empty value if the map contains no mapping for this key. 962 * @throws InvalidDataConversionException If value cannot be converted. 963 */ 964 public JsonMap getMap(String key, boolean createIfNotExists) { 965 JsonMap m = getWithDefault(key, null, JsonMap.class); 966 if (m == null && createIfNotExists) { 967 m = new JsonMap(); 968 put(key, m); 969 } 970 return m; 971 } 972 973 /** 974 * Same as {@link #getMap(String, JsonMap)} except converts the keys and values to the specified types. 975 * 976 * @param <K> The key type. 977 * @param <V> The value type. 978 * @param key The key. 979 * @param keyType The key type class. 980 * @param valType The value type class. 981 * @param def The default value if the map doesn't contain the specified mapping. 982 * @return The converted value, or the default value if the map contains no mapping for this key. 983 * @throws InvalidDataConversionException If value cannot be converted. 984 */ 985 public <K,V> Map<K,V> getMap(String key, Class<K> keyType, Class<V> valType, Map<K,V> def) { 986 Object o = get(key); 987 if (o == null) 988 return def; 989 return bs().convertToType(o, Map.class, keyType, valType); 990 } 991 992 /** 993 * Returns the specified entry value converted to a {@link JsonList}. 994 * 995 * <p> 996 * Shortcut for <code>get(key, JsonList.<jk>class</jk>)</code>. 997 * 998 * @param key The key. 999 * @return The converted value, or <jk>null</jk> if the map contains no mapping for this key. 1000 * @throws InvalidDataConversionException If value cannot be converted. 1001 */ 1002 public JsonList getList(String key) { 1003 return get(key, JsonList.class); 1004 } 1005 1006 /** 1007 * Returns the specified entry value converted to a {@link JsonList}. 1008 * 1009 * <p> 1010 * Shortcut for <code>getWithDefault(key, defVal, JsonList.<jk>class</jk>)</code>. 1011 * 1012 * @param key The key. 1013 * @param defVal The default value if the map doesn't contain the specified mapping. 1014 * @return The converted value, or the default value if the map contains no mapping for this key. 1015 * @throws InvalidDataConversionException If value cannot be converted. 1016 */ 1017 public JsonList getList(String key, JsonList defVal) { 1018 return getWithDefault(key, defVal, JsonList.class); 1019 } 1020 1021 /** 1022 * Same as {@link #getList(String)} but creates a new empty {@link JsonList} if it doesn't already exist. 1023 * 1024 * @param key The key. 1025 * @param createIfNotExists If mapping doesn't already exist, create one with an empty {@link JsonList}. 1026 * @return The converted value, or an empty value if the map contains no mapping for this key. 1027 * @throws InvalidDataConversionException If value cannot be converted. 1028 */ 1029 public JsonList getList(String key, boolean createIfNotExists) { 1030 JsonList m = getWithDefault(key, null, JsonList.class); 1031 if (m == null && createIfNotExists) { 1032 m = new JsonList(); 1033 put(key, m); 1034 } 1035 return m; 1036 } 1037 1038 /** 1039 * Same as {@link #getList(String, JsonList)} except converts the elements to the specified types. 1040 * 1041 * @param <E> The element type. 1042 * @param key The key. 1043 * @param elementType The element type class. 1044 * @param def The default value if the map doesn't contain the specified mapping. 1045 * @return The converted value, or the default value if the map contains no mapping for this key. 1046 * @throws InvalidDataConversionException If value cannot be converted. 1047 */ 1048 public <E> List<E> getList(String key, Class<E> elementType, List<E> def) { 1049 Object o = get(key); 1050 if (o == null) 1051 return def; 1052 return bs().convertToType(o, List.class, elementType); 1053 } 1054 1055 /** 1056 * Returns the first entry that exists converted to a {@link String}. 1057 * 1058 * <p> 1059 * Shortcut for <code>find(String.<jk>class</jk>, keys)</code>. 1060 * 1061 * @param keys The list of keys to look for. 1062 * @return 1063 * The converted value of the first key in the list that has an entry in this map, or <jk>null</jk> if the map 1064 * contains no mapping for any of the keys. 1065 */ 1066 public String findString(String... keys) { 1067 return find(String.class, keys); 1068 } 1069 1070 /** 1071 * Returns the first entry that exists converted to an {@link Integer}. 1072 * 1073 * <p> 1074 * Shortcut for <code>find(Integer.<jk>class</jk>, keys)</code>. 1075 * 1076 * @param keys The list of keys to look for. 1077 * @return 1078 * The converted value of the first key in the list that has an entry in this map, or <jk>null</jk> if the map 1079 * contains no mapping for any of the keys. 1080 * @throws InvalidDataConversionException If value cannot be converted. 1081 */ 1082 public Integer findInt(String... keys) { 1083 return find(Integer.class, keys); 1084 } 1085 1086 /** 1087 * Returns the first entry that exists converted to a {@link Long}. 1088 * 1089 * <p> 1090 * Shortcut for <code>find(Long.<jk>class</jk>, keys)</code>. 1091 * 1092 * @param keys The list of keys to look for. 1093 * @return 1094 * The converted value of the first key in the list that has an entry in this map, or <jk>null</jk> if the map 1095 * contains no mapping for any of the keys. 1096 * @throws InvalidDataConversionException If value cannot be converted. 1097 */ 1098 public Long findLong(String... keys) { 1099 return find(Long.class, keys); 1100 } 1101 1102 /** 1103 * Returns the first entry that exists converted to a {@link Boolean}. 1104 * 1105 * <p> 1106 * Shortcut for <code>find(Boolean.<jk>class</jk>, keys)</code>. 1107 * 1108 * @param keys The list of keys to look for. 1109 * @return 1110 * The converted value of the first key in the list that has an entry in this map, or <jk>null</jk> if the map 1111 * contains no mapping for any of the keys. 1112 * @throws InvalidDataConversionException If value cannot be converted. 1113 */ 1114 public Boolean findBoolean(String... keys) { 1115 return find(Boolean.class, keys); 1116 } 1117 1118 /** 1119 * Returns the first entry that exists converted to a {@link JsonMap}. 1120 * 1121 * <p> 1122 * Shortcut for <code>find(JsonMap.<jk>class</jk>, keys)</code>. 1123 * 1124 * @param keys The list of keys to look for. 1125 * @return 1126 * The converted value of the first key in the list that has an entry in this map, or <jk>null</jk> if the map 1127 * contains no mapping for any of the keys. 1128 * @throws InvalidDataConversionException If value cannot be converted. 1129 */ 1130 public JsonMap findMap(String... keys) { 1131 return find(JsonMap.class, keys); 1132 } 1133 1134 /** 1135 * Returns the first entry that exists converted to a {@link JsonList}. 1136 * 1137 * <p> 1138 * Shortcut for <code>find(JsonList.<jk>class</jk>, keys)</code>. 1139 * 1140 * @param keys The list of keys to look for. 1141 * @return 1142 * The converted value of the first key in the list that has an entry in this map, or <jk>null</jk> if the map 1143 * contains no mapping for any of the keys. 1144 * @throws InvalidDataConversionException If value cannot be converted. 1145 */ 1146 public JsonList findList(String... keys) { 1147 return find(JsonList.class, keys); 1148 } 1149 1150 /** 1151 * Returns the first key in the map. 1152 * 1153 * @return The first key in the map, or <jk>null</jk> if the map is empty. 1154 */ 1155 public String getFirstKey() { 1156 return isEmpty() ? null : keySet().iterator().next(); 1157 } 1158 1159 /** 1160 * Returns the class type of the object at the specified index. 1161 * 1162 * @param key The key into this map. 1163 * @return 1164 * The data type of the object at the specified key, or <jk>null</jk> if the value is null or does not exist. 1165 */ 1166 public ClassMeta<?> getClassMeta(String key) { 1167 return bs().getClassMetaForObject(get(key)); 1168 } 1169 1170 /** 1171 * Equivalent to calling <c>get(class,key,def)</c> followed by <c>remove(key);</c> 1172 * @param key The key. 1173 * @param defVal The default value if the map doesn't contain the specified mapping. 1174 * @param type The class type. 1175 * 1176 * @param <T> The class type. 1177 * @return The converted value, or the default value if the map contains no mapping for this key. 1178 * @throws InvalidDataConversionException If value cannot be converted. 1179 */ 1180 public <T> T removeWithDefault(String key, T defVal, Class<T> type) { 1181 T t = getWithDefault(key, defVal, type); 1182 remove(key); 1183 return t; 1184 } 1185 1186 /** 1187 * Equivalent to calling <code>removeWithDefault(key,<jk>null</jk>,String.<jk>class</jk>)</code>. 1188 * 1189 * @param key The key. 1190 * @return The converted value, or <jk>null</jk> if the map contains no mapping for this key. 1191 * @throws InvalidDataConversionException If value cannot be converted. 1192 */ 1193 public String removeString(String key) { 1194 return removeString(key, null); 1195 } 1196 1197 /** 1198 * Equivalent to calling <code>removeWithDefault(key,def,String.<jk>class</jk>)</code>. 1199 * 1200 * @param key The key. 1201 * @param def The default value if the map doesn't contain the specified mapping. 1202 * @return The converted value, or the default value if the map contains no mapping for this key. 1203 * @throws InvalidDataConversionException If value cannot be converted. 1204 */ 1205 public String removeString(String key, String def) { 1206 return removeWithDefault(key, def, String.class); 1207 } 1208 1209 /** 1210 * Equivalent to calling <code>removeWithDefault(key,<jk>null</jk>,Integer.<jk>class</jk>)</code>. 1211 * 1212 * @param key The key. 1213 * @return The converted value, or <jk>null</jk> if the map contains no mapping for this key. 1214 * @throws InvalidDataConversionException If value cannot be converted. 1215 */ 1216 public Integer removeInt(String key) { 1217 return removeInt(key, null); 1218 } 1219 1220 /** 1221 * Equivalent to calling <code>removeWithDefault(key,def,Integer.<jk>class</jk>)</code>. 1222 * 1223 * @param key The key. 1224 * @param def The default value if the map doesn't contain the specified mapping. 1225 * @return The converted value, or the default value if the map contains no mapping for this key. 1226 * @throws InvalidDataConversionException If value cannot be converted. 1227 */ 1228 public Integer removeInt(String key, Integer def) { 1229 return removeWithDefault(key, def, Integer.class); 1230 } 1231 1232 /** 1233 * Equivalent to calling <code>removeWithDefault(key,<jk>null</jk>,Boolean.<jk>class</jk>)</code>. 1234 * 1235 * @param key The key. 1236 * @return The converted value, or <jk>null</jk> if the map contains no mapping for this key. 1237 * @throws InvalidDataConversionException If value cannot be converted. 1238 */ 1239 public Boolean removeBoolean(String key) { 1240 return removeBoolean(key, null); 1241 } 1242 1243 /** 1244 * Equivalent to calling <code>removeWithDefault(key,def,Boolean.<jk>class</jk>)</code>. 1245 * 1246 * @param key The key. 1247 * @param def The default value if the map doesn't contain the specified mapping. 1248 * @return The converted value, or the default value if the map contains no mapping for this key. 1249 * @throws InvalidDataConversionException If value cannot be converted. 1250 */ 1251 public Boolean removeBoolean(String key, Boolean def) { 1252 return removeWithDefault(key, def, Boolean.class); 1253 } 1254 1255 /** 1256 * Convenience method for removing several keys at once. 1257 * 1258 * @param keys The list of keys to remove. 1259 */ 1260 public void removeAll(Collection<String> keys) { 1261 keys.forEach(x -> remove(x)); 1262 } 1263 1264 /** 1265 * Convenience method for removing several keys at once. 1266 * 1267 * @param keys The list of keys to remove. 1268 */ 1269 public void removeAll(String... keys) { 1270 for (String k : keys) 1271 remove(k); 1272 } 1273 1274 /** 1275 * The opposite of {@link #removeAll(String...)}. 1276 * 1277 * <p> 1278 * Discards all keys from this map that aren't in the specified list. 1279 * 1280 * @param keys The keys to keep. 1281 * @return This map. 1282 */ 1283 public JsonMap keepAll(String...keys) { 1284 for (Iterator<String> i = keySet().iterator(); i.hasNext();) { 1285 boolean remove = true; 1286 String key = i.next(); 1287 for (String k : keys) { 1288 if (k.equals(key)) { 1289 remove = false; 1290 break; 1291 } 1292 } 1293 if (remove) 1294 i.remove(); 1295 } 1296 return this; 1297 } 1298 1299 /** 1300 * Returns <jk>true</jk> if the map contains the specified entry and the value is not null nor an empty string. 1301 * 1302 * <p> 1303 * Always returns <jk>false</jk> if the value is not a {@link CharSequence}. 1304 * 1305 * @param key The key. 1306 * @return <jk>true</jk> if the map contains the specified entry and the value is not null nor an empty string. 1307 */ 1308 public boolean containsKeyNotEmpty(String key) { 1309 Object val = get(key); 1310 if (val == null) 1311 return false; 1312 if (val instanceof CharSequence) 1313 return ! StringUtils.isEmpty((CharSequence)val); 1314 return false; 1315 } 1316 1317 /** 1318 * Returns <jk>true</jk> if this map contains the specified key, ignoring the inner map if it exists. 1319 * 1320 * @param key The key to look up. 1321 * @return <jk>true</jk> if this map contains the specified key. 1322 */ 1323 public boolean containsOuterKey(Object key) { 1324 return super.containsKey(key); 1325 } 1326 1327 /** 1328 * Returns a copy of this <c>JsonMap</c> with only the specified keys. 1329 * 1330 * @param keys The keys of the entries to copy. 1331 * @return A new map with just the keys and values from this map. 1332 */ 1333 public JsonMap include(String...keys) { 1334 JsonMap m2 = new JsonMap(); 1335 this.forEach((k,v) -> { 1336 for (String kk : keys) 1337 if (kk.equals(k)) 1338 m2.put(kk, v); 1339 }); 1340 return m2; 1341 } 1342 1343 /** 1344 * Returns a copy of this <c>JsonMap</c> without the specified keys. 1345 * 1346 * @param keys The keys of the entries not to copy. 1347 * @return A new map without the keys and values from this map. 1348 */ 1349 public JsonMap exclude(String...keys) { 1350 JsonMap m2 = new JsonMap(); 1351 this.forEach((k,v) -> { 1352 boolean exclude = false; 1353 for (String kk : keys) 1354 if (kk.equals(k)) 1355 exclude = true; 1356 if (! exclude) 1357 m2.put(k, v); 1358 }); 1359 return m2; 1360 } 1361 1362 /** 1363 * Converts this map into an object of the specified type. 1364 * 1365 * <p> 1366 * If this map contains a <js>"_type"</js> entry, it must be the same as or a subclass of the <c>type</c>. 1367 * 1368 * @param <T> The class type to convert this map object to. 1369 * @param type The class type to convert this map object to. 1370 * @return The new object. 1371 * @throws ClassCastException 1372 * If the <js>"_type"</js> entry is present and not assignable from <c>type</c> 1373 */ 1374 @SuppressWarnings("unchecked") 1375 public <T> T cast(Class<T> type) { 1376 BeanSession bs = bs(); 1377 ClassMeta<?> c2 = bs.getClassMeta(type); 1378 String typePropertyName = bs.getBeanTypePropertyName(c2); 1379 ClassMeta<?> c1 = bs.getBeanRegistry().getClassMeta((String)get(typePropertyName)); 1380 ClassMeta<?> c = c1 == null ? c2 : narrowClassMeta(c1, c2); 1381 if (c.isObject()) 1382 return (T)this; 1383 return (T)cast2(c); 1384 } 1385 1386 /** 1387 * Same as {@link #cast(Class)}, except allows you to specify a {@link ClassMeta} parameter. 1388 * 1389 * @param <T> The class type to convert this map object to. 1390 * @param cm The class type to convert this map object to. 1391 * @return The new object. 1392 * @throws ClassCastException 1393 * If the <js>"_type"</js> entry is present and not assignable from <c>type</c> 1394 */ 1395 @SuppressWarnings({"unchecked"}) 1396 public <T> T cast(ClassMeta<T> cm) { 1397 BeanSession bs = bs(); 1398 ClassMeta<?> c1 = bs.getBeanRegistry().getClassMeta((String)get(bs.getBeanTypePropertyName(cm))); 1399 ClassMeta<?> c = narrowClassMeta(c1, cm); 1400 return (T)cast2(c); 1401 } 1402 1403 //------------------------------------------------------------------------------------------------------------------ 1404 // POJO REST methods. 1405 //------------------------------------------------------------------------------------------------------------------ 1406 1407 /** 1408 * Same as {@link #get(String,Class) get(String,Class)}, but the key is a slash-delimited path used to traverse 1409 * entries in this POJO. 1410 * 1411 * <p> 1412 * For example, the following code is equivalent: 1413 * </p> 1414 * <p class='bjava'> 1415 * JsonMap <jv>map</jv> = JsonMap.<jsm>ofJson</jsm>(<js>"..."</js>); 1416 * 1417 * <jc>// Long way</jc> 1418 * <jk>long</jk> <jv>_long</jv> = <jv>map</jv>.getMap(<js>"foo"</js>).getList(<js>"bar"</js>).getMap(<js>"0"</js>).getLong(<js>"baz"</js>); 1419 * 1420 * <jc>// Using this method</jc> 1421 * <jk>long</jk> <jv>_long</jv> = <jv>map</jv>.getAt(<js>"foo/bar/0/baz"</js>, <jk>long</jk>.<jk>class</jk>); 1422 * </p> 1423 * 1424 * <p> 1425 * This method uses the {@link ObjectRest} class to perform the lookup, so the map can contain any of the various 1426 * class types that the {@link ObjectRest} class supports (e.g. beans, collections, arrays). 1427 * 1428 * @param path The path to the entry. 1429 * @param type The class type. 1430 * 1431 * @param <T> The class type. 1432 * @return The value, or <jk>null</jk> if the entry doesn't exist. 1433 */ 1434 public <T> T getAt(String path, Class<T> type) { 1435 return getObjectRest().get(path, type); 1436 } 1437 1438 /** 1439 * Same as {@link #getAt(String,Class)}, but allows for conversion to complex maps and collections. 1440 * 1441 * <p> 1442 * This method uses the {@link ObjectRest} class to perform the lookup, so the map can contain any of the various 1443 * class types that the {@link ObjectRest} class supports (e.g. beans, collections, arrays). 1444 * 1445 * @param path The path to the entry. 1446 * @param type The class type. 1447 * @param args The class parameter types. 1448 * 1449 * @param <T> The class type. 1450 * @return The value, or <jk>null</jk> if the entry doesn't exist. 1451 */ 1452 public <T> T getAt(String path, Type type, Type...args) { 1453 return getObjectRest().get(path, type, args); 1454 } 1455 1456 /** 1457 * Same as <c>put(String,Object)</c>, but the key is a slash-delimited path used to traverse entries in this 1458 * POJO. 1459 * 1460 * <p> 1461 * For example, the following code is equivalent: 1462 * </p> 1463 * <p class='bjava'> 1464 * JsonMap <jv>map</jv> = JsonMap.<jsm>ofJson</jsm>(<js>"..."</js>); 1465 * 1466 * <jc>// Long way</jc> 1467 * <jv>map</jv>.getMap(<js>"foo"</js>).getList(<js>"bar"</js>).getMap(<js>"0"</js>).put(<js>"baz"</js>, 123); 1468 * 1469 * <jc>// Using this method</jc> 1470 * <jv>map</jv>.putAt(<js>"foo/bar/0/baz"</js>, 123); 1471 * </p> 1472 * 1473 * <p> 1474 * This method uses the {@link ObjectRest} class to perform the lookup, so the map can contain any of the various 1475 * class types that the {@link ObjectRest} class supports (e.g. beans, collections, arrays). 1476 * 1477 * @param path The path to the entry. 1478 * @param o The new value. 1479 * @return The previous value, or <jk>null</jk> if the entry doesn't exist. 1480 */ 1481 public Object putAt(String path, Object o) { 1482 return getObjectRest().put(path, o); 1483 } 1484 1485 /** 1486 * Similar to {@link #putAt(String,Object) putAt(String,Object)}, but used to append to collections and arrays. 1487 * 1488 * <p> 1489 * For example, the following code is equivalent: 1490 * </p> 1491 * <p class='bjava'> 1492 * JsonMap <jv>map</jv> = JsonMap.<jsm>ofJson</jsm>(<js>"..."</js>); 1493 * 1494 * <jc>// Long way</jc> 1495 * <jv>map</jv>.getMap(<js>"foo"</js>).getList(<js>"bar"</js>).append(123); 1496 * 1497 * <jc>// Using this method</jc> 1498 * <jv>map</jv>.postAt(<js>"foo/bar"</js>, 123); 1499 * </p> 1500 * 1501 * <p> 1502 * This method uses the {@link ObjectRest} class to perform the lookup, so the map can contain any of the various 1503 * class types that the {@link ObjectRest} class supports (e.g. beans, collections, arrays). 1504 * 1505 * @param path The path to the entry. 1506 * @param o The new value. 1507 * @return The previous value, or <jk>null</jk> if the entry doesn't exist. 1508 */ 1509 public Object postAt(String path, Object o) { 1510 return getObjectRest().post(path, o); 1511 } 1512 1513 /** 1514 * Similar to {@link #remove(Object) remove(Object)}, but the key is a slash-delimited path used to traverse entries 1515 * in this POJO. 1516 * 1517 * <p> 1518 * For example, the following code is equivalent: 1519 * </p> 1520 * <p class='bjava'> 1521 * JsonMap <jv>map</jv> = JsonMap.<jsm>ofJson</jsm>(<js>"..."</js>); 1522 * 1523 * <jc>// Long way</jc> 1524 * <jv>map</jv>.getMap(<js>"foo"</js>).getList(<js>"bar"</js>).getMap(0).remove(<js>"baz"</js>); 1525 * 1526 * <jc>// Using this method</jc> 1527 * <jv>map</jv>.deleteAt(<js>"foo/bar/0/baz"</js>); 1528 * </p> 1529 * 1530 * <p> 1531 * This method uses the {@link ObjectRest} class to perform the lookup, so the map can contain any of the various 1532 * class types that the {@link ObjectRest} class supports (e.g. beans, collections, arrays). 1533 * 1534 * @param path The path to the entry. 1535 * @return The previous value, or <jk>null</jk> if the entry doesn't exist. 1536 */ 1537 public Object deleteAt(String path) { 1538 return getObjectRest().delete(path); 1539 } 1540 1541 //------------------------------------------------------------------------------------------------------------------ 1542 // Other methods 1543 //------------------------------------------------------------------------------------------------------------------ 1544 1545 @Override 1546 public Object put(String key, Object value) { 1547 if (valueFilter.test(value)) 1548 super.put(key, value); 1549 return null; 1550 } 1551 1552 /** 1553 * Returns the {@link BeanSession} currently associated with this map. 1554 * 1555 * @return The {@link BeanSession} currently associated with this map. 1556 */ 1557 public BeanSession getBeanSession() { 1558 return session; 1559 } 1560 1561 /** 1562 * Sets the {@link BeanSession} currently associated with this map. 1563 * 1564 * @param value The {@link BeanSession} currently associated with this map. 1565 * @return This object. 1566 */ 1567 public JsonMap setBeanSession(BeanSession value) { 1568 this.session = value; 1569 return this; 1570 } 1571 1572 /** 1573 * Convenience method for inserting JSON directly into an attribute on this object. 1574 * 1575 * <p> 1576 * The JSON text can be an object (i.e. <js>"{...}"</js>) or an array (i.e. <js>"[...]"</js>). 1577 * 1578 * @param key The key. 1579 * @param json The JSON text that will be parsed into an Object and then inserted into this map. 1580 * @throws ParseException Malformed input encountered. 1581 */ 1582 public void putJson(String key, String json) throws ParseException { 1583 this.put(key, JsonParser.DEFAULT.parse(json, Object.class)); 1584 } 1585 1586 /** 1587 * Serialize this object into a string using the specified serializer. 1588 * 1589 * @param serializer The serializer to use to convert this object to a string. 1590 * @return This object serialized as a string. 1591 */ 1592 public String asString(WriterSerializer serializer) { 1593 return serializer.toString(this); 1594 } 1595 1596 /** 1597 * Serialize this object to Simplified JSON using {@link Json5Serializer#DEFAULT}. 1598 * 1599 * @return This object serialized as a string. 1600 */ 1601 public String asString() { 1602 if (Json5Serializer.DEFAULT == null) 1603 return stringify(this); 1604 return Json5Serializer.DEFAULT.toString(this); 1605 } 1606 1607 /** 1608 * Serialize this object to Simplified JSON using {@link Json5Serializer#DEFAULT_READABLE}. 1609 * 1610 * @return This object serialized as a string. 1611 */ 1612 public String asReadableString() { 1613 if (Json5Serializer.DEFAULT_READABLE == null) 1614 return stringify(this); 1615 return Json5Serializer.DEFAULT_READABLE.toString(this); 1616 } 1617 1618 /** 1619 * Convenience method for serializing this map to the specified <c>Writer</c> using the 1620 * {@link JsonSerializer#DEFAULT} serializer. 1621 * 1622 * @param w The writer to serialize this object to. 1623 * @return This object. 1624 * @throws IOException If a problem occurred trying to write to the writer. 1625 * @throws SerializeException If a problem occurred trying to convert the output. 1626 */ 1627 public JsonMap writeTo(Writer w) throws IOException, SerializeException { 1628 JsonSerializer.DEFAULT.serialize(this, w); 1629 return this; 1630 } 1631 1632 /** 1633 * Returns <jk>true</jk> if this map is unmodifiable. 1634 * 1635 * @return <jk>true</jk> if this map is unmodifiable. 1636 */ 1637 public boolean isUnmodifiable() { 1638 return false; 1639 } 1640 1641 /** 1642 * Returns a modifiable copy of this map if it's unmodifiable. 1643 * 1644 * @return A modifiable copy of this map if it's unmodifiable, or this map if it is already modifiable. 1645 */ 1646 public JsonMap modifiable() { 1647 if (isUnmodifiable()) 1648 return new JsonMap(this); 1649 return this; 1650 } 1651 1652 /** 1653 * Returns an unmodifiable copy of this map if it's modifiable. 1654 * 1655 * @return An unmodifiable copy of this map if it's modifiable, or this map if it is already unmodifiable. 1656 */ 1657 public JsonMap unmodifiable() { 1658 if (this instanceof UnmodifiableJsonMap) 1659 return this; 1660 return new UnmodifiableJsonMap(this); 1661 } 1662 1663 //------------------------------------------------------------------------------------------------------------------ 1664 // Utility methods 1665 //------------------------------------------------------------------------------------------------------------------ 1666 1667 private BeanSession bs() { 1668 if (session == null) 1669 session = BeanContext.DEFAULT_SESSION; 1670 return session; 1671 } 1672 1673 private ObjectRest getObjectRest() { 1674 if (objectRest == null) 1675 objectRest = new ObjectRest(this); 1676 return objectRest; 1677 } 1678 1679 /* 1680 * Combines the class specified by a "_type" attribute with the ClassMeta 1681 * passed in through the cast(ClassMeta) method. 1682 * The rule is that child classes supersede parent classes, and c2 supersedes c1 1683 * if one isn't the parent of another. 1684 */ 1685 private ClassMeta<?> narrowClassMeta(ClassMeta<?> c1, ClassMeta<?> c2) { 1686 if (c1 == null) 1687 return c2; 1688 ClassMeta<?> c = getNarrowedClassMeta(c1, c2); 1689 if (c1.isMap()) { 1690 ClassMeta<?> k = getNarrowedClassMeta(c1.getKeyType(), c2.getKeyType()); 1691 ClassMeta<?> v = getNarrowedClassMeta(c1.getValueType(), c2.getValueType()); 1692 return bs().getClassMeta(c.getInnerClass(), k, v); 1693 } 1694 if (c1.isCollection()) { 1695 ClassMeta<?> e = getNarrowedClassMeta(c1.getElementType(), c2.getElementType()); 1696 return bs().getClassMeta(c.getInnerClass(), e); 1697 } 1698 return c; 1699 } 1700 1701 /* 1702 * If c1 is a child of c2 or the same as c2, returns c1. 1703 * Otherwise, returns c2. 1704 */ 1705 private static ClassMeta<?> getNarrowedClassMeta(ClassMeta<?> c1, ClassMeta<?> c2) { 1706 if (c2 == null || c2.getInfo().isParentOf(c1.getInnerClass())) 1707 return c1; 1708 return c2; 1709 } 1710 1711 /* 1712 * Converts this map to the specified class type. 1713 */ 1714 @SuppressWarnings({"unchecked","rawtypes"}) 1715 private <T> T cast2(ClassMeta<T> cm) { 1716 1717 BeanSession bs = bs(); 1718 try { 1719 Object value = get("value"); 1720 1721 if (cm.isMap()) { 1722 Map m2 = (cm.canCreateNewInstance() ? (Map)cm.newInstance() : new JsonMap(bs)); 1723 ClassMeta<?> kType = cm.getKeyType(), vType = cm.getValueType(); 1724 forEach((k,v) -> { 1725 if (! k.equals(bs.getBeanTypePropertyName(cm))) { 1726 1727 // Attempt to recursively cast child maps. 1728 if (v instanceof JsonMap) 1729 v = ((JsonMap)v).cast(vType); 1730 1731 Object k2 = (kType.isString() ? k : bs.convertToType(k, kType)); 1732 v = (vType.isObject() ? v : bs.convertToType(v, vType)); 1733 1734 m2.put(k2, v); 1735 } 1736 }); 1737 return (T)m2; 1738 1739 } else if (cm.isBean()) { 1740 BeanMap<? extends T> bm = bs.newBeanMap(cm.getInnerClass()); 1741 1742 // Iterate through all the entries in the map and set the individual field values. 1743 forEach((k,v) -> { 1744 if (! k.equals(bs.getBeanTypePropertyName(cm))) { 1745 1746 // Attempt to recursively cast child maps. 1747 if (v instanceof JsonMap) 1748 v = ((JsonMap)v).cast(bm.getProperty(k).getMeta().getClassMeta()); 1749 1750 bm.put(k, v); 1751 } 1752 }); 1753 1754 return bm.getBean(); 1755 1756 } else if (cm.isCollectionOrArray()) { 1757 List items = (List)get("items"); 1758 return bs.convertToType(items, cm); 1759 1760 } else if (value != null) { 1761 return bs.convertToType(value, cm); 1762 } 1763 1764 } catch (Exception e) { 1765 throw new BeanRuntimeException(e, cm.getInnerClass(), 1766 "Error occurred attempting to cast to an object of type ''{0}''", cm.getInnerClass().getName()); 1767 } 1768 1769 throw new BeanRuntimeException(cm.getInnerClass(), 1770 "Cannot convert to class type ''{0}''. Only beans and maps can be converted using this method.", 1771 cm.getInnerClass().getName()); 1772 } 1773 1774 private void parse(Reader r, Parser p) throws ParseException { 1775 if (p == null) 1776 p = JsonParser.DEFAULT; 1777 p.parseIntoMap(r, this, bs().string(), bs().object()); 1778 } 1779 1780 private static final class UnmodifiableJsonMap extends JsonMap { 1781 private static final long serialVersionUID = 1L; 1782 1783 UnmodifiableJsonMap(JsonMap contents) { 1784 super(); 1785 if (contents != null) 1786 contents.forEach((k,v) -> super.put(k, v)); 1787 } 1788 1789 @Override 1790 public final Object put(String key, Object val) { 1791 throw new UnsupportedOperationException("Not supported on read-only object."); 1792 } 1793 1794 @Override 1795 public final Object remove(Object key) { 1796 throw new UnsupportedOperationException("Not supported on read-only object."); 1797 } 1798 1799 @Override 1800 public final boolean isUnmodifiable() { 1801 return true; 1802 } 1803 } 1804 1805 //------------------------------------------------------------------------------------------------------------------ 1806 // Overridden methods. 1807 //------------------------------------------------------------------------------------------------------------------ 1808 1809 @Override /* Map */ 1810 public Object get(Object key) { 1811 Object o = super.get(key); 1812 if (o == null && inner != null) 1813 o = inner.get(key); 1814 return o; 1815 } 1816 1817 @Override /* Map */ 1818 public boolean containsKey(Object key) { 1819 if (super.containsKey(key)) 1820 return true; 1821 if (inner != null) 1822 return inner.containsKey(key); 1823 return false; 1824 } 1825 1826 @Override /* Map */ 1827 public Set<String> keySet() { 1828 if (inner == null) 1829 return super.keySet(); 1830 LinkedHashSet<String> s = set(); 1831 s.addAll(inner.keySet()); 1832 s.addAll(super.keySet()); 1833 return s; 1834 } 1835 1836 @Override /* Map */ 1837 public Set<Map.Entry<String,Object>> entrySet() { 1838 if (inner == null) 1839 return super.entrySet(); 1840 1841 final Set<String> keySet = keySet(); 1842 final Iterator<String> keys = keySet.iterator(); 1843 1844 return new AbstractSet<>() { 1845 1846 @Override /* Iterable */ 1847 public Iterator<Map.Entry<String,Object>> iterator() { 1848 1849 return new Iterator<>() { 1850 1851 @Override /* Iterator */ 1852 public boolean hasNext() { 1853 return keys.hasNext(); 1854 } 1855 1856 @Override /* Iterator */ 1857 public Map.Entry<String,Object> next() { 1858 return new Map.Entry<>() { 1859 String key = keys.next(); 1860 1861 @Override /* Map.Entry */ 1862 public String getKey() { 1863 return key; 1864 } 1865 1866 @Override /* Map.Entry */ 1867 public Object getValue() { 1868 return get(key); 1869 } 1870 1871 @Override /* Map.Entry */ 1872 public Object setValue(Object object) { 1873 return put(key, object); 1874 } 1875 }; 1876 } 1877 1878 @Override /* Iterator */ 1879 public void remove() { 1880 throw new UnsupportedOperationException("Not supported on read-only object."); 1881 } 1882 }; 1883 } 1884 1885 @Override /* Set */ 1886 public int size() { 1887 return keySet.size(); 1888 } 1889 }; 1890 } 1891 1892 /** 1893 * A synonym for {@link #toString()} 1894 * 1895 * @return This object as a JSON string. 1896 */ 1897 public String asJson() { 1898 return toString(); 1899 } 1900 1901 @Override /* Object */ 1902 public String toString() { 1903 return Json5.of(this); 1904 } 1905}