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