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