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.ClassUtils.*; 016import static org.apache.juneau.internal.StringUtils.*; 017 018import java.io.*; 019import java.lang.reflect.*; 020import java.util.*; 021 022import org.apache.juneau.internal.*; 023import org.apache.juneau.json.*; 024import org.apache.juneau.parser.*; 025import org.apache.juneau.serializer.*; 026import org.apache.juneau.transform.*; 027import org.apache.juneau.utils.*; 028 029/** 030 * Java implementation of a JSON object. 031 * 032 * <p> 033 * An extension of {@link LinkedHashMap}, so all methods available in that class are also available to this class. 034 * <p> 035 * Note that the use of this class is optional. 036 * The serializers will accept any objects that implement the {@link java.util.Map} interface. 037 * But this class provides some useful additional functionality when working with JSON models constructed from Java 038 * Collections Framework objects. 039 * For example, a constructor is provided for converting a JSON object string directly into a {@link Map}. 040 * It also contains accessor methods for to avoid common typecasting when accessing elements in a list. 041 * 042 * <h5 class='section'>Example:</h5> 043 * <p class='bcode w800'> 044 * <jc>// Construct an empty Map</jc> 045 * Map m = <jk>new</jk> ObjectMap(); 046 * 047 * <jc>// Construct a Map from JSON</jc> 048 * String json = <js>"{a:'A',b:{c:'C',d:123}}"</js>; 049 * m = <jk>new</jk> ObjectMap(json); 050 * 051 * <jc>// Construct a Map using the append method</jc> 052 * m = <jk>new</jk> ObjectMap().append(<js>"foo"</js>,<js>"x"</js>).append(<js>"bar"</js>,123) 053 * .append(<js>"baz"</js>,<jk>true</jk>); 054 * 055 * <jc>// Construct a Map from XML generated by XmlSerializer</jc> 056 * 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>; 057 * m = <jk>new</jk> ObjectMap(xml, DataFormat.<jsf>XML</jsf>); 058 * m = (Map)XmlParser.<jsf>DEFAULT</jsf>.parse(xml); <jc>// Equivalent</jc> 059 * m = (Map)XmlParser.<jsf>DEFAULT</jsf>.parse(Object.<jk>class</jk>, xml); <jc>// Equivalent</jc> 060 * m = XmlParser.<jsf>DEFAULT</jsf>.parse(Map.<jk>class</jk>, xml); <jc>// Equivalent</jc> 061 * m = XmlParser.<jsf>DEFAULT</jsf>.parse(ObjectMap.<jk>class</jk>, xml); <jc>// Equivalent</jc> 062 * 063 * <jc>// Construct a Map from a URL GET parameter string generated by UrlEncodingParser</jc> 064 * String urlParams = <js>"?a='A'&b={c:'C',d:123}"</js>; 065 * m = <jk>new</jk> ObjectMap(urlParams, DataFormat.<jsf>URLPARAM</jsf>); 066 * m = (Map)UrlEncodingParser.<jsf>DEFAULT</jsf>.parse(Object.<jk>class</jk>, xml); <jc>// Equivalent</jc> 067 * m = UrlEncodingParser.<jsf>DEFAULT</jsf>.parse(Map.<jk>class</jk>, xml); <jc>// Equivalent</jc> 068 * m = UrlEncodingParser.<jsf>DEFAULT</jsf>.parse(ObjectMap.<jk>class</jk>, xml); <jc>// Equivalent</jc> 069 * 070 * <jc>// Construct JSON from ObjectMap</jc> 071 * m = <jk>new</jk> ObjectMap(<js>"{foo:'bar'},{baz:[123,true]}"</js>); 072 * json = m.toString(); <jc>// Produces "{foo:'bar'},{baz:[123,true]}"</jc> 073 * json = m.toString(JsonSerializer.<jsf>DEFAULT_CONDENSED</jsf>); <jc>// Equivalent</jc> 074 * json = JsonSerializer.<jsf>DEFAULT_CONDENSED</jsf>.serialize(m); <jc>// Equivalent</jc> 075 * 076 * <jc>// Get a map entry as an Integer</jc> 077 * m = <jk>new</jk> ObjectMap(<js>"{foo:123}"</js>); 078 * Integer i = m.getInt(<js>"foo"</js>); 079 * i = m.get(Integer.<jk>class</jk>, <js>"foo"</js>); <jc>// Equivalent</jc> 080 * 081 * <jc>// Get a map entry as a Float</jc> 082 * m = <jk>new</jk> ObjectMap(<js>"{foo:123}"</js>); 083 * Float f = m.getFloat(<js>"foo"</js>); 084 * f = m.get(Float.<jk>class</jk>, <js>"foo"</js>); <jc>// Equivalent</jc> 085 * 086 * <jc>// Same as above, except converted to a String</jc> 087 * m = <jk>new</jk> ObjectMap(<js>"{foo:123}"</js>); 088 * String s = m.getString(<js>"foo"</js>); <jc>// Returns "123"</jc> 089 * s = m.get(String.<jk>class</jk>, <js>"foo"</js>); <jc>// Equivalent</jc> 090 * 091 * <jc>// Get one of the entries in the list as a bean (converted to a bean if it isn't already one)</jc> 092 * m = <jk>new</jk> ObjectMap(<js>"{person:{name:'John Smith',age:45}}"</js>); 093 * Person p = m.get(Person.<jk>class</jk>, <js>"person"</js>); 094 * 095 * <jc>// Add an inner map</jc> 096 * ObjectMap m1 = <jk>new</jk> ObjectMap(<js>"{a:1}"</js>); 097 * ObjectMap m2 = <jk>new</jk> ObjectMap(<js>"{b:2}"</js>).setInner(m1); 098 * <jk>int</jk> a = m2.getInt(<js>"a"</js>); <jc>// a == 1 </jc> 099 * </p> 100 * 101 * <p> 102 * This class is not thread safe. 103 */ 104public class ObjectMap extends LinkedHashMap<String,Object> { 105 private static final long serialVersionUID = 1L; 106 107 private transient BeanSession session; 108 private Map<String,Object> inner; 109 private transient PojoRest pojoRest; 110 111 /** 112 * An empty read-only ObjectMap. 113 */ 114 public static final ObjectMap EMPTY_MAP = new ObjectMap() { 115 116 private static final long serialVersionUID = 1L; 117 118 @Override /* Map */ 119 public Set<Map.Entry<String,Object>> entrySet() { 120 return Collections.EMPTY_MAP.entrySet(); 121 } 122 123 @Override /* Map */ 124 public Set<String> keySet() { 125 return Collections.EMPTY_MAP.keySet(); 126 } 127 128 @Override /* Map */ 129 public Object put(String key, Object value) { 130 throw new UnsupportedOperationException(); 131 } 132 133 @Override /* Map */ 134 public Object remove(Object key) { 135 throw new UnsupportedOperationException(); 136 } 137 138 @Override /* Map */ 139 public Collection<Object> values() { 140 return Collections.emptyMap().values(); 141 } 142 }; 143 144 /** 145 * Construct an ObjectMap directly from a string using the specified parser. 146 * 147 * @param s The string being parsed. 148 * @param p The parser to use to parse the input. 149 * @throws ParseException If the input contains a syntax error or is malformed. 150 */ 151 public ObjectMap(CharSequence s, Parser p) throws ParseException { 152 this(p == null ? null : p.createBeanSession()); 153 if (p == null) 154 p = JsonParser.DEFAULT; 155 if (! StringUtils.isEmpty(s)) 156 p.parseIntoMap(s, this, bs().string(), bs().object()); 157 } 158 159 /** 160 * Shortcut for <code><jk>new</jk> ObjectMap(string,JsonParser.<jsf>DEFAULT</jsf>);</code> 161 * 162 * @param s The JSON text to parse. 163 * @throws ParseException If the input contains a syntax error or is malformed. 164 */ 165 public ObjectMap(CharSequence s) throws ParseException { 166 this(s, null); 167 } 168 169 /** 170 * Construct an ObjectMap directly from a reader using the specified parser. 171 * 172 * @param r The reader to read from. The reader will be wrapped in a {@link BufferedReader} if it isn't already. 173 * @param p The parser to use to parse the input. 174 * @throws ParseException If the input contains a syntax error or is malformed. 175 * @throws IOException If a problem occurred trying to read from the reader. 176 */ 177 public ObjectMap(Reader r, Parser p) throws ParseException, IOException { 178 this(p == null ? null : p.createBeanSession()); 179 parseReader(r, p); 180 } 181 182 /** 183 * Shortcut for <code><jk>new</jk> ObjectMap(reader, JsonParser.<jsf>DEFAULT</jsf>)</code>. 184 * 185 * @param r The reader to read from. The reader will be wrapped in a {@link BufferedReader} if it isn't already. 186 * @throws ParseException If the input contains a syntax error or is malformed. 187 * @throws IOException If a problem occurred trying to read from the reader. 188 */ 189 public ObjectMap(Reader r) throws ParseException, IOException { 190 parseReader(r, JsonParser.DEFAULT); 191 } 192 193 private void parseReader(Reader r, Parser p) throws ParseException { 194 if (p == null) 195 p = JsonParser.DEFAULT; 196 p.parseIntoMap(r, this, bs().string(), bs().object()); 197 } 198 199 /** 200 * Construct an empty JSON object (an empty {@link LinkedHashMap}). 201 */ 202 public ObjectMap() { 203 } 204 205 /** 206 * Construct an empty JSON object (an empty {@link LinkedHashMap}) with the specified bean context. 207 * 208 * @param session The bean session to use for creating beans. 209 */ 210 public ObjectMap(BeanSession session) { 211 this.session = session; 212 } 213 214 /** 215 * Construct a JSON object and fill it with the contents from the specified {@link Map}. 216 * 217 * @param m The map whose entries will be copied into this map. 218 */ 219 public ObjectMap(Map<?,?> m) { 220 this(); 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 <code>-1</code>. 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 * <code>Collection</code> classes are assumed to be followed by zero or one objects indicating the element type. 498 * 499 * <p> 500 * <code>Map</code> 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 * <h5 class='section'>Notes:</h5> 509 * <ul class='spaced-list'> 510 * <li> 511 * Use the {@link #get(String, Class)} method instead if you don't need a parameterized map/collection. 512 * </ul> 513 * 514 * @param key The key. 515 * @param <T> The class type returned. 516 * @param type The class type returned. 517 * @param args The class type parameters. 518 * @return The value, or <jk>null</jk> if the entry doesn't exist. 519 */ 520 public <T> T get(String key, Type type, Type...args) { 521 return getWithDefault(key, null, type, args); 522 } 523 524 /** 525 * Same as {@link Map#get(Object) get()}, but returns the default value if the key could not be found. 526 * 527 * @param key The key. 528 * @param def The default value if the entry doesn't exist. 529 * @return The value, or the default value if the entry doesn't exist. 530 */ 531 public Object getWithDefault(String key, Object def) { 532 Object o = get(key); 533 return (o == null ? def : o); 534 } 535 536 /** 537 * Same as {@link #get(String,Class)} but returns a default value if the value does not exist. 538 * 539 * @param key The key. 540 * @param def The default value. Can be <jk>null</jk>. 541 * @param <T> The class type returned. 542 * @param type The class type returned. 543 * @return The value, or <jk>null</jk> if the entry doesn't exist. 544 */ 545 public <T> T getWithDefault(String key, T def, Class<T> type) { 546 return getWithDefault(key, def, type, new Type[0]); 547 } 548 549 /** 550 * Same as {@link #get(String,Type,Type...)} but returns a default value if the value does not exist. 551 * 552 * @param key The key. 553 * @param def The default value. Can be <jk>null</jk>. 554 * @param <T> The class type returned. 555 * @param type The class type returned. 556 * @param args The class type parameters. 557 * @return The value, or <jk>null</jk> if the entry doesn't exist. 558 */ 559 public <T> T getWithDefault(String key, T def, Type type, Type...args) { 560 Object o = get(key); 561 if (o == null) 562 return def; 563 T t = bs().convertToType(o, type, args); 564 return t == null ? def : t; 565 } 566 567 568 /** 569 * Same as {@link Map#get(Object) get()}, but converts the raw value to the specified class type using the specified 570 * POJO swap. 571 * 572 * @param key The key. 573 * @param pojoSwap The swap class used to convert the raw type to a transformed type. 574 * @param <T> The transformed class type. 575 * @return The value, or <jk>null</jk> if the entry doesn't exist. 576 * @throws ParseException Thrown by the POJO swap if a problem occurred trying to parse the value. 577 */ 578 @SuppressWarnings({ "rawtypes", "unchecked" }) 579 public <T> T getSwapped(String key, PojoSwap<T,?> pojoSwap) throws ParseException { 580 try { 581 Object o = super.get(key); 582 if (o == null) 583 return null; 584 PojoSwap swap = pojoSwap; 585 return (T)swap.unswap(bs(), o, null); 586 } catch (ParseException e) { 587 throw e; 588 } catch (Exception e) { 589 throw new ParseException(e); 590 } 591 } 592 593 /** 594 * Returns the value for the first key in the list that has an entry in this map. 595 * 596 * @param keys The keys to look up in order. 597 * @return The value of the first entry whose key exists, or <jk>null</jk> if none of the keys exist in this map. 598 */ 599 public Object find(String...keys) { 600 for (String key : keys) 601 if (containsKey(key)) 602 return get(key); 603 return null; 604 } 605 606 /** 607 * Returns the value for the first key in the list that has an entry in this map. 608 * 609 * <p> 610 * Casts or converts the value to the specified class type. 611 * 612 * <p> 613 * See {@link BeanSession#convertToType(Object, ClassMeta)} for the list of valid data conversions. 614 * 615 * @param type The class type to convert the value to. 616 * @param <T> The class type to convert the value to. 617 * @param keys The keys to look up in order. 618 * @return The value of the first entry whose key exists, or <jk>null</jk> if none of the keys exist in this map. 619 */ 620 public <T> T find(Class<T> type, String...keys) { 621 for (String key : keys) 622 if (containsKey(key)) 623 return get(key, type); 624 return null; 625 } 626 627 /** 628 * Same as {@link #get(String,Class) get(String,Class)}, but the key is a slash-delimited path used to traverse 629 * entries in this POJO. 630 * 631 * <p> 632 * For example, the following code is equivalent: 633 * </p> 634 * <p class='bcode w800'> 635 * ObjectMap m = getObjectMap(); 636 * 637 * <jc>// Long way</jc> 638 * <jk>long</jk> l = m.getObjectMap(<js>"foo"</js>).getObjectList(<js>"bar"</js>).getObjectMap(<js>"0"</js>) 639 * .getLong(<js>"baz"</js>); 640 * 641 * <jc>// Using this method</jc> 642 * <jk>long</jk> l = m.getAt(<js>"foo/bar/0/baz"</js>, <jk>long</jk>.<jk>class</jk>); 643 * </p> 644 * 645 * <p> 646 * This method uses the {@link PojoRest} class to perform the lookup, so the map can contain any of the various 647 * class types that the {@link PojoRest} class supports (e.g. beans, collections, arrays). 648 * 649 * @param path The path to the entry. 650 * @param type The class type. 651 * 652 * @param <T> The class type. 653 * @return The value, or <jk>null</jk> if the entry doesn't exist. 654 */ 655 public <T> T getAt(String path, Class<T> type) { 656 return getPojoRest().get(path, type); 657 } 658 659 /** 660 * Same as {@link #getAt(String,Class)}, but allows for conversion to complex maps and collections. 661 * 662 * <p> 663 * This method uses the {@link PojoRest} class to perform the lookup, so the map can contain any of the various 664 * class types that the {@link PojoRest} class supports (e.g. beans, collections, arrays). 665 * 666 * @param path The path to the entry. 667 * @param type The class type. 668 * @param args The class parameter types. 669 * 670 * @param <T> The class type. 671 * @return The value, or <jk>null</jk> if the entry doesn't exist. 672 */ 673 public <T> T getAt(String path, Type type, Type...args) { 674 return getPojoRest().get(path, type, args); 675 } 676 677 /** 678 * Same as <code>put(String,Object)</code>, but the key is a slash-delimited path used to traverse entries in this 679 * POJO. 680 * 681 * <p> 682 * For example, the following code is equivalent: 683 * </p> 684 * <p class='bcode w800'> 685 * ObjectMap m = getObjectMap(); 686 * 687 * <jc>// Long way</jc> 688 * m.getObjectMap(<js>"foo"</js>).getObjectList(<js>"bar"</js>).getObjectMap(<js>"0"</js>) 689 * .put(<js>"baz"</js>, 123); 690 * 691 * <jc>// Using this method</jc> 692 * m.putAt(<js>"foo/bar/0/baz"</js>, 123); 693 * </p> 694 * 695 * <p> 696 * This method uses the {@link PojoRest} class to perform the lookup, so the map can contain any of the various 697 * class types that the {@link PojoRest} class supports (e.g. beans, collections, arrays). 698 * 699 * @param path The path to the entry. 700 * @param o The new value. 701 * @return The previous value, or <jk>null</jk> if the entry doesn't exist. 702 */ 703 public Object putAt(String path, Object o) { 704 return getPojoRest().put(path, o); 705 } 706 707 /** 708 * Similar to {@link #putAt(String,Object) putAt(String,Object)}, but used to append to collections and arrays. 709 * 710 * <p> 711 * For example, the following code is equivalent: 712 * </p> 713 * <p class='bcode w800'> 714 * ObjectMap m = getObjectMap(); 715 * 716 * <jc>// Long way</jc> 717 * m.getObjectMap(<js>"foo"</js>).getObjectList(<js>"bar"</js>).append(123); 718 * 719 * <jc>// Using this method</jc> 720 * m.postAt(<js>"foo/bar"</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 postAt(String path, Object o) { 732 return getPojoRest().post(path, o); 733 } 734 735 /** 736 * Similar to {@link #remove(Object) remove(Object)}, but the key is a slash-delimited path used to traverse entries 737 * in this POJO. 738 * 739 * <p> 740 * For example, the following code is equivalent: 741 * </p> 742 * <p class='bcode w800'> 743 * ObjectMap m = getObjectMap(); 744 * 745 * <jc>// Long way</jc> 746 * m.getObjectMap(<js>"foo"</js>).getObjectList(<js>"bar"</js>).getObjectMap(0).remove(<js>"baz"</js>); 747 * 748 * <jc>// Using this method</jc> 749 * m.deleteAt(<js>"foo/bar/0/baz"</js>); 750 * </p> 751 * 752 * <p> 753 * This method uses the {@link PojoRest} class to perform the lookup, so the map can contain any of the various 754 * class types that the {@link PojoRest} class supports (e.g. beans, collections, arrays). 755 * 756 * @param path The path to the entry. 757 * @return The previous value, or <jk>null</jk> if the entry doesn't exist. 758 */ 759 public Object deleteAt(String path) { 760 return getPojoRest().delete(path); 761 } 762 763 /** 764 * Convenience method for inserting JSON directly into an attribute on this object. 765 * 766 * <p> 767 * The JSON text can be an object (i.e. <js>"{...}"</js>) or an array (i.e. <js>"[...]"</js>). 768 * 769 * @param key The key. 770 * @param json The JSON text that will be parsed into an Object and then inserted into this map. 771 * @throws ParseException If the input contains a syntax error or is malformed. 772 */ 773 public void putJson(String key, String json) throws ParseException { 774 this.put(key, JsonParser.DEFAULT.parse(json, Object.class)); 775 } 776 777 /** 778 * Returns the specified entry value converted to a {@link String}. 779 * 780 * <p> 781 * Shortcut for <code>get(key, String.<jk>class</jk>)</code>. 782 * 783 * @param key The key. 784 * @return The converted value, or <jk>null</jk> if the map contains no mapping for this key. 785 */ 786 public String getString(String key) { 787 return get(key, String.class); 788 } 789 790 /** 791 * Returns the specified entry value converted to a {@link String}. 792 * 793 * <p> 794 * Shortcut for <code>get(key, String[].<jk>class</jk>)</code>. 795 * 796 * @param key The key. 797 * @return The converted value, or <jk>null</jk> if the map contains no mapping for this key. 798 */ 799 public String[] getStringArray(String key) { 800 return getStringArray(key, null); 801 } 802 803 /** 804 * Same as {@link #getStringArray(String)} but returns a default value if the value cannot be found. 805 * 806 * @param key The map key. 807 * @param def The default value if value is not found. 808 * @return The value converted to a string array. 809 */ 810 public String[] getStringArray(String key, String[] def) { 811 Object s = get(key, Object.class); 812 if (s == null) 813 return def; 814 String[] r = null; 815 if (s instanceof Collection) 816 r = ArrayUtils.toStringArray((Collection<?>)s); 817 else if (s instanceof String[]) 818 r = (String[])s; 819 else if (s instanceof Object[]) 820 r = ArrayUtils.toStringArray(Arrays.asList((Object[])s)); 821 else 822 r = split(asString(s)); 823 return (r.length == 0 ? def : r); 824 } 825 826 /** 827 * Returns the specified entry value converted to a {@link String}. 828 * 829 * <p> 830 * Shortcut for <code>getWithDefault(key, defVal, String.<jk>class</jk>)</code>. 831 * 832 * @param key The key. 833 * @param defVal The default value if the map doesn't contain the specified mapping. 834 * @return The converted value, or the default value if the map contains no mapping for this key. 835 */ 836 public String getString(String key, String defVal) { 837 return getWithDefault(key, defVal, String.class); 838 } 839 840 /** 841 * Returns the specified entry value converted to an {@link Integer}. 842 * 843 * <p> 844 * Shortcut for <code>get(key, Integer.<jk>class</jk>)</code>. 845 * 846 * @param key The key. 847 * @return The converted value, or <jk>null</jk> if the map contains no mapping for this key. 848 * @throws InvalidDataConversionException If value cannot be converted. 849 */ 850 public Integer getInt(String key) { 851 return get(key, Integer.class); 852 } 853 854 /** 855 * Returns the specified entry value converted to an {@link Integer}. 856 * 857 * <p> 858 * Shortcut for <code>getWithDefault(key, defVal, Integer.<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 * @throws InvalidDataConversionException If value cannot be converted. 864 */ 865 public Integer getInt(String key, Integer defVal) { 866 return getWithDefault(key, defVal, Integer.class); 867 } 868 869 /** 870 * Returns the specified entry value converted to a {@link Long}. 871 * 872 * <p> 873 * Shortcut for <code>get(key, Long.<jk>class</jk>)</code>. 874 * 875 * @param key The key. 876 * @return The converted value, or <jk>null</jk> if the map contains no mapping for this key. 877 * @throws InvalidDataConversionException If value cannot be converted. 878 */ 879 public Long getLong(String key) { 880 return get(key, Long.class); 881 } 882 883 /** 884 * Returns the specified entry value converted to a {@link Long}. 885 * 886 * <p> 887 * Shortcut for <code>getWithDefault(key, defVal, Long.<jk>class</jk>)</code>. 888 * 889 * @param key The key. 890 * @param defVal The default value if the map doesn't contain the specified mapping. 891 * @return The converted value, or the default value if the map contains no mapping for this key. 892 * @throws InvalidDataConversionException If value cannot be converted. 893 */ 894 public Long getLong(String key, Long defVal) { 895 return getWithDefault(key, defVal, Long.class); 896 } 897 898 /** 899 * Returns the specified entry value converted to a {@link Boolean}. 900 * 901 * <p> 902 * Shortcut for <code>get(key, Boolean.<jk>class</jk>)</code>. 903 * 904 * @param key The key. 905 * @return The converted value, or <jk>null</jk> if the map contains no mapping for this key. 906 * @throws InvalidDataConversionException If value cannot be converted. 907 */ 908 public Boolean getBoolean(String key) { 909 return get(key, Boolean.class); 910 } 911 912 /** 913 * Returns the specified entry value converted to a {@link Boolean}. 914 * 915 * <p> 916 * Shortcut for <code>getWithDefault(key, defVal, Boolean.<jk>class</jk>)</code>. 917 * 918 * @param key The key. 919 * @param defVal The default value if the map doesn't contain the specified mapping. 920 * @return The converted value, or the default value if the map contains no mapping for this key. 921 * @throws InvalidDataConversionException If value cannot be converted. 922 */ 923 public Boolean getBoolean(String key, Boolean defVal) { 924 return getWithDefault(key, defVal, Boolean.class); 925 } 926 927 /** 928 * Returns the specified entry value converted to a {@link Map}. 929 * 930 * <p> 931 * Shortcut for <code>get(key, Map.<jk>class</jk>)</code>. 932 * 933 * @param key The key. 934 * @return The converted value, or <jk>null</jk> if the map contains no mapping for this key. 935 * @throws InvalidDataConversionException If value cannot be converted. 936 */ 937 public Map<?,?> getMap(String key) { 938 return get(key, Map.class); 939 } 940 941 /** 942 * Returns the specified entry value converted to a {@link Map}. 943 * 944 * <p> 945 * Shortcut for <code>getWithDefault(key, defVal, Map.<jk>class</jk>)</code>. 946 * 947 * @param key The key. 948 * @param defVal The default value if the map doesn't contain the specified mapping. 949 * @return The converted value, or the default value if the map contains no mapping for this key. 950 * @throws InvalidDataConversionException If value cannot be converted. 951 */ 952 public Map<?,?> getMap(String key, Map<?,?> defVal) { 953 return getWithDefault(key, defVal, Map.class); 954 } 955 956 /** 957 * Same as {@link #getMap(String, Map)} except converts the keys and values to the specified types. 958 * 959 * @param key The key. 960 * @param keyType The key type class. 961 * @param valType The value type class. 962 * @param def The default value if the map doesn't contain the specified mapping. 963 * @return The converted value, or the default value if the map contains no mapping for this key. 964 * @throws InvalidDataConversionException If value cannot be converted. 965 */ 966 public <K,V> Map<K,V> getMap(String key, Class<K> keyType, Class<V> valType, Map<K,V> def) { 967 Object o = get(key); 968 if (o == null) 969 return def; 970 return bs().convertToType(o, Map.class, keyType, valType); 971 } 972 973 /** 974 * Returns the specified entry value converted to a {@link List}. 975 * 976 * <p> 977 * Shortcut for <code>get(key, List.<jk>class</jk>)</code>. 978 * 979 * @param key The key. 980 * @return The converted value, or <jk>null</jk> if the map contains no mapping for this key. 981 * @throws InvalidDataConversionException If value cannot be converted. 982 */ 983 public List<?> getList(String key) { 984 return get(key, List.class); 985 } 986 987 /** 988 * Returns the specified entry value converted to a {@link List}. 989 * 990 * <p> 991 * Shortcut for <code>getWithDefault(key, defVal, List.<jk>class</jk>)</code>. 992 * 993 * @param key The key. 994 * @param defVal The default value if the map doesn't contain the specified mapping. 995 * @return The converted value, or the default value if the map contains no mapping for this key. 996 * @throws InvalidDataConversionException If value cannot be converted. 997 */ 998 public List<?> getList(String key, List<?> defVal) { 999 return getWithDefault(key, defVal, List.class); 1000 } 1001 1002 /** 1003 * Same as {@link #getList(String, List)} except converts the elements to the specified types. 1004 * 1005 * @param key The key. 1006 * @param elementType The element type class. 1007 * @param def The default value if the map doesn't contain the specified mapping. 1008 * @return The converted value, or the default value if the map contains no mapping for this key. 1009 * @throws InvalidDataConversionException If value cannot be converted. 1010 */ 1011 public <E> List<E> getList(String key, Class<E> elementType, List<E> def) { 1012 Object o = get(key); 1013 if (o == null) 1014 return def; 1015 return bs().convertToType(o, List.class, elementType); 1016 } 1017 1018 /** 1019 * Returns the specified entry value converted to a {@link Map}. 1020 * 1021 * <p> 1022 * Shortcut for <code>get(key, ObjectMap.<jk>class</jk>)</code>. 1023 * 1024 * @param key The key. 1025 * @return The converted value, or <jk>null</jk> if the map contains no mapping for this key. 1026 * @throws InvalidDataConversionException If value cannot be converted. 1027 */ 1028 public ObjectMap getObjectMap(String key) { 1029 return get(key, ObjectMap.class); 1030 } 1031 1032 /** 1033 * Returns the specified entry value converted to a {@link ObjectMap}. 1034 * 1035 * <p> 1036 * Shortcut for <code>getWithDefault(key, defVal, ObjectMap.<jk>class</jk>)</code>. 1037 * 1038 * @param key The key. 1039 * @param defVal The default value if the map doesn't contain the specified mapping. 1040 * @return The converted value, or the default value if the map contains no mapping for this key. 1041 * @throws InvalidDataConversionException If value cannot be converted. 1042 */ 1043 public ObjectMap getObjectMap(String key, ObjectMap defVal) { 1044 return getWithDefault(key, defVal, ObjectMap.class); 1045 } 1046 1047 /** 1048 * Same as {@link #getObjectMap(String)} but creates a new empty {@link ObjectMap} if it doesn't already exist. 1049 * 1050 * @param key The key. 1051 * @param createIfNotExists If mapping doesn't already exist, create one with an empty {@link ObjectMap}. 1052 * @return The converted value, or an empty value if the map contains no mapping for this key. 1053 * @throws InvalidDataConversionException If value cannot be converted. 1054 */ 1055 public ObjectMap getObjectMap(String key, boolean createIfNotExists) { 1056 ObjectMap m = getWithDefault(key, null, ObjectMap.class); 1057 if (m == null && createIfNotExists) { 1058 m = new ObjectMap(); 1059 put(key, m); 1060 } 1061 return m; 1062 } 1063 1064 /** 1065 * Returns the specified entry value converted to a {@link ObjectList}. 1066 * 1067 * <p> 1068 * Shortcut for <code>get(key, ObjectList.<jk>class</jk>)</code>. 1069 * 1070 * @param key The key. 1071 * @return The converted value, or <jk>null</jk> if the map contains no mapping for this key. 1072 * @throws InvalidDataConversionException If value cannot be converted. 1073 */ 1074 public ObjectList getObjectList(String key) { 1075 return get(key, ObjectList.class); 1076 } 1077 1078 /** 1079 * Returns the specified entry value converted to a {@link ObjectList}. 1080 * 1081 * <p> 1082 * Shortcut for <code>getWithDefault(key, defVal, ObjectList.<jk>class</jk>)</code>. 1083 * 1084 * @param key The key. 1085 * @param defVal The default value if the map doesn't contain the specified mapping. 1086 * @return The converted value, or the default value if the map contains no mapping for this key. 1087 * @throws InvalidDataConversionException If value cannot be converted. 1088 */ 1089 public ObjectList getObjectList(String key, ObjectList defVal) { 1090 return getWithDefault(key, defVal, ObjectList.class); 1091 } 1092 1093 /** 1094 * Same as {@link #getObjectList(String)} but creates a new empty {@link ObjectList} if it doesn't already exist. 1095 * 1096 * @param key The key. 1097 * @param createIfNotExists If mapping doesn't already exist, create one with an empty {@link ObjectList}. 1098 * @return The converted value, or an empty value if the map contains no mapping for this key. 1099 * @throws InvalidDataConversionException If value cannot be converted. 1100 */ 1101 public ObjectList getObjectList(String key, boolean createIfNotExists) { 1102 ObjectList m = getWithDefault(key, null, ObjectList.class); 1103 if (m == null && createIfNotExists) { 1104 m = new ObjectList(); 1105 put(key, m); 1106 } 1107 return m; 1108 } 1109 1110 /** 1111 * Returns the first entry that exists converted to a {@link String}. 1112 * 1113 * <p> 1114 * Shortcut for <code>find(String.<jk>class</jk>, keys)</code>. 1115 * 1116 * @param keys The list of keys to look for. 1117 * @return 1118 * The converted value of the first key in the list that has an entry in this map, or <jk>null</jk> if the map 1119 * contains no mapping for any of the keys. 1120 */ 1121 public String findString(String... keys) { 1122 return find(String.class, keys); 1123 } 1124 1125 /** 1126 * Returns the first entry that exists converted to an {@link Integer}. 1127 * 1128 * <p> 1129 * Shortcut for <code>find(Integer.<jk>class</jk>, keys)</code>. 1130 * 1131 * @param keys The list of keys to look for. 1132 * @return 1133 * The converted value of the first key in the list that has an entry in this map, or <jk>null</jk> if the map 1134 * contains no mapping for any of the keys. 1135 * @throws InvalidDataConversionException If value cannot be converted. 1136 */ 1137 public Integer findInt(String... keys) { 1138 return find(Integer.class, keys); 1139 } 1140 1141 /** 1142 * Returns the first entry that exists converted to a {@link Long}. 1143 * 1144 * <p> 1145 * Shortcut for <code>find(Long.<jk>class</jk>, keys)</code>. 1146 * 1147 * @param keys The list of keys to look for. 1148 * @return 1149 * The converted value of the first key in the list that has an entry in this map, or <jk>null</jk> if the map 1150 * contains no mapping for any of the keys. 1151 * @throws InvalidDataConversionException If value cannot be converted. 1152 */ 1153 public Long findLong(String... keys) { 1154 return find(Long.class, keys); 1155 } 1156 1157 /** 1158 * Returns the first entry that exists converted to a {@link Boolean}. 1159 * 1160 * <p> 1161 * Shortcut for <code>find(Boolean.<jk>class</jk>, keys)</code>. 1162 * 1163 * @param keys The list of keys to look for. 1164 * @return 1165 * The converted value of the first key in the list that has an entry in this map, or <jk>null</jk> if the map 1166 * contains no mapping for any of the keys. 1167 * @throws InvalidDataConversionException If value cannot be converted. 1168 */ 1169 public Boolean findBoolean(String... keys) { 1170 return find(Boolean.class, keys); 1171 } 1172 1173 /** 1174 * Returns the first entry that exists converted to a {@link Map}. 1175 * 1176 * <p> 1177 * Shortcut for <code>find(Map.<jk>class</jk>, keys)</code>. 1178 * 1179 * @param keys The list of keys to look for. 1180 * @return 1181 * The converted value of the first key in the list that has an entry in this map, or <jk>null</jk> if the map 1182 * contains no mapping for any of the keys. 1183 * @throws InvalidDataConversionException If value cannot be converted. 1184 */ 1185 public Map<?,?> findMap(String... keys) { 1186 return find(Map.class, keys); 1187 } 1188 1189 /** 1190 * Returns the first entry that exists converted to a {@link List}. 1191 * 1192 * <p> 1193 * Shortcut for <code>find(List.<jk>class</jk>, keys)</code>. 1194 * 1195 * @param keys The list of keys to look for. 1196 * @return 1197 * The converted value of the first key in the list that has an entry in this map, or <jk>null</jk> if the map 1198 * contains no mapping for any of the keys. 1199 * @throws InvalidDataConversionException If value cannot be converted. 1200 */ 1201 public List<?> findList(String... keys) { 1202 return find(List.class, keys); 1203 } 1204 1205 /** 1206 * Returns the first entry that exists converted to a {@link ObjectMap}. 1207 * 1208 * <p> 1209 * Shortcut for <code>find(ObjectMap.<jk>class</jk>, keys)</code>. 1210 * 1211 * @param keys The list of keys to look for. 1212 * @return 1213 * The converted value of the first key in the list that has an entry in this map, or <jk>null</jk> if the map 1214 * contains no mapping for any of the keys. 1215 * @throws InvalidDataConversionException If value cannot be converted. 1216 */ 1217 public ObjectMap findObjectMap(String... keys) { 1218 return find(ObjectMap.class, keys); 1219 } 1220 1221 /** 1222 * Returns the first entry that exists converted to a {@link ObjectList}. 1223 * 1224 * <p> 1225 * Shortcut for <code>find(ObjectList.<jk>class</jk>, keys)</code>. 1226 * 1227 * @param keys The list of keys to look for. 1228 * @return 1229 * The converted value of the first key in the list that has an entry in this map, or <jk>null</jk> if the map 1230 * contains no mapping for any of the keys. 1231 * @throws InvalidDataConversionException If value cannot be converted. 1232 */ 1233 public ObjectList findObjectList(String... keys) { 1234 return find(ObjectList.class, keys); 1235 } 1236 1237 /** 1238 * Returns the first key in the map. 1239 * 1240 * @return The first key in the map, or <jk>null</jk> if the map is empty. 1241 */ 1242 public String getFirstKey() { 1243 return isEmpty() ? null : keySet().iterator().next(); 1244 } 1245 1246 /** 1247 * Returns the class type of the object at the specified index. 1248 * 1249 * @param key The key into this map. 1250 * @return 1251 * The data type of the object at the specified key, or <jk>null</jk> if the value is null or does not exist. 1252 */ 1253 public ClassMeta<?> getClassMeta(String key) { 1254 return bs().getClassMetaForObject(get(key)); 1255 } 1256 1257 /** 1258 * Equivalent to calling <code>get(class,key,def)</code> followed by <code>remove(key);</code> 1259 * @param key The key. 1260 * @param defVal The default value if the map doesn't contain the specified mapping. 1261 * @param type The class type. 1262 * 1263 * @param <T> The class type. 1264 * @return The converted value, or the default value if the map contains no mapping for this key. 1265 * @throws InvalidDataConversionException If value cannot be converted. 1266 */ 1267 public <T> T removeWithDefault(String key, T defVal, Class<T> type) { 1268 T t = getWithDefault(key, defVal, type); 1269 remove(key); 1270 return t; 1271 } 1272 1273 1274 /** 1275 * Convenience method for removing several keys at once. 1276 * 1277 * @param keys The list of keys to remove. 1278 */ 1279 public void removeAll(Collection<String> keys) { 1280 for (String k : keys) 1281 remove(k); 1282 } 1283 1284 /** 1285 * Convenience method for removing several keys at once. 1286 * 1287 * @param keys The list of keys to remove. 1288 */ 1289 public void removeAll(String... keys) { 1290 for (String k : keys) 1291 remove(k); 1292 } 1293 1294 /** 1295 * The opposite of {@link #removeAll(String...)}. 1296 * 1297 * <p> 1298 * Discards all keys from this map that aren't in the specified list. 1299 * 1300 * @param keys The keys to keep. 1301 * @return This map. 1302 */ 1303 public ObjectMap keepAll(String...keys) { 1304 for (Iterator<String> i = keySet().iterator(); i.hasNext();) { 1305 boolean remove = true; 1306 String key = i.next(); 1307 for (String k : keys) { 1308 if (k.equals(key)) { 1309 remove = false; 1310 break; 1311 } 1312 } 1313 if (remove) 1314 i.remove(); 1315 } 1316 return this; 1317 } 1318 1319 @Override /* Map */ 1320 public boolean containsKey(Object key) { 1321 if (super.containsKey(key)) 1322 return true; 1323 if (inner != null) 1324 return inner.containsKey(key); 1325 return false; 1326 } 1327 1328 /** 1329 * Returns <jk>true</jk> if the map contains the specified entry and the value is not null nor an empty string. 1330 * 1331 * <p> 1332 * Always returns <jk>false</jk> if the value is not a {@link CharSequence}. 1333 * 1334 * @param key The key. 1335 * @return <jk>true</jk> if the map contains the specified entry and the value is not null nor an empty string. 1336 */ 1337 public boolean containsKeyNotEmpty(String key) { 1338 Object val = get(key); 1339 if (val == null) 1340 return false; 1341 if (val instanceof CharSequence) 1342 return ! StringUtils.isEmpty(val); 1343 return false; 1344 } 1345 1346 /** 1347 * Returns <jk>true</jk> if this map contains the specified key, ignoring the inner map if it exists. 1348 * 1349 * @param key The key to look up. 1350 * @return <jk>true</jk> if this map contains the specified key. 1351 */ 1352 public boolean containsOuterKey(Object key) { 1353 return super.containsKey(key); 1354 } 1355 1356 /** 1357 * Returns a copy of this <code>ObjectMap</code> with only the specified keys. 1358 * 1359 * @param keys The keys of the entries to copy. 1360 * @return A new map with just the keys and values from this map. 1361 */ 1362 public ObjectMap include(String...keys) { 1363 ObjectMap m2 = new ObjectMap(); 1364 for (Map.Entry<String,Object> e : this.entrySet()) 1365 for (String k : keys) 1366 if (k.equals(e.getKey())) 1367 m2.put(k, e.getValue()); 1368 return m2; 1369 } 1370 1371 /** 1372 * Returns a copy of this <code>ObjectMap</code> without the specified keys. 1373 * 1374 * @param keys The keys of the entries not to copy. 1375 * @return A new map without the keys and values from this map. 1376 */ 1377 public ObjectMap exclude(String...keys) { 1378 ObjectMap m2 = new ObjectMap(); 1379 for (Map.Entry<String,Object> e : this.entrySet()) { 1380 boolean exclude = false; 1381 for (String k : keys) 1382 if (k.equals(e.getKey())) 1383 exclude = true; 1384 if (! exclude) 1385 m2.put(e.getKey(), e.getValue()); 1386 } 1387 return m2; 1388 } 1389 1390 /** 1391 * Sets a value in this map if the entry does not exist or the value is <jk>null</jk>. 1392 * 1393 * @param key The map key. 1394 * @param val The value to set if the current value does not exist or is <jk>null</jk>. 1395 * @return This object (for method chaining). 1396 */ 1397 public ObjectMap putIfNull(String key, Object val) { 1398 Object o = get(key); 1399 if (o == null) 1400 put(key, val); 1401 return this; 1402 } 1403 1404 /** 1405 * Sets a value in this map if the entry does not exist or the value is <jk>null</jk> or an empty string. 1406 * 1407 * @param key The map key. 1408 * @param val The value to set if the current value does not exist or is <jk>null</jk> or an empty string. 1409 * @return This object (for method chaining). 1410 */ 1411 public ObjectMap putIfEmpty(String key, Object val) { 1412 Object o = get(key); 1413 if (o == null || o.toString().isEmpty()) 1414 put(key, val); 1415 return this; 1416 } 1417 1418 /** 1419 * Adds a mapping if the specified key doesn't exist. 1420 * 1421 * @param key The map key. 1422 * @param val The value to set if the current value does not exist or is <jk>null</jk> or an empty string. 1423 * @return This object (for method chaining). 1424 */ 1425 public ObjectMap putIfNotExists(String key, Object val) { 1426 if (! containsKey(key)) 1427 put(key, val); 1428 return this; 1429 } 1430 1431 /** 1432 * Converts this map into an object of the specified type. 1433 * 1434 * <p> 1435 * If this map contains a <js>"_type"</js> entry, it must be the same as or a subclass of the <code>type</code>. 1436 * 1437 * @param <T> The class type to convert this map object to. 1438 * @param type The class type to convert this map object to. 1439 * @return The new object. 1440 * @throws ClassCastException 1441 * If the <js>"_type"</js> entry is present and not assignable from <code>type</code> 1442 */ 1443 @SuppressWarnings("unchecked") 1444 public <T> T cast(Class<T> type) { 1445 BeanSession bs = bs(); 1446 ClassMeta<?> c2 = bs.getClassMeta(type); 1447 String typePropertyName = bs.getBeanTypePropertyName(c2); 1448 ClassMeta<?> c1 = bs.getBeanRegistry().getClassMeta((String)get(typePropertyName)); 1449 ClassMeta<?> c = c1 == null ? c2 : narrowClassMeta(c1, c2); 1450 if (c.isObject()) 1451 return (T)this; 1452 return (T)cast2(c); 1453 } 1454 1455 /** 1456 * Same as {@link #cast(Class)}, except allows you to specify a {@link ClassMeta} parameter. 1457 * 1458 * @param <T> The class type to convert this map object to. 1459 * @param cm The class type to convert this map object to. 1460 * @return The new object. 1461 * @throws ClassCastException 1462 * If the <js>"_type"</js> entry is present and not assignable from <code>type</code> 1463 */ 1464 @SuppressWarnings({"unchecked"}) 1465 public <T> T cast(ClassMeta<T> cm) { 1466 BeanSession bs = bs(); 1467 ClassMeta<?> c1 = bs.getBeanRegistry().getClassMeta((String)get(bs.getBeanTypePropertyName(cm))); 1468 ClassMeta<?> c = narrowClassMeta(c1, cm); 1469 return (T)cast2(c); 1470 } 1471 1472 /* 1473 * Combines the class specified by a "_type" attribute with the ClassMeta 1474 * passed in through the cast(ClassMeta) method. 1475 * The rule is that child classes supersede parent classes, and c2 supersedes c1 1476 * if one isn't the parent of another. 1477 */ 1478 private ClassMeta<?> narrowClassMeta(ClassMeta<?> c1, ClassMeta<?> c2) { 1479 if (c1 == null) 1480 return c2; 1481 ClassMeta<?> c = getNarrowedClassMeta(c1, c2); 1482 if (c1.isMap()) { 1483 ClassMeta<?> k = getNarrowedClassMeta(c1.getKeyType(), c2.getKeyType()); 1484 ClassMeta<?> v = getNarrowedClassMeta(c1.getValueType(), c2.getValueType()); 1485 return bs().getClassMeta(c.getInnerClass(), k, v); 1486 } 1487 if (c1.isCollection()) { 1488 ClassMeta<?> e = getNarrowedClassMeta(c1.getElementType(), c2.getElementType()); 1489 return bs().getClassMeta(c.getInnerClass(), e); 1490 } 1491 return c; 1492 } 1493 1494 /* 1495 * If c1 is a child of c2 or the same as c2, returns c1. 1496 * Otherwise, returns c2. 1497 */ 1498 private static ClassMeta<?> getNarrowedClassMeta(ClassMeta<?> c1, ClassMeta<?> c2) { 1499 if (c2 == null || isParentClass(c2.getInnerClass(), c1.getInnerClass())) 1500 return c1; 1501 return c2; 1502 } 1503 1504 /* 1505 * Converts this map to the specified class type. 1506 */ 1507 @SuppressWarnings({"unchecked","rawtypes"}) 1508 private <T> T cast2(ClassMeta<T> cm) { 1509 1510 BeanSession bs = bs(); 1511 try { 1512 Object value = get("value"); 1513 1514 if (cm.isMap()) { 1515 Map m2 = (cm.canCreateNewInstance() ? (Map)cm.newInstance() : new ObjectMap(bs)); 1516 ClassMeta<?> kType = cm.getKeyType(), vType = cm.getValueType(); 1517 for (Map.Entry<String,Object> e : entrySet()) { 1518 Object k = e.getKey(); 1519 Object v = e.getValue(); 1520 if (! k.equals(bs.getBeanTypePropertyName(cm))) { 1521 1522 // Attempt to recursively cast child maps. 1523 if (v instanceof ObjectMap) 1524 v = ((ObjectMap)v).cast(vType); 1525 1526 k = (kType.isString() ? k : bs.convertToType(k, kType)); 1527 v = (vType.isObject() ? v : bs.convertToType(v, vType)); 1528 1529 m2.put(k, v); 1530 } 1531 } 1532 return (T)m2; 1533 1534 } else if (cm.isBean()) { 1535 BeanMap<? extends T> bm = bs.newBeanMap(cm.getInnerClass()); 1536 1537 // Iterate through all the entries in the map and set the individual field values. 1538 for (Map.Entry<String,Object> e : entrySet()) { 1539 String k = e.getKey(); 1540 Object v = e.getValue(); 1541 if (! k.equals(bs.getBeanTypePropertyName(cm))) { 1542 1543 // Attempt to recursively cast child maps. 1544 if (v instanceof ObjectMap) 1545 v = ((ObjectMap)v).cast(bm.getProperty(k).getMeta().getClassMeta()); 1546 1547 bm.put(k, v); 1548 } 1549 } 1550 1551 return bm.getBean(); 1552 1553 } else if (cm.isCollectionOrArray()) { 1554 List items = (List)get("items"); 1555 return bs.convertToType(items, cm); 1556 1557 } else if (value != null) { 1558 return bs.convertToType(value, cm); 1559 } 1560 1561 } catch (Exception e) { 1562 throw new BeanRuntimeException(e, cm.innerClass, 1563 "Error occurred attempting to cast to an object of type ''{0}''", cm.innerClass.getName()); 1564 } 1565 1566 throw new BeanRuntimeException(cm.innerClass, 1567 "Cannot convert to class type ''{0}''. Only beans and maps can be converted using this method.", 1568 cm.innerClass.getName()); 1569 } 1570 1571 private PojoRest getPojoRest() { 1572 if (pojoRest == null) 1573 pojoRest = new PojoRest(this); 1574 return pojoRest; 1575 } 1576 1577 /** 1578 * Serialize this object into a string using the specified serializer. 1579 * 1580 * @param serializer The serializer to use to convert this object to a string. 1581 * @return This object serialized as a string. 1582 * @throws SerializeException If a problem occurred trying to convert the output. 1583 */ 1584 public String toString(WriterSerializer serializer) throws SerializeException { 1585 return serializer.serialize(this); 1586 } 1587 1588 /** 1589 * Serialize this object into a JSON string using the {@link JsonSerializer#DEFAULT} serializer. 1590 */ 1591 @Override /* Object */ 1592 public String toString() { 1593 try { 1594 return this.toString(SimpleJsonSerializer.DEFAULT); 1595 } catch (SerializeException e) { 1596 return e.getLocalizedMessage(); 1597 } 1598 } 1599 1600 /** 1601 * Convenience method for serializing this map to the specified <code>Writer</code> using the 1602 * {@link JsonSerializer#DEFAULT} serializer. 1603 * 1604 * @param w The writer to serialize this object to. 1605 * @return This object (for method chaining). 1606 * @throws IOException If a problem occurred trying to write to the writer. 1607 * @throws SerializeException If a problem occurred trying to convert the output. 1608 */ 1609 public ObjectMap serializeTo(Writer w) throws IOException, SerializeException { 1610 JsonSerializer.DEFAULT.serialize(this); 1611 return this; 1612 } 1613 1614 /** 1615 * Returns <jk>true</jk> if this map is unmodifiable. 1616 * 1617 * @return <jk>true</jk> if this map is unmodifiable. 1618 */ 1619 public boolean isUnmodifiable() { 1620 return false; 1621 } 1622 1623 /** 1624 * Returns a modifiable copy of this map if it's unmodifiable. 1625 * 1626 * @return A modifiable copy of this map if it's unmodifiable, or this map if it is already modifiable. 1627 */ 1628 public ObjectMap modifiable() { 1629 if (isUnmodifiable()) 1630 return new ObjectMap(this); 1631 return this; 1632 } 1633 1634 /** 1635 * Returns an unmodifiable copy of this map if it's modifiable. 1636 * 1637 * @return An unmodifiable copy of this map if it's modifiable, or this map if it is already unmodifiable. 1638 */ 1639 public ObjectMap unmodifiable() { 1640 if (this instanceof UnmodifiableObjectMap) 1641 return this; 1642 return new UnmodifiableObjectMap(this); 1643 } 1644 1645 @Override /* Map */ 1646 public Set<String> keySet() { 1647 if (inner == null) 1648 return super.keySet(); 1649 LinkedHashSet<String> s = new LinkedHashSet<>(); 1650 s.addAll(inner.keySet()); 1651 s.addAll(super.keySet()); 1652 return s; 1653 } 1654 1655 @Override /* Map */ 1656 public Set<Map.Entry<String,Object>> entrySet() { 1657 if (inner == null) 1658 return super.entrySet(); 1659 1660 final Set<String> keySet = keySet(); 1661 final Iterator<String> keys = keySet.iterator(); 1662 1663 return new AbstractSet<Map.Entry<String,Object>>() { 1664 1665 @Override /* Iterable */ 1666 public Iterator<Map.Entry<String,Object>> iterator() { 1667 1668 return new Iterator<Map.Entry<String,Object>>() { 1669 1670 @Override /* Iterator */ 1671 public boolean hasNext() { 1672 return keys.hasNext(); 1673 } 1674 1675 @Override /* Iterator */ 1676 public Map.Entry<String,Object> next() { 1677 return new Map.Entry<String,Object>() { 1678 String key = keys.next(); 1679 1680 @Override /* Map.Entry */ 1681 public String getKey() { 1682 return key; 1683 } 1684 1685 @Override /* Map.Entry */ 1686 public Object getValue() { 1687 return get(key); 1688 } 1689 1690 @Override /* Map.Entry */ 1691 public Object setValue(Object object) { 1692 return put(key, object); 1693 } 1694 }; 1695 } 1696 1697 @Override /* Iterator */ 1698 public void remove() { 1699 throw new UnsupportedOperationException(); 1700 } 1701 }; 1702 } 1703 1704 @Override /* Set */ 1705 public int size() { 1706 return keySet.size(); 1707 } 1708 }; 1709 } 1710 1711 private static final class UnmodifiableObjectMap extends ObjectMap { 1712 private static final long serialVersionUID = 1L; 1713 1714 UnmodifiableObjectMap(ObjectMap contents) { 1715 super(); 1716 if (contents != null) { 1717 for (Map.Entry<String,Object> e : contents.entrySet()) { 1718 super.put(e.getKey(), e.getValue()); 1719 } 1720 } 1721 } 1722 1723 @Override 1724 public final Object put(String key, Object val) { 1725 throw new UnsupportedOperationException("ObjectMap is read-only."); 1726 } 1727 1728 @Override 1729 public final Object remove(Object key) { 1730 throw new UnsupportedOperationException("ObjectMap is read-only."); 1731 } 1732 1733 @Override 1734 public final boolean isUnmodifiable() { 1735 return true; 1736 } 1737 } 1738 1739 private BeanSession bs() { 1740 if (session == null) 1741 session = BeanContext.DEFAULT.createBeanSession(); 1742 return session; 1743 } 1744}