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.marshall.collections; 018 019import static java.util.Collections.*; 020import static org.apache.juneau.commons.utils.ClassUtils.*; 021import static org.apache.juneau.commons.utils.CollectionUtils.*; 022import static org.apache.juneau.commons.utils.CollectionUtils.toStringArray; 023import static org.apache.juneau.commons.utils.Shorts.*; 024import static org.apache.juneau.commons.utils.StringUtils.*; 025 026import java.lang.reflect.*; 027import java.util.*; 028import java.util.function.*; 029 030import org.apache.juneau.commons.bean.*; 031import org.apache.juneau.commons.utils.*; 032import org.apache.juneau.marshall.*; 033import org.apache.juneau.marshall.objecttools.*; 034import org.apache.juneau.marshall.parser.*; 035import org.apache.juneau.marshall.serializer.*; 036import org.apache.juneau.marshall.swap.spi.*; 037 038/** 039 * Neutral, marshaller-agnostic map of string-to-object entries. 040 * 041 * <p> 042 * This class is the base for {@link JsonMap} and the per-marshaller {@code XMap} variants. It carries 043 * all marshaller-neutral functionality such as typed accessors, fluent setters, bean integration, 044 * and {@link PathTraversal}-based path navigation. The default {@link #toString()} comes from 045 * {@link LinkedHashMap}; subclasses should override it to produce the appropriate marshalled form. 046 * 047 * <p> 048 * Note that the use of this class is optional. The serializers will accept any objects that implement the 049 * {@link Map} interface. But this class provides useful additional functionality when working with 050 * data models constructed from Java Collections Framework objects. 051 * 052 * <h5 class='section'>Notes:</h5><ul> 053 * <li class='warn'>This class is not thread safe. 054 * </ul> 055 */ 056@SuppressWarnings({ 057 "java:S2160", // equals() / hashCode() inherited from LinkedHashMap; map equality is element-based 058 "java:S6539", // Monster class; MarshalledMap's large method surface follows directly from implementing the Map contract plus path-navigation helpers 059 "rawtypes", // cast2() builds the target map as a raw Map and getSwapped() bridges through a raw ObjectSwap 060 "unchecked" // Casts to T in cast()/cast2()/getSwapped() are driven by the requested Class/ClassMeta/ObjectSwap, which determines T at runtime 061}) 062public class MarshalledMap extends LinkedHashMap<String,Object> { 063 064 private static class Unmodifiable extends MarshalledMap { 065 private static final long serialVersionUID = 1L; 066 067 @SuppressWarnings({ 068 "synthetic-access" // Access to outer class members is intentional 069 }) 070 Unmodifiable(MarshalledMap contents) { 071 if (nn(contents)) 072 contents.forEach(super::put); 073 } 074 075 @Override 076 public void clear() { 077 throw uoroex(); 078 } 079 080 @Override 081 public Object compute(String key, BiFunction<? super String,? super Object,?> remappingFunction) { 082 throw uoroex(); 083 } 084 085 @Override 086 public Object computeIfAbsent(String key, Function<? super String,?> mappingFunction) { 087 throw uoroex(); 088 } 089 090 @Override 091 public Object computeIfPresent(String key, BiFunction<? super String,? super Object,?> remappingFunction) { 092 throw uoroex(); 093 } 094 095 @Override 096 public Set<Map.Entry<String,Object>> entrySet() { 097 return u(super.entrySet()); 098 } 099 100 @Override 101 public boolean isUnmodifiable() { return true; } 102 103 @Override 104 public Set<String> keySet() { 105 return u(super.keySet()); 106 } 107 108 @Override 109 public Object merge(String key, Object value, BiFunction<? super Object,? super Object,?> remappingFunction) { 110 throw uoroex(); 111 } 112 113 @Override 114 public Object put(String key, Object val) { 115 throw uoroex(); 116 } 117 118 @Override 119 public void putAll(Map<? extends String,?> m) { 120 throw uoroex(); 121 } 122 123 @Override 124 public Object putIfAbsent(String key, Object value) { 125 throw uoroex(); 126 } 127 128 @Override 129 public Object remove(Object key) { 130 throw uoroex(); 131 } 132 133 @Override 134 public boolean remove(Object key, Object value) { 135 throw uoroex(); 136 } 137 138 @Override 139 public Object replace(String key, Object value) { 140 throw uoroex(); 141 } 142 143 @Override 144 public boolean replace(String key, Object oldValue, Object newValue) { 145 throw uoroex(); 146 } 147 148 @Override 149 public void replaceAll(BiFunction<? super String,? super Object,?> function) { 150 throw uoroex(); 151 } 152 153 @Override 154 public Collection<Object> values() { 155 return Collections.unmodifiableCollection(super.values()); 156 } 157 } 158 159 private static final long serialVersionUID = 1L; 160 private static final Type[] EMPTY_TYPE_ARRAY = {}; 161 162 /** 163 * An empty read-only MarshalledMap. 164 * 165 * @serial exclude 166 */ 167 @SuppressWarnings({ 168 "java:S2386" // Public static final field accessed externally, cannot be protected 169 }) 170 public static final MarshalledMap EMPTY_MAP = new MarshalledMap() { 171 172 private static final long serialVersionUID = 1L; 173 174 @Override /* Overridden from Map */ 175 public Set<Map.Entry<String,Object>> entrySet() { 176 return Collections.<String,Object>emptyMap().entrySet(); 177 } 178 179 @Override /* Overridden from Map */ 180 public Set<String> keySet() { 181 return Collections.<String,Object>emptyMap().keySet(); 182 } 183 184 @Override /* Overridden from Map */ 185 public Object put(String key, Object value) { 186 throw uoroex(); 187 } 188 189 @Override /* Overridden from Map */ 190 public Object remove(Object key) { 191 throw uoroex(); 192 } 193 194 @Override /* Overridden from Map */ 195 public Collection<Object> values() { 196 return emptyMap().values(); 197 } 198 }; 199 200 /** 201 * Construct an empty map. 202 * 203 * @return An empty map. 204 */ 205 public static MarshalledMap create() { 206 return new MarshalledMap(); 207 } 208 209 /** 210 * Construct an empty filtered map. 211 * 212 * @return An empty map. 213 */ 214 public static MarshalledMap filteredMap() { 215 return create().filtered(); 216 } 217 218 /** 219 * Construct a filtered map initialized with the specified key/value pairs. 220 * 221 * @param keyValuePairs A list of key/value pairs to add to this map. 222 * @return A new map, never <jk>null</jk>. 223 */ 224 public static MarshalledMap filteredMap(Object...keyValuePairs) { 225 return new MarshalledMap(keyValuePairs).filtered(); 226 } 227 228 /** 229 * Construct a map initialized with the specified map. 230 * 231 * @param values 232 * The map to copy. 233 * <br>Can be <jk>null</jk> (returns <jk>null</jk>). 234 * <br>Keys will be converted to strings using {@link Object#toString()}. 235 * @return A new map or <jk>null</jk> if the map was <jk>null</jk>. 236 */ 237 public static MarshalledMap of(Map<?,?> values) { 238 return values == null ? null : new MarshalledMap(values); 239 } 240 241 /** 242 * Construct a map initialized with the specified key/value pairs. 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 MarshalledMap of(Object...keyValuePairs) { 248 return new MarshalledMap(keyValuePairs); 249 } 250 251 /** 252 * Construct a map initialized by parsing the specified string with the specified parser. 253 * 254 * @param in The input being parsed. 255 * @param p 256 * The parser to use to parse the input. 257 * <br>Must not be <jk>null</jk>. 258 * @return A new map (empty if the input was <jk>null</jk>), never <jk>null</jk>. 259 * @throws ParseException Malformed input encountered. 260 */ 261 public static MarshalledMap ofString(CharSequence in, Parser p) throws ParseException { 262 return in == null ? new MarshalledMap() : new MarshalledMap(in, p); 263 } 264 265 /** 266 * Construct a map initialized by parsing the specified reader with the specified parser. 267 * 268 * @param in The reader containing the input being parsed. 269 * @param p 270 * The parser to use to parse the input. 271 * <br>Must not be <jk>null</jk>. 272 * @return A new map (empty if the input was <jk>null</jk>), never <jk>null</jk>. 273 * @throws ParseException Malformed input encountered. 274 */ 275 public static MarshalledMap ofString(java.io.Reader in, Parser p) throws ParseException { 276 return in == null ? new MarshalledMap() : new MarshalledMap(in, p); 277 } 278 279 /* 280 * If c1 is a child of c2 or the same as c2, returns c1. 281 * Otherwise, returns c2. 282 */ 283 private static ClassMeta<?> getNarrowedClassMeta(ClassMeta<?> c1, ClassMeta<?> c2) { 284 if (c2 == null || c2.isAssignableFrom(c1.inner())) 285 return c1; 286 return c2; 287 } 288 289 private transient MarshallingSession session; 290 private transient Map<String,Object> inner; 291 292 private transient PathTraversal pathTraversal; 293 294 private transient Predicate<Object> valueFilter = x -> true; 295 296 /** 297 * Construct an empty map. 298 */ 299 public MarshalledMap() {} 300 301 /** 302 * Construct an empty map with the specified bean context. 303 * 304 * @param session The bean session to use for creating beans. 305 */ 306 public MarshalledMap(MarshallingSession session) { 307 this.session = session; 308 } 309 310 /** 311 * Construct a map initialized by parsing the specified string with the specified parser. 312 * 313 * @param in 314 * The input being parsed. 315 * <br>Can be <jk>null</jk> or empty (results in an empty map; no parsing occurs). 316 * @param p 317 * The parser to use to parse the input. 318 * <br>Must not be <jk>null</jk>. 319 * @throws ParseException Malformed input encountered. 320 */ 321 public MarshalledMap(CharSequence in, Parser p) throws ParseException { 322 this(reqnn("p", p).getMarshallingContext().getSession()); 323 if (ine(in)) 324 p.readIntoMap(in, this, bs().string(), bs().object()); 325 } 326 327 /** 328 * Construct a map initialized with the specified map. 329 * 330 * @param in 331 * The map to copy. 332 * <br>Can be <jk>null</jk> (results in an empty map). 333 * <br>Keys will be converted to strings using {@link Object#toString()}. 334 */ 335 public MarshalledMap(Map<?,?> in) { 336 this(); 337 if (nn(in)) 338 in.forEach((k, v) -> put(k.toString(), v)); 339 } 340 341 /** 342 * Construct a map initialized with the specified key/value pairs. 343 * 344 * @param keyValuePairs A list of key/value pairs to add to this map. 345 */ 346 public MarshalledMap(Object...keyValuePairs) { 347 req(keyValuePairs.length % 2 == 0, "Odd number of parameters passed into MarshalledMap(Object...)"); 348 for (var i = 0; i + 1 < keyValuePairs.length; i += 2) 349 put(s(keyValuePairs[i]), keyValuePairs[i + 1]); 350 } 351 352 /** 353 * Construct a map initialized by parsing the specified reader with the specified parser. 354 * 355 * @param in 356 * The reader containing the input being parsed. 357 * @param p 358 * The parser to use to parse the input. 359 * <br>Must not be <jk>null</jk>. 360 * @throws ParseException Malformed input encountered. 361 */ 362 @SuppressWarnings({ 363 "javabugs:S2259" // The parser is null-checked by reqnn() in the delegating constructor call. 364 }) 365 public MarshalledMap(java.io.Reader in, Parser p) throws ParseException { 366 this(reqnn("p", p).getMarshallingContext().getSession()); 367 p.readIntoMap(in, this, bs().string(), bs().object()); 368 } 369 370 /** 371 * Appends all the entries in the specified map to this map. 372 * 373 * @param values The map to copy. Can be <jk>null</jk> (no-op). 374 * @return This object. 375 */ 376 public MarshalledMap append(Map<String,Object> values) { 377 if (nn(values)) 378 super.putAll(values); 379 return this; 380 } 381 382 /** 383 * Adds an entry to this map. 384 * 385 * @param key The key. 386 * @param value The value. 387 * @return This object. 388 */ 389 public MarshalledMap append(String key, Object value) { 390 put(key, value); 391 return this; 392 } 393 394 /** 395 * Adds the first value that matches the specified predicate. 396 * 397 * @param <T> The value types. 398 * @param test The predicate to match against. 399 * @param key The key. 400 * @param values The values to test. 401 * @return This object. 402 */ 403 @SuppressWarnings({ 404 "varargs" // appendFirst()'s T... values is only iterated, never stored or exposed, so heap pollution cannot occur 405 }) 406 public <T> MarshalledMap appendFirst(Predicate<T> test, String key, T...values) { 407 for (var v : values) 408 if (t(test, v)) 409 return append(key, v); 410 return this; 411 } 412 413 /** 414 * Add if flag is <jk>true</jk>. 415 * 416 * @param flag The flag to check. 417 * @param key The key. 418 * @param value The value. 419 * @return This object. 420 */ 421 public MarshalledMap appendIf(boolean flag, String key, Object value) { 422 if (flag) 423 append(key, value); 424 return this; 425 } 426 427 /** 428 * Add if predicate matches value. 429 * 430 * @param <T> The value type. 431 * @param test The predicate to match against. 432 * @param key The key. 433 * @param value The value. 434 * @return This object. 435 */ 436 public <T> MarshalledMap appendIf(Predicate<T> test, String key, T value) { 437 return appendIf(t(test, value), key, value); 438 } 439 440 /** 441 * Adds a value in this map if the entry does not exist or the current value is <jk>null</jk>. 442 * 443 * @param key The map key. 444 * @param value The value to set if the current value does not exist or is <jk>null</jk>. 445 * @return This object. 446 */ 447 public MarshalledMap appendIfAbsent(String key, Object value) { 448 return appendIfAbsentIf(x -> true, key, value); 449 } 450 451 /** 452 * 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. 453 * 454 * @param <T> The value type. 455 * @param predicate The predicate to test the value with. 456 * @param key The map key. 457 * @param value The value to set if the current value does not exist or is <jk>null</jk>. 458 * @return This object. 459 */ 460 public <T> MarshalledMap appendIfAbsentIf(Predicate<T> predicate, String key, T value) { 461 Object o = get(key); 462 if (o == null && predicate.test(value)) 463 put(key, value); 464 return this; 465 } 466 467 /** 468 * Returns the node addressed by the specified <a class="doclink" href="https://datatracker.ietf.org/doc/html/rfc6901">RFC 6901</a> 469 * JSON Pointer relative to this map. 470 * 471 * <p> 472 * Convenience for <c>MarshalledNode.<jsm>of</jsm>(<jk>this</jk>).at(<jv>pointer</jv>)</c>. A read-miss returns 473 * <jk>null</jk>; an addressed JSON <jk>null</jk> value returns a null node. 474 * 475 * <p> 476 * <b>Beta — API subject to change:</b> This method is part of the next-generation typed tree model layered over the 477 * {@code Marshalled*} collections and may change incompatibly in a future release. 478 * 479 * @param pointer The RFC 6901 JSON Pointer. 480 * @return A node wrapping the addressed value, or <jk>null</jk> if the addressed value is absent. 481 */ 482 public MarshalledNode at(String pointer) { 483 return MarshalledNode.of(this).at(pointer); 484 } 485 486 /** 487 * Serializes this map to a string using the specified serializer. 488 * 489 * @param serializer The serializer to use to convert this object to a string. 490 * @return This object serialized as a string. 491 */ 492 public String toString(WriterSerializer serializer) { 493 return serializer.toString(this); 494 } 495 496 /** 497 * Converts this map into an object of the specified type. 498 * 499 * <p> 500 * If this map contains a <js>"_type"</js> entry, it must be the same as or a subclass of the <c>type</c>. 501 * 502 * @param <T> The class type to convert this map object to. 503 * @param type The class type to convert this map object to. 504 * @return The new object. 505 * @throws ClassCastException 506 * If the <js>"_type"</js> entry is present and not assignable from <c>type</c> 507 */ 508 public <T> T cast(Class<T> type) { 509 MarshallingSession bs = bs(); 510 ClassMeta<?> c2 = bs.getClassMeta(type); 511 String typePropertyName = bs.getBeanTypePropertyName(c2); 512 ClassMeta<?> c1 = bs.getBeanRegistry().getClassMeta((String)get(typePropertyName)); 513 ClassMeta<?> c = c1 == null ? c2 : narrowClassMeta(c1, c2); 514 if (c.isObject()) 515 return (T)this; 516 return (T)cast2(c); 517 } 518 519 /** 520 * Same as {@link #cast(Class)}, except allows you to specify a {@link ClassMeta} parameter. 521 * 522 * @param <T> The class type to convert this map object to. 523 * @param cm The class type to convert this map object to. 524 * @return The new object. 525 * @throws ClassCastException 526 * If the <js>"_type"</js> entry is present and not assignable from <c>type</c> 527 */ 528 public <T> T cast(ClassMeta<T> cm) { 529 MarshallingSession bs = bs(); 530 var c1 = bs.getBeanRegistry().getClassMeta((String)get(bs.getBeanTypePropertyName(cm))); 531 var c = narrowClassMeta(c1, cm); 532 return (T)cast2(c); 533 } 534 535 @Override /* Overridden from Map */ 536 public boolean containsKey(Object key) { 537 if (super.containsKey(key)) 538 return true; 539 if (nn(inner)) 540 return inner.containsKey(key); 541 return false; 542 } 543 544 /** 545 * Returns <jk>true</jk> if the map contains the specified entry and the value is not null nor an empty string. 546 * 547 * <p> 548 * Always returns <jk>false</jk> if the value is not a {@link CharSequence}. 549 * 550 * @param key The key. 551 * @return <jk>true</jk> if the map contains the specified entry and the value is not null nor an empty string. 552 */ 553 public boolean containsKeyNotEmpty(String key) { 554 Object val = get(key); 555 if (val == null) 556 return false; 557 if (val instanceof CharSequence val2) 558 return isNotBlank(val2); 559 return false; 560 } 561 562 /** 563 * Returns <jk>true</jk> if this map contains the specified key, ignoring the inner map if it exists. 564 * 565 * @param key The key to look up. 566 * @return <jk>true</jk> if this map contains the specified key. 567 */ 568 public boolean containsOuterKey(Object key) { 569 return super.containsKey(key); 570 } 571 572 /** 573 * Similar to {@link #remove(Object) remove(Object)}, but the key is a slash-delimited path used to traverse entries 574 * in this POJO. 575 * 576 * <p> 577 * This method uses the {@link PathTraversal} class to perform the lookup, so the map can contain any of the various 578 * class types that the {@link PathTraversal} class supports (e.g. beans, collections, arrays). 579 * 580 * @param path The path to the entry. 581 * @return The previous value, or <jk>null</jk> if the entry doesn't exist. 582 */ 583 public Object deleteAt(String path) { 584 return getPathTraversal().delete(path); 585 } 586 587 @Override /* Overridden from Map */ 588 public Set<Map.Entry<String,Object>> entrySet() { 589 if (inner == null) 590 return super.entrySet(); 591 592 final Set<String> keySet = keySet(); 593 final Iterator<String> keys = keySet.iterator(); 594 595 return new AbstractSet<>() { 596 597 @Override /* Overridden from Iterable */ 598 public Iterator<Map.Entry<String,Object>> iterator() { 599 600 return new Iterator<>() { 601 602 @Override /* Overridden from Iterator */ 603 public boolean hasNext() { 604 return keys.hasNext(); 605 } 606 607 @Override /* Overridden from Iterator */ 608 public Map.Entry<String,Object> next() { 609 return new Map.Entry<>() { 610 String key = keys.next(); 611 612 @Override /* Overridden from Map.Entry */ 613 public String getKey() { return key; } 614 615 @Override /* Overridden from Map.Entry */ 616 public Object getValue() { return get(key); } 617 618 @Override /* Overridden from Map.Entry */ 619 public Object setValue(Object object) { 620 return put(key, object); 621 } 622 }; 623 } 624 625 @Override /* Overridden from Iterator */ 626 public void remove() { 627 throw uoroex(); 628 } 629 }; 630 } 631 632 @Override /* Overridden from Set */ 633 public int size() { 634 return keySet.size(); 635 } 636 }; 637 } 638 639 /** 640 * Returns a copy of this map without the specified keys. 641 * 642 * @param keys The keys of the entries not to copy. 643 * @return A new map without the keys and values from this map. 644 */ 645 public MarshalledMap exclude(String...keys) { 646 var m2 = new MarshalledMap(); 647 this.forEach((k, v) -> { 648 var exclude = false; 649 for (var kk : keys) 650 if (kk.equals(k)) 651 exclude = true; 652 if (! exclude) 653 m2.put(k, v); 654 }); 655 return m2; 656 } 657 658 /** 659 * Enables filtering based on default values. 660 * 661 * <p> 662 * Any of the following types will be ignored when set as values in this map: 663 * <ul> 664 * <li><jk>null</jk> 665 * <li><jk>false</jk> 666 * <li><c>-1</c> (any Number type) 667 * <li>Empty arrays/collections/maps. 668 * </ul> 669 * @return This object. 670 */ 671 public MarshalledMap filtered() { 672 // @formatter:off 673 return filtered(x -> ! ( 674 x == null 675 || (x instanceof Boolean x2 && x2.equals(false)) 676 || (x instanceof Number x3 && x3.intValue() == -1) 677 || (isArray(x) && Array.getLength(x) == 0) 678 || (x instanceof Map<?,?> x2 && x2.isEmpty()) 679 || (x instanceof Collection<?> x3 && x3.isEmpty()) 680 )); 681 // @formatter:on 682 } 683 684 /** 685 * Enables filtering based on a predicate test. 686 * 687 * <p> 688 * If the predicate evaluates to <jk>false</jk> on values added to this map, the entry will be skipped. 689 * 690 * @param value The value tester predicate. 691 * @return This object. 692 */ 693 public MarshalledMap filtered(Predicate<Object> value) { 694 valueFilter = value; 695 return this; 696 } 697 698 /** 699 * Returns the value for the first key in the list that has an entry in this map. 700 * 701 * <p> 702 * Casts or converts the value to the specified class type. 703 * 704 * <p> 705 * See {@link MarshallingSession#convertToType(Object, ClassMeta)} for the list of valid data conversions. 706 * 707 * @param type The class type to convert the value to. 708 * @param <T> The class type to convert the value to. 709 * @param keys The keys to look up in order. 710 * @return The value of the first entry whose key exists, or <jk>null</jk> if none of the keys exist in this map. 711 */ 712 public <T> T find(Class<T> type, String...keys) { 713 for (var key : keys) 714 if (containsKey(key)) 715 return get(key, type); 716 return null; 717 } 718 719 /** 720 * Returns the value for the first key in the list that has an entry in this map. 721 * 722 * @param keys The keys to look up in order. 723 * @return The value of the first entry whose key exists, or <jk>null</jk> if none of the keys exist in this map. 724 */ 725 public Object find(String...keys) { 726 for (var key : keys) 727 if (containsKey(key)) 728 return get(key); 729 return null; 730 } 731 732 /** 733 * Returns the first entry that exists converted to a {@link Boolean}. 734 * 735 * @param keys The list of keys to look for. 736 * @return 737 * The converted value of the first key in the list that has an entry in this map, or <jk>null</jk> if the map 738 * contains no mapping for any of the keys. 739 * @throws InvalidDataConversionException If value cannot be converted. 740 */ 741 public Boolean findBoolean(String...keys) { 742 return find(Boolean.class, keys); 743 } 744 745 /** 746 * Returns the first entry that exists converted to an {@link Integer}. 747 * 748 * @param keys The list of keys to look for. 749 * @return 750 * The converted value of the first key in the list that has an entry in this map, or <jk>null</jk> if the map 751 * contains no mapping for any of the keys. 752 * @throws InvalidDataConversionException If value cannot be converted. 753 */ 754 public Integer findInt(String...keys) { 755 return find(Integer.class, keys); 756 } 757 758 /** 759 * Searches for the specified key in this map ignoring case. 760 * 761 * @param key 762 * The key to search for. 763 * For performance reasons, it's preferable that the key be all lowercase. 764 * @return The key, or <jk>null</jk> if map does not contain this key. 765 */ 766 public String findKeyIgnoreCase(String key) { 767 for (var k : keySet()) 768 if (eqic(key, k)) 769 return k; 770 return null; 771 } 772 773 /** 774 * Returns the first entry that exists converted to a {@link MarshalledList}. 775 * 776 * @param keys The list of keys to look for. 777 * @return 778 * The converted value of the first key in the list that has an entry in this map, or <jk>null</jk> if the map 779 * contains no mapping for any of the keys. 780 * @throws InvalidDataConversionException If value cannot be converted. 781 */ 782 public MarshalledList findList(String...keys) { 783 return find(MarshalledList.class, keys); 784 } 785 786 /** 787 * Returns the first entry that exists converted to a {@link Long}. 788 * 789 * @param keys The list of keys to look for. 790 * @return 791 * The converted value of the first key in the list that has an entry in this map, or <jk>null</jk> if the map 792 * contains no mapping for any of the keys. 793 * @throws InvalidDataConversionException If value cannot be converted. 794 */ 795 public Long findLong(String...keys) { 796 return find(Long.class, keys); 797 } 798 799 /** 800 * Returns the first entry that exists converted to a {@link MarshalledMap}. 801 * 802 * @param keys The list of keys to look for. 803 * @return 804 * The converted value of the first key in the list that has an entry in this map, or <jk>null</jk> if the map 805 * contains no mapping for any of the keys. 806 * @throws InvalidDataConversionException If value cannot be converted. 807 */ 808 public MarshalledMap findMap(String...keys) { 809 return find(MarshalledMap.class, keys); 810 } 811 812 /** 813 * Returns the first entry that exists converted to a {@link String}. 814 * 815 * @param keys The list of keys to look for. 816 * @return 817 * The converted value of the first key in the list that has an entry in this map, or <jk>null</jk> if the map 818 * contains no mapping for any of the keys. 819 */ 820 public String findString(String...keys) { 821 return find(String.class, keys); 822 } 823 824 @Override /* Overridden from Map */ 825 public Object get(Object key) { 826 Object o = super.get(key); 827 if (o == null && nn(inner)) 828 o = inner.get(key); 829 return o; 830 } 831 832 /** 833 * Same as {@link Map#get(Object) get()}, but casts or converts the value to the specified class type. 834 * 835 * <p> 836 * See {@link MarshallingSession#convertToType(Object, ClassMeta)} for the list of valid data conversions. 837 * 838 * @param key The key. 839 * @param <T> The class type returned. 840 * @param type The class type returned. 841 * @return The value, or <jk>null</jk> if the entry doesn't exist. 842 */ 843 public <T> T get(String key, Class<T> type) { 844 return getWithDefault(key, (T)null, type); 845 } 846 847 /** 848 * Same as {@link #get(String,Class)}, but allows for complex data types consisting of collections or maps. 849 * 850 * <p> 851 * The type can be a simple type (e.g. beans, strings, numbers) or parameterized type (collections/maps). 852 * 853 * <p> 854 * See {@link MarshallingSession#convertToType(Object, ClassMeta)} for the list of valid data conversions. 855 * 856 * @param key The key. 857 * @param <T> The class type returned. 858 * @param type The class type returned. 859 * @param args The class type parameters. 860 * @return The value, or <jk>null</jk> if the entry doesn't exist. 861 */ 862 public <T> T get(String key, Type type, Type...args) { 863 return getWithDefault(key, null, type, args); 864 } 865 866 /** 867 * Same as {@link #get(String,Class) get(String,Class)}, but the key is a slash-delimited path used to traverse 868 * entries in this POJO. 869 * 870 * @param path The path to the entry. 871 * @param type The class type. 872 * 873 * @param <T> The class type. 874 * @return The value, or <jk>null</jk> if the entry doesn't exist. 875 */ 876 public <T> T getAt(String path, Class<T> type) { 877 return getPathTraversal().get(path, type); 878 } 879 880 /** 881 * Same as {@link #getAt(String,Class)}, but allows for conversion to complex maps and collections. 882 * 883 * @param path The path to the entry. 884 * @param type The class type. 885 * @param args The class parameter types. 886 * 887 * @param <T> The class type. 888 * @return The value, or <jk>null</jk> if the entry doesn't exist. 889 */ 890 public <T> T getAt(String path, Type type, Type...args) { 891 return getPathTraversal().get(path, type, args); 892 } 893 894 /** 895 * Returns the specified entry value converted to a {@link Boolean}. 896 * 897 * @param key The key. 898 * @return The converted value, or <jk>null</jk> if the map contains no mapping for this key. 899 * @throws InvalidDataConversionException If value cannot be converted. 900 */ 901 public Boolean getBoolean(String key) { 902 return get(key, Boolean.class); 903 } 904 905 /** 906 * Returns the specified entry value converted to a {@link Boolean}. 907 * 908 * @param key The key. 909 * @param defVal The default value if the map doesn't contain the specified mapping. 910 * @return The converted value, or the default value if the map contains no mapping for this key. 911 * @throws InvalidDataConversionException If value cannot be converted. 912 */ 913 public Boolean getBoolean(String key, Boolean defVal) { 914 return getWithDefault(key, defVal, Boolean.class); 915 } 916 917 /** 918 * Returns the class type of the object at the specified index. 919 * 920 * @param key The key into this map. 921 * @return 922 * The data type of the object at the specified key, or <jk>null</jk> if the value is null or does not exist. 923 */ 924 @SuppressWarnings({ 925 "java:S1452" // Wildcard required - ClassMeta<?> for value type metadata 926 }) 927 public ClassMeta<?> getClassMeta(String key) { 928 return bs().getClassMetaForObject(get(key)); 929 } 930 931 /** 932 * Returns the first key in the map. 933 * 934 * @return The first key in the map, or <jk>null</jk> if the map is empty. 935 */ 936 public String getFirstKey() { return first(keySet()).orElse(null); } 937 938 /** 939 * Returns the specified entry value converted to an {@link Integer}. 940 * 941 * @param key The key. 942 * @return The converted value, or <jk>null</jk> if the map contains no mapping for this key. 943 * @throws InvalidDataConversionException If value cannot be converted. 944 */ 945 public Integer getInt(String key) { 946 return get(key, Integer.class); 947 } 948 949 /** 950 * Returns the specified entry value converted to an {@link Integer}. 951 * 952 * @param key The key. 953 * @param defVal The default value if the map doesn't contain the specified mapping. 954 * @return The converted value, or the default value if the map contains no mapping for this key. 955 * @throws InvalidDataConversionException If value cannot be converted. 956 */ 957 public Integer getInt(String key, Integer defVal) { 958 return getWithDefault(key, defVal, Integer.class); 959 } 960 961 /** 962 * Returns the specified entry value converted to a {@link MarshalledList}. 963 * 964 * @param key The key. 965 * @return The converted value, or <jk>null</jk> if the map contains no mapping for this key. 966 * @throws InvalidDataConversionException If value cannot be converted. 967 */ 968 public MarshalledList getList(String key) { 969 return get(key, MarshalledList.class); 970 } 971 972 /** 973 * Same as {@link #getList(String)} but creates a new empty {@link MarshalledList} if it doesn't already exist. 974 * 975 * @param key The key. 976 * @param createIfNotExists If mapping doesn't already exist, create one with an empty {@link MarshalledList}. 977 * @return The converted value, or an empty value if the map contains no mapping for this key. 978 * @throws InvalidDataConversionException If value cannot be converted. 979 */ 980 public MarshalledList getList(String key, boolean createIfNotExists) { 981 var m = getWithDefault(key, null, MarshalledList.class); 982 if (m == null && createIfNotExists) { 983 m = new MarshalledList(); 984 put(key, m); 985 } 986 return m; 987 } 988 989 /** 990 * Same as {@link #getList(String, MarshalledList)} except converts the elements to the specified types. 991 * 992 * @param <E> The element type. 993 * @param key The key. 994 * @param elementType The element type class. 995 * @param def The default value if the map doesn't contain the specified mapping. 996 * @return The converted value, or the default value if the map contains no mapping for this key. 997 * @throws InvalidDataConversionException If value cannot be converted. 998 */ 999 public <E> List<E> getList(String key, Class<E> elementType, List<E> def) { 1000 Object o = get(key); 1001 if (o == null) 1002 return def; 1003 return bs().convertToType(o, List.class, elementType); 1004 } 1005 1006 /** 1007 * Returns the specified entry value converted to a {@link MarshalledList}. 1008 * 1009 * @param key The key. 1010 * @param defVal The default value if the map doesn't contain the specified mapping. 1011 * @return The converted value, or the default value if the map contains no mapping for this key. 1012 * @throws InvalidDataConversionException If value cannot be converted. 1013 */ 1014 public MarshalledList getList(String key, MarshalledList defVal) { 1015 return getWithDefault(key, defVal, MarshalledList.class); 1016 } 1017 1018 /** 1019 * Returns the specified entry value converted to a {@link Long}. 1020 * 1021 * @param key The key. 1022 * @return The converted value, or <jk>null</jk> if the map contains no mapping for this key. 1023 * @throws InvalidDataConversionException If value cannot be converted. 1024 */ 1025 public Long getLong(String key) { 1026 return get(key, Long.class); 1027 } 1028 1029 /** 1030 * Returns the specified entry value converted to a {@link Long}. 1031 * 1032 * @param key The key. 1033 * @param defVal The default value if the map doesn't contain the specified mapping. 1034 * @return The converted value, or the default value if the map contains no mapping for this key. 1035 * @throws InvalidDataConversionException If value cannot be converted. 1036 */ 1037 public Long getLong(String key, Long defVal) { 1038 return getWithDefault(key, defVal, Long.class); 1039 } 1040 1041 /** 1042 * Returns the specified entry value converted to a {@link Map}. 1043 * 1044 * @param key The key. 1045 * @return The converted value, or <jk>null</jk> if the map contains no mapping for this key. 1046 * @throws InvalidDataConversionException If value cannot be converted. 1047 */ 1048 public MarshalledMap getMap(String key) { 1049 return get(key, MarshalledMap.class); 1050 } 1051 1052 /** 1053 * Same as {@link #getMap(String)} but creates a new empty {@link MarshalledMap} if it doesn't already exist. 1054 * 1055 * @param key The key. 1056 * @param createIfNotExists If mapping doesn't already exist, create one with an empty {@link MarshalledMap}. 1057 * @return The converted value, or an empty value if the map contains no mapping for this key. 1058 * @throws InvalidDataConversionException If value cannot be converted. 1059 */ 1060 public MarshalledMap getMap(String key, boolean createIfNotExists) { 1061 var m = getWithDefault(key, null, MarshalledMap.class); 1062 if (m == null && createIfNotExists) { 1063 m = new MarshalledMap(); 1064 put(key, m); 1065 } 1066 return m; 1067 } 1068 1069 /** 1070 * Same as {@link #getMap(String, MarshalledMap)} except converts the keys and values to the specified types. 1071 * 1072 * @param <K> The key type. 1073 * @param <V> The value type. 1074 * @param key The key. 1075 * @param keyType The key type class. 1076 * @param valType The value type class. 1077 * @param def The default value if the map doesn't contain the specified mapping. 1078 * @return The converted value, or the default value if the map contains no mapping for this key. 1079 * @throws InvalidDataConversionException If value cannot be converted. 1080 */ 1081 public <K,V> Map<K,V> getMap(String key, Class<K> keyType, Class<V> valType, Map<K,V> def) { 1082 Object o = get(key); 1083 if (o == null) 1084 return def; 1085 return bs().convertToType(o, Map.class, keyType, valType); 1086 } 1087 1088 /** 1089 * Returns the specified entry value converted to a {@link MarshalledMap}. 1090 * 1091 * @param key The key. 1092 * @param defVal The default value if the map doesn't contain the specified mapping. 1093 * @return The converted value, or the default value if the map contains no mapping for this key. 1094 * @throws InvalidDataConversionException If value cannot be converted. 1095 */ 1096 public MarshalledMap getMap(String key, MarshalledMap defVal) { 1097 return getWithDefault(key, defVal, MarshalledMap.class); 1098 } 1099 1100 /** 1101 * Returns the {@link MarshallingSession} currently associated with this map. 1102 * 1103 * @return The {@link MarshallingSession} currently associated with this map. 1104 */ 1105 public MarshallingSession getMarshallingSession() { return session; } 1106 1107 /** 1108 * Returns the specified entry value converted to a {@link String}. 1109 * 1110 * @param key The key. 1111 * @return The converted value, or <jk>null</jk> if the map contains no mapping for this key. 1112 */ 1113 public String getString(String key) { 1114 return get(key, String.class); 1115 } 1116 1117 /** 1118 * Returns the specified entry value converted to a {@link String}. 1119 * 1120 * @param key The key. 1121 * @param defVal The default value if the map doesn't contain the specified mapping. 1122 * @return The converted value, or the default value if the map contains no mapping for this key. 1123 */ 1124 public String getString(String key, String defVal) { 1125 return getWithDefault(key, defVal, String.class); 1126 } 1127 1128 /** 1129 * Returns the specified entry value converted to a {@link String}[] array. 1130 * 1131 * @param key The key. 1132 * @return The converted value, or <jk>null</jk> if the map contains no mapping for this key. 1133 */ 1134 public String[] getStringArray(String key) { 1135 return getStringArray(key, null); 1136 } 1137 1138 /** 1139 * Same as {@link #getStringArray(String)} but returns a default value if the value cannot be found. 1140 * 1141 * @param key The map key. 1142 * @param def The default value if value is not found. 1143 * @return The value converted to a string array. 1144 */ 1145 public String[] getStringArray(String key, String[] def) { 1146 Object s = get(key, Object.class); 1147 if (s == null) 1148 return def; 1149 String[] r = null; 1150 if (s instanceof Collection<?> s2) 1151 r = toStringArray(s2); 1152 else if (s instanceof String[] s2) 1153 r = s2; 1154 else if (s instanceof Object[] s3) 1155 r = toStringArray(l(s3)); 1156 else 1157 r = StringUtils.splita(s(s)); 1158 return (r == null || r.length == 0) ? def : r; 1159 } 1160 1161 /** 1162 * Same as {@link Map#get(Object) get()}, but converts the raw value to the specified class type using the specified 1163 * POJO swap. 1164 * 1165 * @param key The key. 1166 * @param objectSwap The swap class used to convert the raw type to a transformed type. 1167 * @param <T> The transformed class type. 1168 * @return The value, or <jk>null</jk> if the entry doesn't exist. 1169 * @throws ParseException Malformed input encountered. 1170 */ 1171 public <T> T getSwapped(String key, ObjectSwap<T,?> objectSwap) throws ParseException { 1172 try { 1173 Object o = super.get(key); 1174 if (o == null) 1175 return null; 1176 ObjectSwap swap = objectSwap; 1177 return (T)swap.unswap(bs(), o, null); 1178 } catch (ParseException e) { 1179 throw e; 1180 } catch (Exception e) { 1181 throw new ParseException(e); 1182 } 1183 } 1184 1185 /** 1186 * Same as {@link Map#get(Object) get()}, but returns the default value if the key could not be found. 1187 * 1188 * @param key The key. 1189 * @param def The default value if the entry doesn't exist. 1190 * @return The value, or the default value if the entry doesn't exist. 1191 */ 1192 public Object getWithDefault(String key, Object def) { 1193 Object o = get(key); 1194 return (o == null ? def : o); 1195 } 1196 1197 /** 1198 * Same as {@link #get(String,Class)} but returns a default value if the value does not exist. 1199 * 1200 * @param key The key. 1201 * @param def The default value. Can be <jk>null</jk> (returns <jk>null</jk> if the entry doesn't exist). 1202 * @param <T> The class type returned. 1203 * @param type The class type returned. 1204 * @return The value, or <jk>null</jk> if the entry doesn't exist. 1205 */ 1206 public <T> T getWithDefault(String key, T def, Class<T> type) { 1207 return getWithDefault(key, def, type, EMPTY_TYPE_ARRAY); 1208 } 1209 1210 /** 1211 * Same as {@link #get(String,Type,Type...)} but returns a default value if the value does not exist. 1212 * 1213 * @param key The key. 1214 * @param def The default value. Can be <jk>null</jk> (returns <jk>null</jk> if the entry doesn't exist). 1215 * @param <T> The class type returned. 1216 * @param type The class type returned. 1217 * @param args The class type parameters. 1218 * @return The value, or <jk>null</jk> if the entry doesn't exist. 1219 */ 1220 public <T> T getWithDefault(String key, T def, Type type, Type...args) { 1221 Object o = get(key); 1222 if (o == null) 1223 return def; 1224 T t = bs().convertToType(o, type, args); 1225 return t == null ? def : t; 1226 } 1227 1228 /** 1229 * Returns a copy of this map with only the specified keys. 1230 * 1231 * @param keys The keys of the entries to copy. 1232 * @return A new map with just the keys and values from this map. 1233 */ 1234 public MarshalledMap include(String...keys) { 1235 var m2 = new MarshalledMap(); 1236 this.forEach((k, v) -> { 1237 for (var kk : keys) 1238 if (kk.equals(k)) 1239 m2.put(kk, v); 1240 }); 1241 return m2; 1242 } 1243 1244 /** 1245 * Set an inner map in this map to allow for chained get calls. 1246 * 1247 * <p> 1248 * If {@link #get(Object)} returns <jk>null</jk>, then {@link #get(Object)} will be called on the inner map. 1249 * 1250 * @param inner 1251 * The inner map. 1252 * Can be <jk>null</jk> to remove the inner map from an existing map. 1253 * @return This object. 1254 */ 1255 public MarshalledMap inner(Map<String,Object> inner) { 1256 this.inner = inner; 1257 return this; 1258 } 1259 1260 /** 1261 * Shortcut for <code>getWithDefault(key, defVal, Boolean.<jk>class</jk>)</code>. 1262 * 1263 * @param key The key. 1264 * @param defVal The default value if the map doesn't contain the specified mapping. 1265 * @return The converted value, or the default value if the map contains no mapping for this key. 1266 * @throws InvalidDataConversionException If value cannot be converted. 1267 */ 1268 public boolean is(String key, boolean defVal) { 1269 Boolean result = getWithDefault(key, defVal, Boolean.class); 1270 return result != null ? result : defVal; 1271 } 1272 1273 /** 1274 * Returns <jk>true</jk> if this map is unmodifiable. 1275 * 1276 * @return <jk>true</jk> if this map is unmodifiable. 1277 */ 1278 public boolean isUnmodifiable() { return false; } 1279 1280 /** 1281 * The opposite of {@link #removeAll(String...)}. 1282 * 1283 * <p> 1284 * Discards all keys from this map that aren't in the specified list. 1285 * 1286 * @param keys The keys to keep. 1287 * @return This map. 1288 */ 1289 public MarshalledMap keepAll(String...keys) { 1290 for (var i = keySet().iterator(); i.hasNext();) { 1291 var remove = true; 1292 var key = i.next(); 1293 for (var k : keys) { 1294 if (k.equals(key)) { 1295 remove = false; 1296 break; 1297 } 1298 } 1299 if (remove) 1300 i.remove(); 1301 } 1302 return this; 1303 } 1304 1305 @Override /* Overridden from Map */ 1306 public Set<String> keySet() { 1307 if (inner == null) 1308 return super.keySet(); 1309 Set<String> s = set(); 1310 s.addAll(inner.keySet()); 1311 s.addAll(super.keySet()); 1312 return s; 1313 } 1314 1315 @Override /* Overridden from Object */ 1316 public boolean equals(Object o) { 1317 return super.equals(o); 1318 } 1319 1320 @Override /* Overridden from Object */ 1321 public int hashCode() { 1322 return super.hashCode(); 1323 } 1324 1325 /** 1326 * Returns a modifiable copy of this map if it's unmodifiable. 1327 * 1328 * @return A modifiable copy of this map if it's unmodifiable, or this map if it is already modifiable. 1329 */ 1330 public MarshalledMap modifiable() { 1331 if (isUnmodifiable()) 1332 return new MarshalledMap(this); 1333 return this; 1334 } 1335 1336 /** 1337 * Similar to {@link #putAt(String,Object) putAt(String,Object)}, but used to append to collections and arrays. 1338 * 1339 * <p> 1340 * This method uses the {@link PathTraversal} class to perform the lookup, so the map can contain any of the various 1341 * class types that the {@link PathTraversal} class supports (e.g. beans, collections, arrays). 1342 * 1343 * @param path The path to the entry. 1344 * @param o The new value. 1345 * @return The previous value, or <jk>null</jk> if the entry doesn't exist. 1346 */ 1347 public Object postAt(String path, Object o) { 1348 return getPathTraversal().post(path, o); 1349 } 1350 1351 @Override 1352 public Object put(String key, Object value) { 1353 if (valueFilter.test(value)) 1354 return super.put(key, value); 1355 return null; 1356 } 1357 1358 /** 1359 * Same as <c>put(String,Object)</c>, but the key is a slash-delimited path used to traverse entries in this 1360 * POJO. 1361 * 1362 * <p> 1363 * This method uses the {@link PathTraversal} class to perform the lookup, so the map can contain any of the various 1364 * class types that the {@link PathTraversal} class supports (e.g. beans, collections, arrays). 1365 * 1366 * @param path The path to the entry. 1367 * @param o The new value. 1368 * @return The previous value, or <jk>null</jk> if the entry doesn't exist. 1369 */ 1370 public Object putAt(String path, Object o) { 1371 return getPathTraversal().put(path, o); 1372 } 1373 1374 /** 1375 * Convenience method for removing several keys at once. 1376 * 1377 * @param keys The list of keys to remove. 1378 */ 1379 public void removeAll(Collection<String> keys) { 1380 keys.forEach(this::remove); 1381 } 1382 1383 /** 1384 * Convenience method for removing several keys at once. 1385 * 1386 * @param keys The list of keys to remove. 1387 */ 1388 public void removeAll(String...keys) { 1389 for (var k : keys) 1390 remove(k); 1391 } 1392 1393 /** 1394 * Equivalent to calling <code>removeWithDefault(key,<jk>null</jk>,Boolean.<jk>class</jk>)</code>. 1395 * 1396 * @param key The key. 1397 * @return The converted value, or <jk>null</jk> if the map contains no mapping for this key. 1398 * @throws InvalidDataConversionException If value cannot be converted. 1399 */ 1400 public Boolean removeBoolean(String key) { 1401 return removeBoolean(key, null); 1402 } 1403 1404 /** 1405 * Equivalent to calling <code>removeWithDefault(key,def,Boolean.<jk>class</jk>)</code>. 1406 * 1407 * @param key The key. 1408 * @param def The default value if the map doesn't contain the specified mapping. 1409 * @return The converted value, or the default value if the map contains no mapping for this key. 1410 * @throws InvalidDataConversionException If value cannot be converted. 1411 */ 1412 public Boolean removeBoolean(String key, Boolean def) { 1413 return removeWithDefault(key, def, Boolean.class); 1414 } 1415 1416 /** 1417 * Equivalent to calling <code>removeWithDefault(key,<jk>null</jk>,Integer.<jk>class</jk>)</code>. 1418 * 1419 * @param key The key. 1420 * @return The converted value, or <jk>null</jk> if the map contains no mapping for this key. 1421 * @throws InvalidDataConversionException If value cannot be converted. 1422 */ 1423 public Integer removeInt(String key) { 1424 return removeInt(key, null); 1425 } 1426 1427 /** 1428 * Equivalent to calling <code>removeWithDefault(key,def,Integer.<jk>class</jk>)</code>. 1429 * 1430 * @param key The key. 1431 * @param def The default value if the map doesn't contain the specified mapping. 1432 * @return The converted value, or the default value if the map contains no mapping for this key. 1433 * @throws InvalidDataConversionException If value cannot be converted. 1434 */ 1435 public Integer removeInt(String key, Integer def) { 1436 return removeWithDefault(key, def, Integer.class); 1437 } 1438 1439 /** 1440 * Equivalent to calling <code>removeWithDefault(key,<jk>null</jk>,String.<jk>class</jk>)</code>. 1441 * 1442 * @param key The key. 1443 * @return The converted value, or <jk>null</jk> if the map contains no mapping for this key. 1444 * @throws InvalidDataConversionException If value cannot be converted. 1445 */ 1446 public String removeString(String key) { 1447 return removeString(key, null); 1448 } 1449 1450 /** 1451 * Equivalent to calling <code>removeWithDefault(key,def,String.<jk>class</jk>)</code>. 1452 * 1453 * @param key The key. 1454 * @param def The default value if the map doesn't contain the specified mapping. 1455 * @return The converted value, or the default value if the map contains no mapping for this key. 1456 * @throws InvalidDataConversionException If value cannot be converted. 1457 */ 1458 public String removeString(String key, String def) { 1459 return removeWithDefault(key, def, String.class); 1460 } 1461 1462 /** 1463 * Equivalent to calling <c>get(class,key,def)</c> followed by <c>remove(key);</c> 1464 * @param key The key. 1465 * @param defVal The default value if the map doesn't contain the specified mapping. 1466 * @param type The class type. 1467 * 1468 * @param <T> The class type. 1469 * @return The converted value, or the default value if the map contains no mapping for this key. 1470 * @throws InvalidDataConversionException If value cannot be converted. 1471 */ 1472 public <T> T removeWithDefault(String key, T defVal, Class<T> type) { 1473 T t = getWithDefault(key, defVal, type); 1474 remove(key); 1475 return t; 1476 } 1477 1478 /** 1479 * Override the default bean session used for converting POJOs. 1480 * 1481 * <p> 1482 * Default is {@link MarshallingContext#DEFAULT}, which is sufficient in most cases. 1483 * 1484 * <p> 1485 * Useful if you're serializing/parsing beans with transforms defined. 1486 * 1487 * @param session The new bean session. 1488 * @return This object. 1489 */ 1490 public MarshalledMap session(MarshallingSession session) { 1491 this.session = session; 1492 return this; 1493 } 1494 1495 /** 1496 * Sets the {@link MarshallingSession} currently associated with this map. 1497 * 1498 * @param value The {@link MarshallingSession} currently associated with this map. 1499 * @return This object. 1500 */ 1501 public MarshalledMap setBeanSession(MarshallingSession value) { 1502 session = value; 1503 return this; 1504 } 1505 1506 /** 1507 * Returns an unmodifiable copy of this map if it's modifiable. 1508 * 1509 * @return An unmodifiable copy of this map if it's modifiable, or this map if it is already unmodifiable. 1510 */ 1511 public MarshalledMap unmodifiable() { 1512 if (this instanceof Unmodifiable this2) 1513 return this2; 1514 return new Unmodifiable(this); 1515 } 1516 1517 /** 1518 * Returns the {@link MarshallingSession} associated with this map (lazily defaults to {@link MarshallingContext#DEFAULT_SESSION}). 1519 * 1520 * @return The {@link MarshallingSession} for this map. 1521 */ 1522 protected MarshallingSession bs() { 1523 if (session == null) 1524 session = MarshallingContext.DEFAULT_SESSION; 1525 return session; 1526 } 1527 1528 /** 1529 * Factory hook used by {@link #cast(Class)} / {@link #cast(ClassMeta)} when the target map type cannot be 1530 * instantiated directly (e.g. when the caller asks for the {@link Map} interface). 1531 * 1532 * <p> 1533 * Subclasses should override to return an instance of the matching {@code XMap} variant so that callers chaining 1534 * off of <c>cast(Map.<jk>class</jk>)</c> still see their flavored map type. 1535 * 1536 * @param bs The marshalling session to associate with the new map. 1537 * @return A new map instance. 1538 */ 1539 protected MarshalledMap newMarshalledMap(MarshallingSession bs) { 1540 return new MarshalledMap(bs); 1541 } 1542 1543 /* 1544 * Converts this map to the specified class type. 1545 */ 1546 @SuppressWarnings({ 1547 "java:S3776" // Cognitive complexity acceptable for this specific logic 1548 }) 1549 private <T> T cast2(ClassMeta<T> cm) { 1550 1551 MarshallingSession bs = bs(); 1552 try { 1553 Object value = get("value"); 1554 1555 if (cm.isMap()) { 1556 Map m2 = (cm.canCreateNewInstance() ? (Map)cm.newInstance() : newMarshalledMap(bs)); 1557 ClassMeta<?> kType = cm.getKeyType(); 1558 ClassMeta<?> vType = cm.getValueType(); 1559 forEach((k, v) -> { 1560 if (! k.equals(bs.getBeanTypePropertyName(cm))) { 1561 1562 // Attempt to recursively cast child maps. 1563 if (v instanceof MarshalledMap v2) 1564 v = v2.cast(vType); 1565 1566 Object k2 = (kType.isString() ? k : bs.convertToType(k, kType)); 1567 v = (vType.isObject() ? v : bs.convertToType(v, vType)); 1568 1569 m2.put(k2, v); 1570 } 1571 }); 1572 return (T)m2; 1573 1574 } else if (cm.isBean()) { 1575 BeanMap<? extends T> bm = bs.newBeanMap(cm.inner()); 1576 1577 // Iterate through all the entries in the map and set the individual field values. 1578 forEach((k, v) -> { 1579 if (! k.equals(bs.getBeanTypePropertyName(cm))) { 1580 1581 // Attempt to recursively cast child maps. 1582 if (v instanceof MarshalledMap v2) { 1583 var pm = bm.getProperty(k); 1584 if (pm != null) 1585 v = v2.cast((ClassMeta<?>) pm.getMeta().getBeanInfo()); 1586 } 1587 1588 bm.put(k, v); 1589 } 1590 }); 1591 1592 return MissingRequiredPropertyException.check(null, bm).getBean(); 1593 1594 } else if (cm.isCollectionOrArray()) { 1595 var items = (List)get("items"); 1596 return bs.convertToType(items, cm); 1597 1598 } else if (nn(value)) { 1599 return bs.convertToType(value, cm); 1600 } 1601 1602 } catch (Exception e) { 1603 var mrpe = MissingRequiredPropertyException.find(e); 1604 if (mrpe != null) 1605 throw mrpe; 1606 throw brex(e, cm.inner(), "Error occurred attempting to cast to an object of type '%s'", cn(cm)); 1607 } 1608 1609 throw brex(cm.inner(), "Cannot convert to class type '%s'. Only beans and maps can be converted using this method.", cn(cm)); 1610 } 1611 1612 private PathTraversal getPathTraversal() { 1613 if (pathTraversal == null) 1614 pathTraversal = new PathTraversal(this); 1615 return pathTraversal; 1616 } 1617 1618 /* 1619 * Combines the class specified by a "_type" attribute with the ClassMeta 1620 * passed in through the cast(ClassMeta) method. 1621 * The rule is that child classes supersede parent classes, and c2 supersedes c1 1622 * if one isn't the parent of another. 1623 */ 1624 private ClassMeta<?> narrowClassMeta(ClassMeta<?> c1, ClassMeta<?> c2) { 1625 if (c1 == null) 1626 return c2; 1627 ClassMeta<?> c = getNarrowedClassMeta(c1, c2); 1628 if (c1.isMap()) { 1629 ClassMeta<?> k = getNarrowedClassMeta(c1.getKeyType(), c2.getKeyType()); 1630 ClassMeta<?> v = getNarrowedClassMeta(c1.getValueType(), c2.getValueType()); 1631 return bs().getClassMeta(c.inner(), k, v); 1632 } 1633 if (c1.isCollection()) { 1634 ClassMeta<?> e = getNarrowedClassMeta(c1.getElementType(), c2.getElementType()); 1635 return bs().getClassMeta(c.inner(), e); 1636 } 1637 return c; 1638 } 1639}