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