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}