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