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