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.ArrayUtils.*; 016import static org.apache.juneau.internal.ClassUtils.*; 017import static org.apache.juneau.internal.CollectionUtils.*; 018import static org.apache.juneau.internal.StringUtils.*; 019import static org.apache.juneau.internal.ObjectUtils.*; 020 021import java.lang.annotation.*; 022import java.lang.reflect.*; 023import java.net.*; 024import java.net.URI; 025import java.util.*; 026 027import org.apache.juneau.annotation.*; 028import org.apache.juneau.collections.*; 029import org.apache.juneau.internal.*; 030import org.apache.juneau.parser.*; 031import org.apache.juneau.reflect.*; 032import org.apache.juneau.serializer.*; 033import org.apache.juneau.transform.*; 034import org.apache.juneau.transforms.*; 035 036/** 037 * Contains metadata about a bean property. 038 * 039 * <p> 040 * Contains information such as type of property (e.g. field/getter/setter), class type of property value, and whether 041 * any transforms are associated with this property. 042 * 043 * <p> 044 * Developers will typically not need access to this class. The information provided by it is already exposed through 045 * several methods on the {@link BeanMap} API. 046 */ 047@SuppressWarnings({ "rawtypes", "unchecked" }) 048public final class BeanPropertyMeta { 049 050 final BeanMeta<?> beanMeta; // The bean that this property belongs to. 051 private final BeanContext beanContext; // The context that created this meta. 052 053 private final String name; // The name of the property. 054 private final Field field; // The bean property field (if it has one). 055 private final Field innerField; // The bean property field (if it has one). 056 private final Method getter, setter, extraKeys; // The bean property getter and setter. 057 private final boolean isUri; // True if this is a URL/URI or annotated with @URI. 058 private final boolean isDyna, isDynaGetterMap; // This is a dyna property (i.e. name="*") 059 060 private final ClassMeta<?> 061 rawTypeMeta, // The real class type of the bean property. 062 typeMeta; // The transformed class type of the bean property. 063 064 private final String[] properties; // The value of the @Beanp(properties) annotation. 065 private final PojoSwap swap; // PojoSwap defined only via @Beanp annotation. 066 067 private final BeanRegistry beanRegistry; 068 069 private final Object overrideValue; // The bean property value (if it's an overridden delegate). 070 private final BeanPropertyMeta delegateFor; // The bean property that this meta is a delegate for. 071 private final boolean canRead, canWrite, readOnly, writeOnly; 072 private final int hashCode; 073 074 /** 075 * Creates a builder for {@link #BeanPropertyMeta} objects. 076 * 077 * @param beanMeta The metadata on the bean 078 * @param name The bean property name. 079 * @return A new builder. 080 */ 081 public static Builder builder(BeanMeta<?> beanMeta, String name) { 082 return new Builder(beanMeta, name); 083 } 084 085 /** 086 * BeanPropertyMeta builder class. 087 */ 088 public static final class Builder { 089 BeanMeta<?> beanMeta; 090 BeanContext beanContext; 091 String name; 092 Field field, innerField; 093 Method getter, setter, extraKeys; 094 boolean isConstructorArg, isUri, isDyna, isDynaGetterMap; 095 ClassMeta<?> rawTypeMeta, typeMeta; 096 String[] properties; 097 PojoSwap swap; 098 BeanRegistry beanRegistry; 099 Object overrideValue; 100 BeanPropertyMeta delegateFor; 101 boolean canRead, canWrite, readOnly, writeOnly; 102 103 Builder(BeanMeta<?> beanMeta, String name) { 104 this.beanMeta = beanMeta; 105 this.beanContext = beanMeta.ctx; 106 this.name = name; 107 } 108 109 /** 110 * Sets the raw metadata type for this bean property. 111 * 112 * @param rawMetaType The raw metadata type for this bean property. 113 * @return This object (for method chaining(). 114 */ 115 public Builder rawMetaType(ClassMeta<?> rawMetaType) { 116 this.rawTypeMeta = rawMetaType; 117 this.typeMeta = rawTypeMeta; 118 return this; 119 } 120 121 /** 122 * Sets the bean registry to use with this bean property. 123 * 124 * @param beanRegistry The bean registry to use with this bean property. 125 * @return This object (for method chaining(). 126 */ 127 public Builder beanRegistry(BeanRegistry beanRegistry) { 128 this.beanRegistry = beanRegistry; 129 return this; 130 } 131 132 /** 133 * Sets the overridden value of this bean property. 134 * 135 * @param overrideValue The overridden value of this bean property. 136 * @return This object (for method chaining(). 137 */ 138 public Builder overrideValue(Object overrideValue) { 139 this.overrideValue = overrideValue; 140 return this; 141 } 142 143 /** 144 * Sets the original bean property that this one is overriding. 145 * 146 * @param delegateFor The original bean property that this one is overriding. 147 * @return This object (for method chaining(). 148 */ 149 public Builder delegateFor(BeanPropertyMeta delegateFor) { 150 this.delegateFor = delegateFor; 151 return this; 152 } 153 154 Builder canRead() { 155 this.canRead = true; 156 return this; 157 } 158 159 Builder canWrite() { 160 this.canWrite = true; 161 return this; 162 } 163 164 @SuppressWarnings("deprecation") 165 boolean validate(BeanContext bc, BeanRegistry parentBeanRegistry, Map<Class<?>,Class<?>[]> typeVarImpls, Set<String> bpro, Set<String> bpwo) throws Exception { 166 167 AList<Class<?>> bdClasses = AList.of(); 168 169 if (field == null && getter == null && setter == null) 170 return false; 171 172 if (field == null && setter == null && bc.isBeansRequireSettersForGetters() && ! isConstructorArg) 173 return false; 174 175 canRead |= (field != null || getter != null); 176 canWrite |= (field != null || setter != null); 177 178 if (innerField != null) { 179 BeanProperty px = innerField.getAnnotation(BeanProperty.class); 180 List<Beanp> lp = bc.getAnnotations(Beanp.class, innerField); 181 if (field != null || px != null || lp.size() > 0) { 182 // Only use field type if it's a bean property or has @Beanp annotation. 183 // Otherwise, we want to infer the type from the getter or setter. 184 rawTypeMeta = bc.resolveClassMeta(px, last(lp), innerField.getGenericType(), typeVarImpls); 185 isUri |= (rawTypeMeta.isUri()); 186 } 187 if (px != null) { 188 if (! px.properties().isEmpty()) 189 properties = split(px.properties()); 190 bdClasses.a(px.beanDictionary()); 191 } 192 for (Beanp p : lp) { 193 if (! p.properties().isEmpty()) 194 properties = split(p.properties()); 195 bdClasses.a(p.dictionary()); 196 if (! p.ro().isEmpty()) 197 readOnly = Boolean.valueOf(p.ro()); 198 if (! p.wo().isEmpty()) 199 writeOnly = Boolean.valueOf(p.wo()); 200 } 201 for (Swap s : bc.getAnnotations(Swap.class, innerField)) 202 swap = getPropertyPojoSwap(s); 203 isUri |= last(bc.getAnnotations(org.apache.juneau.annotation.URI.class, innerField)) != null; 204 } 205 206 if (getter != null) { 207 BeanProperty px = MethodInfo.of(getter).getLastAnnotation(BeanProperty.class); 208 List<Beanp> lp = bc.getAnnotations(Beanp.class, getter); 209 if (rawTypeMeta == null) 210 rawTypeMeta = bc.resolveClassMeta(px, last(lp), getter.getGenericReturnType(), typeVarImpls); 211 isUri |= (rawTypeMeta.isUri() || bc.hasAnnotation(org.apache.juneau.annotation.URI.class, getter)); 212 if (px != null) { 213 if (properties != null && ! px.properties().isEmpty()) 214 properties = split(px.properties()); 215 bdClasses.a(px.beanDictionary()); 216 } 217 for (Beanp p : lp) { 218 if (properties != null && ! p.properties().isEmpty()) 219 properties = split(p.properties()); 220 bdClasses.a(p.dictionary()); 221 if (! p.ro().isEmpty()) 222 readOnly = Boolean.valueOf(p.ro()); 223 if (! p.wo().isEmpty()) 224 writeOnly = Boolean.valueOf(p.wo()); 225 } 226 for (Swap s : bc.getAnnotations(Swap.class, getter)) 227 swap = getPropertyPojoSwap(s); 228 } 229 230 if (setter != null) { 231 BeanProperty px = MethodInfo.of(setter).getLastAnnotation(BeanProperty.class); 232 List<Beanp> lp = bc.getAnnotations(Beanp.class, setter); 233 if (rawTypeMeta == null) 234 rawTypeMeta = bc.resolveClassMeta(px, last(lp), setter.getGenericParameterTypes()[0], typeVarImpls); 235 isUri |= (rawTypeMeta.isUri() || bc.hasAnnotation(org.apache.juneau.annotation.URI.class, setter)); 236 if (px != null) { 237 if (swap == null) 238 swap = getPropertyPojoSwap(px); 239 if (properties != null && ! px.properties().isEmpty()) 240 properties = split(px.properties()); 241 bdClasses.a(px.beanDictionary()); 242 } 243 for (Beanp p : lp) { 244 if (swap == null) 245 swap = getPropertyPojoSwap(p); 246 if (properties != null && ! p.properties().isEmpty()) 247 properties = split(p.properties()); 248 bdClasses.a(p.dictionary()); 249 if (! p.ro().isEmpty()) 250 readOnly = Boolean.valueOf(p.ro()); 251 if (! p.wo().isEmpty()) 252 writeOnly = Boolean.valueOf(p.wo()); 253 } 254 for (Swap s : bc.getAnnotations(Swap.class, setter)) 255 swap = getPropertyPojoSwap(s); 256 } 257 258 if (rawTypeMeta == null) 259 return false; 260 261 this.beanRegistry = new BeanRegistry(beanContext, parentBeanRegistry, bdClasses.toArray(new Class<?>[0])); 262 263 isDyna = "*".equals(name); 264 265 // Do some annotation validation. 266 ClassInfo ci = rawTypeMeta.getInfo(); 267 if (getter != null) { 268 Class<?>[] pt = getter.getParameterTypes(); 269 if (isDyna) { 270 if (ci.isChildOf(Map.class) && pt.length == 0) { 271 isDynaGetterMap = true; 272 } else if (pt.length == 1 && pt[0] == String.class) { 273 // OK. 274 } else { 275 return false; 276 } 277 } else { 278 if (! ci.isChildOf(getter.getReturnType())) 279 return false; 280 } 281 } 282 if (setter != null) { 283 Class<?>[] pt = setter.getParameterTypes(); 284 if (isDyna) { 285 if (pt.length == 2 && pt[0] == String.class) { 286 // OK. 287 } else { 288 return false; 289 } 290 } else { 291 if (pt.length != 1) 292 return false; 293 if (! ci.isChildOf(pt[0])) 294 return false; 295 } 296 } 297 if (field != null) { 298 if (isDyna) { 299 if (! ClassInfo.of(field.getType()).isChildOf(Map.class)) 300 return false; 301 } else { 302 if (! ci.isChildOf(field.getType())) 303 return false; 304 } 305 } 306 307 if (isDyna) { 308 rawTypeMeta = rawTypeMeta.getValueType(); 309 if (rawTypeMeta == null) 310 rawTypeMeta = beanContext.object(); 311 } 312 if (rawTypeMeta == null) 313 return false; 314 315 if (typeMeta == null) 316 typeMeta = (swap != null ? beanContext.getClassMeta(swap.getSwapClass().innerType()) : rawTypeMeta == null ? beanContext.object() : rawTypeMeta); 317 if (typeMeta == null) 318 typeMeta = rawTypeMeta; 319 320 if (bpro.contains(name) || bpro.contains("*")) 321 readOnly = true; 322 if (bpwo.contains(name) || bpwo.contains("*")) 323 writeOnly = true; 324 325 return true; 326 } 327 328 /** 329 * @return A new BeanPropertyMeta object using this builder. 330 */ 331 public BeanPropertyMeta build() { 332 return new BeanPropertyMeta(this); 333 } 334 335 @SuppressWarnings("deprecation") 336 private PojoSwap getPropertyPojoSwap(BeanProperty p) throws Exception { 337 if (! p.format().isEmpty()) 338 return castOrCreate(PojoSwap.class, StringFormatSwap.class, false, p.format()); 339 return null; 340 } 341 342 private PojoSwap getPropertyPojoSwap(Beanp p) throws Exception { 343 if (! p.format().isEmpty()) 344 return castOrCreate(PojoSwap.class, StringFormatSwap.class, false, p.format()); 345 return null; 346 } 347 348 private PojoSwap getPropertyPojoSwap(Swap s) throws Exception { 349 Class<?> c = s.value(); 350 if (c == Null.class) 351 c = s.impl(); 352 if (c == Null.class) 353 return null; 354 ClassInfo ci = ClassInfo.of(c); 355 if (ci.isChildOf(PojoSwap.class)) { 356 PojoSwap ps = castOrCreate(PojoSwap.class, c); 357 if (ps.forMediaTypes() != null) 358 throw new RuntimeException("TODO - Media types on swaps not yet supported on bean properties."); 359 if (ps.withTemplate() != null) 360 throw new RuntimeException("TODO - Templates on swaps not yet supported on bean properties."); 361 return ps; 362 } 363 if (ci.isChildOf(Surrogate.class)) 364 throw new RuntimeException("TODO - Surrogate swaps not yet supported on bean properties."); 365 throw new BasicRuntimeException("Invalid class used in @Swap annotation. Must be a subclass of PojoSwap or Surrogate.", c); 366 } 367 368 BeanPropertyMeta.Builder setGetter(Method getter) { 369 setAccessible(getter); 370 this.getter = getter; 371 return this; 372 } 373 374 BeanPropertyMeta.Builder setSetter(Method setter) { 375 setAccessible(setter); 376 this.setter = setter; 377 return this; 378 } 379 380 BeanPropertyMeta.Builder setField(Field field) { 381 setAccessible(field); 382 this.field = field; 383 this.innerField = field; 384 return this; 385 } 386 387 BeanPropertyMeta.Builder setInnerField(Field innerField) { 388 this.innerField = innerField; 389 return this; 390 } 391 392 BeanPropertyMeta.Builder setExtraKeys(Method extraKeys) { 393 setAccessible(extraKeys); 394 this.extraKeys = extraKeys; 395 return this; 396 } 397 398 BeanPropertyMeta.Builder setAsConstructorArg() { 399 this.isConstructorArg = true; 400 return this; 401 } 402 403 } 404 405 /** 406 * Creates a new BeanPropertyMeta using the contents of the specified builder. 407 * 408 * @param b The builder to copy fields from. 409 */ 410 protected BeanPropertyMeta(BeanPropertyMeta.Builder b) { 411 this.field = b.field; 412 this.innerField = b.innerField; 413 this.getter = b.getter; 414 this.setter = b.setter; 415 this.extraKeys = b.extraKeys; 416 this.isUri = b.isUri; 417 this.beanMeta = b.beanMeta; 418 this.beanContext = b.beanContext; 419 this.name = b.name; 420 this.rawTypeMeta = b.rawTypeMeta; 421 this.typeMeta = b.typeMeta; 422 this.properties = b.properties; 423 this.swap = b.swap; 424 this.beanRegistry = b.beanRegistry; 425 this.overrideValue = b.overrideValue; 426 this.delegateFor = b.delegateFor; 427 this.isDyna = b.isDyna; 428 this.isDynaGetterMap = b.isDynaGetterMap; 429 this.canRead = b.canRead; 430 this.canWrite = b.canWrite; 431 this.readOnly = b.readOnly; 432 this.writeOnly = b.writeOnly; 433 this.hashCode = HashCode.of(beanMeta,name); 434 } 435 436 /** 437 * Returns the name of this bean property. 438 * 439 * @return The name of the bean property. 440 */ 441 public String getName() { 442 return name; 443 } 444 445 /** 446 * Returns the bean meta that this property belongs to. 447 * 448 * @return The bean meta that this property belongs to. 449 */ 450 @BeanIgnore 451 public BeanMeta<?> getBeanMeta() { 452 return beanMeta; 453 } 454 455 /** 456 * Returns the getter method for this property. 457 * 458 * @return The getter method for this bean property, or <jk>null</jk> if there is no getter method. 459 */ 460 public Method getGetter() { 461 return getter; 462 } 463 464 /** 465 * Returns the setter method for this property. 466 * 467 * @return The setter method for this bean property, or <jk>null</jk> if there is no setter method. 468 */ 469 public Method getSetter() { 470 return setter; 471 } 472 473 /** 474 * Returns the field for this property. 475 * 476 * @return The field for this bean property, or <jk>null</jk> if there is no field associated with this bean property. 477 */ 478 public Field getField() { 479 return field; 480 } 481 482 /** 483 * Returns the field for this property even if the field is private. 484 * 485 * @return The field for this bean property, or <jk>null</jk> if there is no field associated with this bean property. 486 */ 487 public Field getInnerField() { 488 return innerField; 489 } 490 491 /** 492 * Returns the {@link ClassMeta} of the class of this property. 493 * 494 * <p> 495 * If this property or the property type class has a {@link PojoSwap} associated with it, this method returns the 496 * transformed class meta. 497 * This matches the class type that is used by the {@link #get(BeanMap,String)} and 498 * {@link #set(BeanMap,String,Object)} methods. 499 * 500 * @return The {@link ClassMeta} of the class of this property. 501 */ 502 public ClassMeta<?> getClassMeta() { 503 return typeMeta; 504 } 505 506 /** 507 * Returns the bean dictionary in use for this bean property. 508 * 509 * <p> 510 * The order of lookup for the dictionary is as follows: 511 * <ol> 512 * <li>Dictionary defined via {@link Beanp#dictionary() @Beanp(dictionary)}. 513 * <li>Dictionary defined via {@link BeanContext#BEAN_beanDictionary} context property. 514 * </ol> 515 * 516 * @return The bean dictionary in use for this bean property. Never <jk>null</jk>. 517 */ 518 public BeanRegistry getBeanRegistry() { 519 return beanRegistry; 520 } 521 522 /** 523 * Returns <jk>true</jk> if this bean property is a URI. 524 * 525 * <p> 526 * A bean property can be considered a URI if any of the following are true: 527 * <ul> 528 * <li>Property class type is {@link URL} or {@link URI}. 529 * <li>Property class type is annotated with {@link org.apache.juneau.annotation.URI @URI}. 530 * <li>Property getter, setter, or field is annotated with {@link org.apache.juneau.annotation.URI @URI}. 531 * </ul> 532 * 533 * @return <jk>true</jk> if this bean property is a URI. 534 */ 535 public boolean isUri() { 536 return isUri; 537 } 538 539 /** 540 * Returns <jk>true</jk> if this bean property is named <js>"*"</js>. 541 * 542 * @return <jk>true</jk> if this bean property is named <js>"*"</js>. 543 */ 544 public boolean isDyna() { 545 return isDyna; 546 } 547 548 /** 549 * Returns the override list of properties defined through a {@link Beanp#properties() @Beanp(bpi)} annotation 550 * on this property. 551 * 552 * @return The list of override properties, or <jk>null</jk> if annotation not specified. 553 */ 554 public String[] getProperties() { 555 return properties; 556 } 557 558 /** 559 * Returns the metadata on the property that this metadata is a delegate for. 560 * 561 * @return the metadata on the property that this metadata is a delegate for, or this object if it's not a delegate. 562 */ 563 public BeanPropertyMeta getDelegateFor() { 564 return delegateFor != null ? delegateFor : this; 565 } 566 567 /** 568 * Equivalent to calling {@link BeanMap#get(Object)}, but is faster since it avoids looking up the property meta. 569 * 570 * @param m The bean map to get the transformed value from. 571 * @param pName 572 * The property name if this is a dyna property (i.e. <js>"*"</js>). 573 * <br>Otherwise can be <jk>null</jk>. 574 * @return 575 * The property value. 576 * <br>Returns <jk>null</jk> if this is a write-only property. 577 */ 578 public Object get(BeanMap<?> m, String pName) { 579 return m.meta.onReadProperty(m.bean, pName, getInner(m, pName)); 580 } 581 582 private Object getInner(BeanMap<?> m, String pName) { 583 try { 584 585 if (writeOnly) 586 return null; 587 588 if (overrideValue != null) 589 return overrideValue; 590 591 // Read-only beans have their properties stored in a cache until getBean() is called. 592 Object bean = m.bean; 593 if (bean == null) 594 return m.propertyCache.get(name); 595 596 return toSerializedForm(m.getBeanSession(), getRaw(m, pName)); 597 598 } catch (Throwable e) { 599 if (beanContext.isIgnoreInvocationExceptionsOnGetters()) { 600 if (rawTypeMeta.isPrimitive()) 601 return rawTypeMeta.getPrimitiveDefault(); 602 return null; 603 } 604 throw new BeanRuntimeException(e, beanMeta.c, "Exception occurred while getting property ''{0}''", name); 605 } 606 } 607 608 /** 609 * Equivalent to calling {@link BeanMap#getRaw(Object)}, but is faster since it avoids looking up the property meta. 610 * 611 * @param m The bean map to get the transformed value from. 612 * @param pName 613 * The property name if this is a dyna property (i.e. <js>"*"</js>). 614 * <br>Otherwise can be <jk>null</jk>. 615 * @return The raw property value. 616 */ 617 public Object getRaw(BeanMap<?> m, String pName) { 618 try { 619 // Read-only beans have their properties stored in a cache until getBean() is called. 620 Object bean = m.bean; 621 if (bean == null) 622 return m.propertyCache.get(name); 623 624 return invokeGetter(bean, pName); 625 626 } catch (Throwable e) { 627 if (beanContext.isIgnoreInvocationExceptionsOnGetters()) { 628 if (rawTypeMeta.isPrimitive()) 629 return rawTypeMeta.getPrimitiveDefault(); 630 return null; 631 } 632 throw new BeanRuntimeException(e, beanMeta.c, "Exception occurred while getting property ''{0}''", name); 633 } 634 } 635 636 /** 637 * Converts a raw bean property value to serialized form. 638 * Applies transforms and child property filters. 639 */ 640 final Object toSerializedForm(BeanSession session, Object o) { 641 try { 642 o = transform(session, o); 643 if (o == null) 644 return null; 645 if (properties != null) { 646 if (rawTypeMeta.isArray()) { 647 Object[] a = (Object[])o; 648 List l = new DelegateList(rawTypeMeta); 649 ClassMeta childType = rawTypeMeta.getElementType(); 650 for (Object c : a) 651 l.add(applyChildPropertiesFilter(session, childType, c)); 652 return l; 653 } else if (rawTypeMeta.isCollection()) { 654 Collection c = (Collection)o; 655 List l = new ArrayList(c.size()); 656 ClassMeta childType = rawTypeMeta.getElementType(); 657 for (Object cc : c) 658 l.add(applyChildPropertiesFilter(session, childType, cc)); 659 return l; 660 } else { 661 return applyChildPropertiesFilter(session, rawTypeMeta, o); 662 } 663 } 664 return o; 665 } catch (SerializeException e) { 666 throw new BeanRuntimeException(e); 667 } 668 } 669 670 /** 671 * Equivalent to calling {@link BeanMap#put(String, Object)}, but is faster since it avoids looking up the property 672 * meta. 673 * 674 * <p> 675 * This is a no-op on a read-only property. 676 * 677 * @param m The bean map to set the property value on. 678 * @param pName 679 * The property name if this is a dyna property (i.e. <js>"*"</js>). 680 * <br>Otherwise can be <jk>null</jk>. 681 * @param value The value to set. 682 * @return The previous property value. 683 * @throws BeanRuntimeException If property could not be set. 684 */ 685 public Object set(BeanMap<?> m, String pName, Object value) throws BeanRuntimeException { 686 return setInner(m, pName, m.meta.onWriteProperty(m.bean, pName, value)); 687 } 688 689 private Object setInner(BeanMap<?> m, String pName, Object value) throws BeanRuntimeException { 690 try { 691 692 if (readOnly) 693 return null; 694 695 BeanSession session = m.getBeanSession(); 696 697 // Convert to raw form. 698 value = unswap(session, value); 699 700 if (m.bean == null) { 701 702 // Read-only beans get their properties stored in a cache. 703 if (m.propertyCache != null) 704 return m.propertyCache.put(name, value); 705 706 throw new BeanRuntimeException("Non-existent bean instance on bean."); 707 } 708 709 boolean isMap = rawTypeMeta.isMap(); 710 boolean isCollection = rawTypeMeta.isCollection(); 711 712 if ((! isDyna) && field == null && setter == null && ! (isMap || isCollection)) { 713 if ((value == null && beanContext.isIgnoreUnknownNullBeanProperties()) || beanContext.isIgnorePropertiesWithoutSetters()) 714 return null; 715 throw new BeanRuntimeException(beanMeta.c, "Setter or public field not defined on property ''{0}''", name); 716 } 717 718 Object bean = m.getBean(true); // Don't use getBean() because it triggers array creation! 719 720 try { 721 722 Object r = (beanContext.isBeanMapPutReturnsOldValue() || isMap || isCollection) && (getter != null || field != null) ? get(m, pName) : null; 723 Class<?> propertyClass = rawTypeMeta.getInnerClass(); 724 ClassInfo pcInfo = rawTypeMeta.getInfo(); 725 726 if (value == null && (isMap || isCollection)) { 727 invokeSetter(bean, pName, null); 728 return r; 729 } 730 731 Class<?> vc = value == null ? null : value.getClass(); 732 733 if (isMap && (setter == null || ! pcInfo.isParentOf(vc))) { 734 735 if (! (value instanceof Map)) { 736 if (value instanceof CharSequence) 737 value = OMap.ofJson((CharSequence)value).session(session); 738 else 739 throw new BeanRuntimeException(beanMeta.c, "Cannot set property ''{0}'' of type ''{1}'' to object of type ''{2}''", name, propertyClass.getName(), findClassName(value)); 740 } 741 742 Map valueMap = (Map)value; 743 Map propMap = (Map)r; 744 ClassMeta<?> valueType = rawTypeMeta.getValueType(); 745 746 // If the property type is abstract, then we either need to reuse the existing 747 // map (if it's not null), or try to assign the value directly. 748 if (! rawTypeMeta.canCreateNewInstance()) { 749 if (propMap == null) { 750 if (setter == null && field == null) 751 throw new BeanRuntimeException(beanMeta.c, "Cannot set property ''{0}'' of type ''{1}'' to object of type ''{2}'' because no setter or public field is defined, and the current value is null", name, propertyClass.getName(), findClassName(value)); 752 753 if (propertyClass.isInstance(valueMap)) { 754 if (! valueType.isObject()) { 755 boolean needsConversion = false; 756 for (Map.Entry e : (Set<Map.Entry>)valueMap.entrySet()) { 757 Object v = e.getValue(); 758 if (v != null && ! valueType.getInnerClass().isInstance(v)) { 759 needsConversion = true; 760 break; 761 } 762 } 763 if (needsConversion) 764 valueMap = (Map)session.convertToType(valueMap, rawTypeMeta); 765 } 766 invokeSetter(bean, pName, valueMap); 767 return r; 768 } 769 throw new BeanRuntimeException(beanMeta.c, "Cannot set property ''{0}'' of type ''{2}'' to object of type ''{2}'' because the assigned map cannot be converted to the specified type because the property type is abstract, and the property value is currently null", name, propertyClass.getName(), findClassName(value)); 770 } 771 } else { 772 if (propMap == null) { 773 propMap = castOrCreate(Map.class, propertyClass); 774 } else { 775 propMap.clear(); 776 } 777 } 778 779 // Set the values. 780 for (Map.Entry e : (Set<Map.Entry>)valueMap.entrySet()) { 781 Object k = e.getKey(); 782 Object v = e.getValue(); 783 if (! valueType.isObject()) 784 v = session.convertToType(v, valueType); 785 propMap.put(k, v); 786 } 787 if (setter != null || field != null) 788 invokeSetter(bean, pName, propMap); 789 790 } else if (isCollection && (setter == null || ! pcInfo.isParentOf(vc))) { 791 792 if (! (value instanceof Collection)) { 793 if (value instanceof CharSequence) 794 value = new OList((CharSequence)value).setBeanSession(session); 795 else 796 throw new BeanRuntimeException(beanMeta.c, "Cannot set property ''{0}'' of type ''{1}'' to object of type ''{2}''", name, propertyClass.getName(), findClassName(value)); 797 } 798 799 Collection valueList = (Collection)value; 800 Collection propList = (Collection)r; 801 ClassMeta elementType = rawTypeMeta.getElementType(); 802 803 // If the property type is abstract, then we either need to reuse the existing 804 // collection (if it's not null), or try to assign the value directly. 805 if (! rawTypeMeta.canCreateNewInstance()) { 806 if (propList == null) { 807 if (setter == null && field == null) 808 throw new BeanRuntimeException(beanMeta.c, "Cannot set property ''{0}'' of type ''{1}'' to object of type ''{2}'' because no setter or public field is defined, and the current value is null", name, propertyClass.getName(), findClassName(value)); 809 810 if (propertyClass.isInstance(valueList)) { 811 if (! elementType.isObject()) { 812 List l = new OList(valueList); 813 for (ListIterator<Object> i = l.listIterator(); i.hasNext(); ) { 814 Object v = i.next(); 815 if (v != null && (! elementType.getInnerClass().isInstance(v))) { 816 i.set(session.convertToType(v, elementType)); 817 } 818 } 819 valueList = l; 820 } 821 invokeSetter(bean, pName, valueList); 822 return r; 823 } 824 throw new BeanRuntimeException(beanMeta.c, "Cannot set property ''{0}'' of type ''{1}'' to object of type ''{2}'' because the assigned map cannot be converted to the specified type because the property type is abstract, and the property value is currently null", name, propertyClass.getName(), findClassName(value)); 825 } 826 propList.clear(); 827 } else { 828 if (propList == null) { 829 propList = castOrCreate(Collection.class, propertyClass); 830 invokeSetter(bean, pName, propList); 831 } else { 832 propList.clear(); 833 } 834 } 835 836 // Set the values. 837 for (Object v : valueList) { 838 if (! elementType.isObject()) 839 v = session.convertToType(v, elementType); 840 propList.add(v); 841 } 842 843 } else { 844 if (swap != null && value != null && swap.getSwapClass().isParentOf(value.getClass())) { 845 value = swap.unswap(session, value, rawTypeMeta); 846 } else { 847 value = session.convertToType(value, rawTypeMeta); 848 } 849 invokeSetter(bean, pName, value); 850 } 851 852 return r; 853 854 } catch (BeanRuntimeException e) { 855 throw e; 856 } catch (Exception e) { 857 if (beanContext.isIgnoreInvocationExceptionsOnSetters()) { 858 if (rawTypeMeta.isPrimitive()) 859 return rawTypeMeta.getPrimitiveDefault(); 860 return null; 861 } 862 throw new BeanRuntimeException(e, beanMeta.c, "Error occurred trying to set property ''{0}''", name); 863 } 864 } catch (ParseException e) { 865 throw new BeanRuntimeException(e); 866 } 867 } 868 869 private Object invokeGetter(Object bean, String pName) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException { 870 if (isDyna) { 871 Map m = null; 872 if (getter != null) { 873 if (! isDynaGetterMap) 874 return getter.invoke(bean, pName); 875 m = (Map)getter.invoke(bean); 876 } 877 else if (field != null) 878 m = (Map)field.get(bean); 879 else 880 throw new BeanRuntimeException(beanMeta.c, "Getter or public field not defined on property ''{0}''", name); 881 return (m == null ? null : m.get(pName)); 882 } 883 if (getter != null) 884 return getter.invoke(bean); 885 if (field != null) 886 return field.get(bean); 887 throw new BeanRuntimeException(beanMeta.c, "Getter or public field not defined on property ''{0}''", name); 888 } 889 890 private Object invokeSetter(Object bean, String pName, Object val) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException { 891 if (isDyna) { 892 if (setter != null) 893 return setter.invoke(bean, pName, val); 894 Map m = null; 895 if (field != null) 896 m = (Map<String,Object>)field.get(bean); 897 else if (getter != null) 898 m = (Map<String,Object>)getter.invoke(bean); 899 else 900 throw new BeanRuntimeException(beanMeta.c, "Cannot set property ''{0}'' of type ''{1}'' to object of type ''{2}'' because no setter is defined on this property, and the existing property value is null", name, this.getClassMeta().getInnerClass().getName(), findClassName(val)); 901 return (m == null ? null : m.put(pName, val)); 902 } 903 if (setter != null) 904 return setter.invoke(bean, val); 905 if (field != null) { 906 field.set(bean, val); 907 return null; 908 } 909 throw new BeanRuntimeException(beanMeta.c, "Cannot set property ''{0}'' of type ''{1}'' to object of type ''{2}'' because no setter is defined on this property, and the existing property value is null", name, this.getClassMeta().getInnerClass().getName(), findClassName(val)); 910 } 911 912 /** 913 * Returns the {@link Map} object returned by the DynaBean getter. 914 * 915 * <p> 916 * The DynaBean property is the property whose name is <js>"*"</js> and returns a map of "extra" properties on the 917 * bean. 918 * 919 * @param bean The bean. 920 * @return 921 * The map returned by the getter, or an empty map if the getter returned <jk>null</jk> or this isn't a DynaBean 922 * property. 923 * @throws IllegalArgumentException Thrown by method invocation. 924 * @throws IllegalAccessException Thrown by method invocation. 925 * @throws InvocationTargetException Thrown by method invocation. 926 */ 927 public Map<String,Object> getDynaMap(Object bean) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException { 928 if (isDyna) { 929 if (extraKeys != null && getter != null && ! isDynaGetterMap) { 930 Map<String,Object> m = new LinkedHashMap<>(); 931 for (String key : (Collection<String>)extraKeys.invoke(bean)) 932 m.put(key, getter.invoke(bean, key)); 933 return m; 934 } 935 if (getter != null && isDynaGetterMap) 936 return (Map)getter.invoke(bean); 937 if (field != null) 938 return (Map)field.get(bean); 939 throw new BeanRuntimeException(beanMeta.c, "Getter or public field not defined on property ''{0}''", name); 940 } 941 return Collections.EMPTY_MAP; 942 } 943 944 /** 945 * Sets an array field on this bean. 946 * 947 * <p> 948 * Works on both <c>Object</c> and primitive arrays. 949 * 950 * @param bean The bean of the field. 951 * @param l The collection to use to set the array field. 952 * @throws IllegalArgumentException Thrown by method invocation. 953 * @throws IllegalAccessException Thrown by method invocation. 954 * @throws InvocationTargetException Thrown by method invocation. 955 */ 956 protected void setArray(Object bean, List l) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException { 957 Object array = toArray(l, this.rawTypeMeta.getElementType().getInnerClass()); 958 invokeSetter(bean, name, array); 959 } 960 961 /** 962 * Adds a value to a {@link Collection} or array property. 963 * 964 * <p> 965 * Note that adding values to an array property is inefficient for large arrays since it must copy the array into a 966 * larger array on each operation. 967 * 968 * @param m The bean of the field being set. 969 * @param pName 970 * The property name if this is a dyna property (i.e. <js>"*"</js>). 971 * <br>Otherwise can be <jk>null</jk>. 972 * @param value The value to add to the field. 973 * @throws BeanRuntimeException If field is not a collection or array. 974 */ 975 public void add(BeanMap<?> m, String pName, Object value) throws BeanRuntimeException { 976 977 // Read-only beans get their properties stored in a cache. 978 if (m.bean == null) { 979 if (! m.propertyCache.containsKey(name)) 980 m.propertyCache.put(name, new OList(m.getBeanSession())); 981 ((OList)m.propertyCache.get(name)).add(value); 982 return; 983 } 984 985 BeanSession session = m.getBeanSession(); 986 987 boolean isCollection = rawTypeMeta.isCollection(); 988 boolean isArray = rawTypeMeta.isArray(); 989 990 if (! (isCollection || isArray)) 991 throw new BeanRuntimeException(beanMeta.c, "Attempt to add element to property ''{0}'' which is not a collection or array", name); 992 993 Object bean = m.getBean(true); 994 995 ClassMeta<?> elementType = rawTypeMeta.getElementType(); 996 997 try { 998 Object v = session.convertToType(value, elementType); 999 1000 if (isCollection) { 1001 Collection c = (Collection)invokeGetter(bean, pName); 1002 1003 if (c != null) { 1004 c.add(v); 1005 return; 1006 } 1007 1008 if (rawTypeMeta.canCreateNewInstance()) 1009 c = (Collection)rawTypeMeta.newInstance(); 1010 else 1011 c = new OList(session); 1012 1013 c.add(v); 1014 1015 invokeSetter(bean, pName, c); 1016 1017 } else /* isArray() */ { 1018 1019 if (m.arrayPropertyCache == null) 1020 m.arrayPropertyCache = new TreeMap<>(); 1021 1022 List l = m.arrayPropertyCache.get(name); 1023 if (l == null) { 1024 l = new LinkedList(); // ArrayLists and LinkLists appear to perform equally. 1025 m.arrayPropertyCache.put(name, l); 1026 1027 // Copy any existing array values into the temporary list. 1028 Object oldArray = invokeGetter(bean, pName); 1029 copyToList(oldArray, l); 1030 } 1031 1032 // Add new entry to our array. 1033 l.add(v); 1034 } 1035 1036 } catch (BeanRuntimeException e) { 1037 throw e; 1038 } catch (Exception e) { 1039 throw new BeanRuntimeException(e); 1040 } 1041 } 1042 1043 /** 1044 * Adds a value to a {@link Map} or bean property. 1045 * 1046 * @param m The bean of the field being set. 1047 * @param pName 1048 * The property name if this is a dyna property (i.e. <js>"*"</js>). 1049 * <br>Otherwise can be <jk>null</jk>. 1050 * @param key The key to add to the field. 1051 * @param value The value to add to the field. 1052 * @throws BeanRuntimeException If field is not a map or array. 1053 */ 1054 public void add(BeanMap<?> m, String pName, String key, Object value) throws BeanRuntimeException { 1055 1056 // Read-only beans get their properties stored in a cache. 1057 if (m.bean == null) { 1058 if (! m.propertyCache.containsKey(name)) 1059 m.propertyCache.put(name, new OMap(m.getBeanSession())); 1060 ((OMap)m.propertyCache.get(name)).append(key.toString(), value); 1061 return; 1062 } 1063 1064 BeanSession session = m.getBeanSession(); 1065 1066 boolean isMap = rawTypeMeta.isMap(); 1067 boolean isBean = rawTypeMeta.isBean(); 1068 1069 if (! (isBean || isMap)) 1070 throw new BeanRuntimeException(beanMeta.c, "Attempt to add key/value to property ''{0}'' which is not a map or bean", name); 1071 1072 Object bean = m.getBean(true); 1073 1074 ClassMeta<?> elementType = rawTypeMeta.getElementType(); 1075 1076 try { 1077 Object v = session.convertToType(value, elementType); 1078 1079 if (isMap) { 1080 Map map = (Map)invokeGetter(bean, pName); 1081 1082 if (map != null) { 1083 map.put(key, v); 1084 return; 1085 } 1086 1087 if (rawTypeMeta.canCreateNewInstance()) 1088 map = (Map)rawTypeMeta.newInstance(); 1089 else 1090 map = new OMap(session); 1091 1092 map.put(key, v); 1093 1094 invokeSetter(bean, pName, map); 1095 1096 } else /* isBean() */ { 1097 1098 Object b = invokeGetter(bean, pName); 1099 1100 if (b != null) { 1101 BeanMap bm = session.toBeanMap(b); 1102 bm.put(key, v); 1103 return; 1104 } 1105 1106 if (rawTypeMeta.canCreateNewInstance(m.getBean(false))) { 1107 b = rawTypeMeta.newInstance(); 1108 BeanMap bm = session.toBeanMap(b); 1109 bm.put(key, v); 1110 } 1111 1112 invokeSetter(bean, pName, b); 1113 } 1114 1115 } catch (BeanRuntimeException e) { 1116 throw e; 1117 } catch (Exception e) { 1118 throw new BeanRuntimeException(e); 1119 } 1120 } 1121 1122 /** 1123 * Returns all instances of the specified annotation in the hierarchy of this bean property. 1124 * 1125 * <p> 1126 * Searches through the class hierarchy (e.g. superclasses, interfaces, packages) for all instances of the 1127 * specified annotation. 1128 * 1129 * @param <A> The class to find annotations for. 1130 * @param a The class to find annotations for. 1131 * @return A list of annotations ordered in parent-to-child order. Never <jk>null</jk>. 1132 */ 1133 public <A extends Annotation> List<A> getAllAnnotationsParentFirst(Class<A> a) { 1134 List<A> l = new LinkedList<>(); 1135 BeanContext bc = beanContext; 1136 if (a == null) 1137 return l; 1138 getBeanMeta().getClassMeta().getInfo().appendAnnotations(l, a, bc); 1139 1140 if (field != null) { 1141 l.addAll(bc.getAnnotations(a, field)); 1142 ClassInfo.of(field.getType()).appendAnnotations(l, a, bc); 1143 } 1144 if (getter != null) { 1145 l.addAll(bc.getAnnotations(a, getter)); 1146 ClassInfo.of(getter.getReturnType()).appendAnnotations(l, a, bc); 1147 } 1148 if (setter != null) { 1149 l.addAll(bc.getAnnotations(a, setter)); 1150 ClassInfo.of(setter.getReturnType()).appendAnnotations(l, a, bc); 1151 } 1152 if (extraKeys != null) { 1153 l.addAll(bc.getAnnotations(a, extraKeys)); 1154 ClassInfo.of(extraKeys.getReturnType()).appendAnnotations(l, a, bc); 1155 } 1156 1157 return l; 1158 } 1159 1160 /** 1161 * Returns all instances of the specified annotation on the getter/setter/field of the property. 1162 * 1163 * @param <A> The class to find annotations for. 1164 * @param a The class to find annotations for. 1165 * @return A list of annotations ordered in child-to-parent order. Never <jk>null</jk>. 1166 */ 1167 public <A extends Annotation> List<A> getAnnotations(Class<A> a) { 1168 List<A> l = new LinkedList<>(); 1169 BeanContext bc = beanContext; 1170 if (a == null) 1171 return l; 1172 l.addAll(bc.getAnnotations(a, field)); 1173 l.addAll(bc.getAnnotations(a, getter)); 1174 l.addAll(bc.getAnnotations(a, setter)); 1175 return l; 1176 } 1177 1178 private Object transform(BeanSession session, Object o) throws SerializeException { 1179 try { 1180 // First use swap defined via @Beanp. 1181 if (swap != null) 1182 return swap.swap(session, o); 1183 if (o == null) 1184 return null; 1185 // Otherwise, look it up via bean context. 1186 if (rawTypeMeta.hasChildPojoSwaps()) { 1187 PojoSwap f = rawTypeMeta.getChildPojoSwapForSwap(o.getClass()); 1188 if (f != null) 1189 return f.swap(session, o); 1190 } 1191 return o; 1192 } catch (SerializeException e) { 1193 throw e; 1194 } catch (Exception e) { 1195 throw new SerializeException(e); 1196 } 1197 } 1198 1199 private Object unswap(BeanSession session, Object o) throws ParseException { 1200 try { 1201 if (swap != null) 1202 return swap.unswap(session, o, rawTypeMeta); 1203 if (o == null) 1204 return null; 1205 if (rawTypeMeta.hasChildPojoSwaps()) { 1206 PojoSwap f = rawTypeMeta.getChildPojoSwapForUnswap(o.getClass()); 1207 if (f != null) 1208 return f.unswap(session, o, rawTypeMeta); 1209 } 1210 return o; 1211 } catch (ParseException e) { 1212 throw e; 1213 } catch (Exception e) { 1214 throw new ParseException(e); 1215 } 1216 } 1217 1218 private Object applyChildPropertiesFilter(BeanSession session, ClassMeta cm, Object o) { 1219 if (o == null) 1220 return null; 1221 if (cm.isBean()) 1222 return new BeanMap(session, o, new BeanMetaFiltered(cm.getBeanMeta(), properties)); 1223 if (cm.isMap()) 1224 return new FilteredMap(cm, (Map)o, properties); 1225 if (cm.isObject()) { 1226 if (o instanceof Map) 1227 return new FilteredMap(cm, (Map)o, properties); 1228 BeanMeta bm = beanContext.getBeanMeta(o.getClass()); 1229 if (bm != null) 1230 return new BeanMap(session, o, new BeanMetaFiltered(cm.getBeanMeta(), properties)); 1231 } 1232 return o; 1233 } 1234 1235 private static String findClassName(Object o) { 1236 if (o == null) 1237 return null; 1238 if (o instanceof Class) 1239 return ((Class<?>)o).getName(); 1240 return o.getClass().getName(); 1241 } 1242 1243 @Override /* Object */ 1244 public String toString() { 1245 return name + ": " + this.rawTypeMeta.getInnerClass().getName() + ", field=["+field+"], getter=["+getter+"], setter=["+setter+"]"; 1246 } 1247 1248 /** 1249 * Returns <jk>true</jk> if this property can be read. 1250 * 1251 * @return <jk>true</jk> if this property can be read. 1252 */ 1253 public boolean canRead() { 1254 return canRead; 1255 } 1256 1257 /** 1258 * Returns <jk>true</jk> if this property can be written. 1259 * 1260 * @return <jk>true</jk> if this property can be written. 1261 */ 1262 public boolean canWrite() { 1263 return canWrite; 1264 } 1265 1266 /** 1267 * Returns <jk>true</jk> if this property is read-only. 1268 * 1269 * <p> 1270 * This implies the property MIGHT be writable, but that parsers should not set a value for it. 1271 * 1272 * @return <jk>true</jk> if this property is read-only. 1273 */ 1274 public boolean isReadOnly() { 1275 return readOnly; 1276 } 1277 1278 /** 1279 * Returns <jk>true</jk> if this property is write-only. 1280 * 1281 * <p> 1282 * This implies the property MIGHT be readable, but that serializers should not serialize it. 1283 * 1284 * @return <jk>true</jk> if this property is write-only. 1285 */ 1286 protected boolean isWriteOnly() { 1287 return writeOnly; 1288 } 1289 1290 @Override /* Object */ 1291 public int hashCode() { 1292 return hashCode; 1293 } 1294 1295 @Override /* Object */ 1296 public boolean equals(Object o) { 1297 return (o instanceof BeanPropertyMeta) && eq(this, (BeanPropertyMeta)o, (x,y)->eq(x.name, y.name) && eq(x.beanMeta, y.beanMeta)); 1298 } 1299}