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