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.Visibility.*; 016import static org.apache.juneau.internal.ArrayUtils.*; 017import static org.apache.juneau.internal.ClassUtils.*; 018import static org.apache.juneau.internal.CollectionUtils.*; 019import static org.apache.juneau.internal.ReflectionUtils.*; 020import static org.apache.juneau.internal.StringUtils.*; 021 022import java.lang.annotation.*; 023import java.lang.reflect.*; 024import java.net.*; 025import java.net.URI; 026import java.util.*; 027 028import org.apache.juneau.annotation.*; 029import org.apache.juneau.internal.*; 030import org.apache.juneau.parser.*; 031import org.apache.juneau.serializer.*; 032import org.apache.juneau.transform.*; 033import org.apache.juneau.transforms.*; 034import org.apache.juneau.utils.*; 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 Method getter, setter; // The bean property getter and setter. 056 private final boolean isUri; // True if this is a URL/URI or annotated with @URI. 057 private final boolean isDyna; // This is a dyna property (i.e. name="*") 058 059 private final ClassMeta<?> 060 rawTypeMeta, // The real class type of the bean property. 061 typeMeta; // The transformed class type of the bean property. 062 063 private final String[] properties; // The value of the @BeanProperty(properties) annotation. 064 private final PojoSwap swap; // PojoSwap defined only via @BeanProperty annotation. 065 066 private final MetadataMap extMeta; // Extended metadata 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; 072 073 /** 074 * Creates a builder for {@link #BeanPropertyMeta} objects. 075 * 076 * @param beanMeta The metadata on the bean 077 * @param name The bean property name. 078 * @return A new builder. 079 */ 080 public static Builder builder(BeanMeta<?> beanMeta, String name) { 081 return new Builder(beanMeta, name); 082 } 083 084 /** 085 * BeanPropertyMeta builder class. 086 */ 087 public static final class Builder { 088 BeanMeta<?> beanMeta; 089 BeanContext beanContext; 090 String name; 091 Field field; 092 Method getter, setter; 093 boolean isConstructorArg, isUri, isDyna; 094 ClassMeta<?> rawTypeMeta, typeMeta; 095 String[] properties; 096 PojoSwap swap; 097 BeanRegistry beanRegistry; 098 Object overrideValue; 099 BeanPropertyMeta delegateFor; 100 MetadataMap extMeta = new MetadataMap(); 101 boolean canRead, canWrite; 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 boolean validate(BeanContext f, BeanRegistry parentBeanRegistry, Map<Class<?>,Class<?>[]> typeVarImpls) throws Exception { 165 166 List<Class<?>> bdClasses = new ArrayList<>(); 167 168 if (field == null && getter == null && setter == null) 169 return false; 170 171 if (field == null && setter == null && f.beansRequireSettersForGetters && ! isConstructorArg) 172 return false; 173 174 canRead |= (field != null || getter != null); 175 canWrite |= (field != null || setter != null); 176 177 if (field != null) { 178 BeanProperty p = field.getAnnotation(BeanProperty.class); 179 rawTypeMeta = f.resolveClassMeta(p, field.getGenericType(), typeVarImpls); 180 isUri |= (rawTypeMeta.isUri() || field.isAnnotationPresent(org.apache.juneau.annotation.URI.class)); 181 if (p != null) { 182 if (! p.properties().isEmpty()) 183 properties = split(p.properties()); 184 bdClasses.addAll(Arrays.asList(p.beanDictionary())); 185 } 186 Swap s = field.getAnnotation(Swap.class); 187 if (s != null) { 188 swap = getPropertyPojoSwap(s); 189 } 190 } 191 192 if (getter != null) { 193 BeanProperty p = getMethodAnnotation(BeanProperty.class, getter); 194 if (rawTypeMeta == null) 195 rawTypeMeta = f.resolveClassMeta(p, getter.getGenericReturnType(), typeVarImpls); 196 isUri |= (rawTypeMeta.isUri() || getter.isAnnotationPresent(org.apache.juneau.annotation.URI.class)); 197 if (p != null) { 198 if (properties != null && ! p.properties().isEmpty()) 199 properties = split(p.properties()); 200 bdClasses.addAll(Arrays.asList(p.beanDictionary())); 201 } 202 Swap s = getter.getAnnotation(Swap.class); 203 if (s != null && swap == null) { 204 swap = getPropertyPojoSwap(s); 205 } 206 } 207 208 if (setter != null) { 209 BeanProperty p = getMethodAnnotation(BeanProperty.class, setter); 210 if (rawTypeMeta == null) 211 rawTypeMeta = f.resolveClassMeta(p, setter.getGenericParameterTypes()[0], typeVarImpls); 212 isUri |= (rawTypeMeta.isUri() || setter.isAnnotationPresent(org.apache.juneau.annotation.URI.class)); 213 if (p != null) { 214 if (swap == null) 215 swap = getPropertyPojoSwap(p); 216 if (properties != null && ! p.properties().isEmpty()) 217 properties = split(p.properties()); 218 bdClasses.addAll(Arrays.asList(p.beanDictionary())); 219 } 220 Swap s = setter.getAnnotation(Swap.class); 221 if (s != null && swap == null) { 222 swap = getPropertyPojoSwap(s); 223 } 224 } 225 226 if (rawTypeMeta == null) 227 return false; 228 229 this.beanRegistry = new BeanRegistry(beanContext, parentBeanRegistry, bdClasses.toArray(new Class<?>[0])); 230 231 isDyna = "*".equals(name); 232 233 // Do some annotation validation. 234 Class<?> c = rawTypeMeta.getInnerClass(); 235 if (getter != null) { 236 if (isDyna) { 237 if (! isParentClass(Map.class, c)) 238 return false; 239 } else { 240 if (! isParentClass(getter.getReturnType(), c)) 241 return false; 242 } 243 } 244 if (setter != null) { 245 Class<?>[] pt = setter.getParameterTypes(); 246 if (pt.length != (isDyna ? 2 : 1)) 247 return false; 248 if (isDyna) { 249 if (! pt[0].equals(String.class)) 250 return false; 251 } else { 252 if (! isParentClass(pt[0], c)) 253 return false; 254 } 255 } 256 if (field != null) { 257 if (isDyna) { 258 if (! isParentClass(Map.class, field.getType())) 259 return false; 260 } else { 261 if (! isParentClass(field.getType(), c)) 262 return false; 263 } 264 } 265 266 if (isDyna) 267 rawTypeMeta = rawTypeMeta.getValueType(); 268 if (rawTypeMeta == null) 269 return false; 270 271 if (typeMeta == null) 272 typeMeta = (swap != null ? beanContext.getClassMeta(swap.getSwapClass()) : rawTypeMeta == null ? beanContext.object() : rawTypeMeta); 273 if (typeMeta == null) 274 typeMeta = rawTypeMeta; 275 276 return true; 277 } 278 279 /** 280 * @return A new BeanPropertyMeta object using this builder. 281 */ 282 public BeanPropertyMeta build() { 283 return new BeanPropertyMeta(this); 284 } 285 286 private PojoSwap getPropertyPojoSwap(BeanProperty p) throws Exception { 287 if (! p.format().isEmpty()) 288 return beanContext.newInstance(PojoSwap.class, StringFormatSwap.class, false, p.format()); 289 return null; 290 } 291 292 private PojoSwap getPropertyPojoSwap(Swap s) throws Exception { 293 Class<?> c = s.value(); 294 if (c == Null.class) 295 c = s.impl(); 296 if (c == Null.class) 297 return null; 298 if (isParentClass(PojoSwap.class, c)) { 299 PojoSwap ps = beanContext.newInstance(PojoSwap.class, c); 300 if (ps.forMediaTypes() != null) 301 throw new RuntimeException("TODO - Media types on swaps not yet supported on bean properties."); 302 if (ps.withTemplate() != null) 303 throw new RuntimeException("TODO - Templates on swaps not yet supported on bean properties."); 304 return ps; 305 } 306 if (isParentClass(Surrogate.class, c)) 307 throw new RuntimeException("TODO - Surrogate swaps not yet supported on bean properties."); 308 throw new FormattedRuntimeException("Invalid class used in @Swap annotation. Must be a subclass of PojoSwap or Surrogate.", c); 309 } 310 311 BeanPropertyMeta.Builder setGetter(Method getter) { 312 setAccessible(getter); 313 this.getter = getter; 314 return this; 315 } 316 317 BeanPropertyMeta.Builder setSetter(Method setter) { 318 setAccessible(setter); 319 this.setter = setter; 320 return this; 321 } 322 323 BeanPropertyMeta.Builder setField(Field field) { 324 setAccessible(field); 325 this.field = field; 326 return this; 327 } 328 329 BeanPropertyMeta.Builder setAsConstructorArg() { 330 this.isConstructorArg = true; 331 return this; 332 } 333 334 } 335 336 /** 337 * Creates a new BeanPropertyMeta using the contents of the specified builder. 338 * 339 * @param b The builder to copy fields from. 340 */ 341 protected BeanPropertyMeta(BeanPropertyMeta.Builder b) { 342 this.field = b.field; 343 this.getter = b.getter; 344 this.setter = b.setter; 345 this.isUri = b.isUri; 346 this.beanMeta = b.beanMeta; 347 this.beanContext = b.beanContext; 348 this.name = b.name; 349 this.rawTypeMeta = b.rawTypeMeta; 350 this.typeMeta = b.typeMeta; 351 this.properties = b.properties; 352 this.swap = b.swap; 353 this.beanRegistry = b.beanRegistry; 354 this.overrideValue = b.overrideValue; 355 this.delegateFor = b.delegateFor; 356 this.extMeta = b.extMeta; 357 this.isDyna = b.isDyna; 358 this.canRead = b.canRead; 359 this.canWrite = b.canWrite; 360 } 361 362 /** 363 * Returns the name of this bean property. 364 * 365 * @return The name of the bean property. 366 */ 367 public String getName() { 368 return name; 369 } 370 371 /** 372 * Returns the bean meta that this property belongs to. 373 * 374 * @return The bean meta that this property belongs to. 375 */ 376 @BeanIgnore 377 public BeanMeta<?> getBeanMeta() { 378 return beanMeta; 379 } 380 381 /** 382 * Returns the getter method for this property. 383 * 384 * @return The getter method for this bean property, or <jk>null</jk> if there is no getter method. 385 */ 386 public Method getGetter() { 387 return getter; 388 } 389 390 /** 391 * Returns the setter method for this property. 392 * 393 * @return The setter method for this bean property, or <jk>null</jk> if there is no setter method. 394 */ 395 public Method getSetter() { 396 return setter; 397 } 398 399 /** 400 * Returns the field for this property. 401 * 402 * @return The field for this bean property, or <jk>null</jk> if there is no field associated with this bean property. 403 */ 404 public Field getField() { 405 return field; 406 } 407 408 /** 409 * Returns the {@link ClassMeta} of the class of this property. 410 * 411 * <p> 412 * If this property or the property type class has a {@link PojoSwap} associated with it, this method returns the 413 * transformed class meta. 414 * This matches the class type that is used by the {@link #get(BeanMap,String)} and 415 * {@link #set(BeanMap,String,Object)} methods. 416 * 417 * @return The {@link ClassMeta} of the class of this property. 418 */ 419 public ClassMeta<?> getClassMeta() { 420 return typeMeta; 421 } 422 423 /** 424 * Returns the bean dictionary in use for this bean property. 425 * 426 * <p> 427 * The order of lookup for the dictionary is as follows: 428 * <ol> 429 * <li>Dictionary defined via {@link BeanProperty#beanDictionary() @BeanProperty.beanDictionary()}. 430 * <li>Dictionary defined via {@link BeanContext#BEAN_beanDictionary} context property. 431 * </ol> 432 * 433 * @return The bean dictionary in use for this bean property. Never <jk>null</jk>. 434 */ 435 public BeanRegistry getBeanRegistry() { 436 return beanRegistry; 437 } 438 439 /** 440 * Returns <jk>true</jk> if this bean property is a URI. 441 * 442 * <p> 443 * A bean property can be considered a URI if any of the following are true: 444 * <ul> 445 * <li>Property class type is {@link URL} or {@link URI}. 446 * <li>Property class type is annotated with {@link org.apache.juneau.annotation.URI @URI}. 447 * <li>Property getter, setter, or field is annotated with {@link org.apache.juneau.annotation.URI @URI}. 448 * </ul> 449 * 450 * @return <jk>true</jk> if this bean property is a URI. 451 */ 452 public boolean isUri() { 453 return isUri; 454 } 455 456 /** 457 * Returns <jk>true</jk> if this bean property is named <js>"*"</js>. 458 * 459 * @return <jk>true</jk> if this bean property is named <js>"*"</js>. 460 */ 461 public boolean isDyna() { 462 return isDyna; 463 } 464 465 /** 466 * Returns the override list of properties defined through a {@link BeanProperty#properties() @BeanProperty.properties()} annotation 467 * on this property. 468 * 469 * @return The list of override properties, or <jk>null</jk> if annotation not specified. 470 */ 471 public String[] getProperties() { 472 return properties; 473 } 474 475 /** 476 * Returns the language-specified extended metadata on this bean property. 477 * 478 * @param c The name of the metadata class to create. 479 * @return Extended metadata on this bean property. Never <jk>null</jk>. 480 */ 481 public <M extends BeanPropertyMetaExtended> M getExtendedMeta(Class<M> c) { 482 if (delegateFor != null) 483 return delegateFor.getExtendedMeta(c); 484 return extMeta.get(c, this); 485 } 486 487 /** 488 * Equivalent to calling {@link BeanMap#get(Object)}, but is faster since it avoids looking up the property meta. 489 * 490 * @param m The bean map to get the transformed value from. 491 * @param pName The property name. 492 * @return The property value. 493 */ 494 public Object get(BeanMap<?> m, String pName) { 495 try { 496 if (overrideValue != null) 497 return overrideValue; 498 499 // Read-only beans have their properties stored in a cache until getBean() is called. 500 Object bean = m.bean; 501 if (bean == null) 502 return m.propertyCache.get(name); 503 504 return toSerializedForm(m.getBeanSession(), getRaw(m, pName)); 505 506 } catch (Throwable e) { 507 if (beanContext.ignoreInvocationExceptionsOnGetters) { 508 if (rawTypeMeta.isPrimitive()) 509 return rawTypeMeta.getPrimitiveDefault(); 510 return null; 511 } 512 throw new BeanRuntimeException(beanMeta.c, "Exception occurred while getting property ''{0}''", name).initCause(e); 513 } 514 } 515 516 /** 517 * Equivalent to calling {@link BeanMap#getRaw(Object)}, but is faster since it avoids looking up the property meta. 518 * 519 * @param m The bean map to get the transformed value from. 520 * @param pName The property name. 521 * @return The raw property value. 522 */ 523 public Object getRaw(BeanMap<?> m, String pName) { 524 try { 525 // Read-only beans have their properties stored in a cache until getBean() is called. 526 Object bean = m.bean; 527 if (bean == null) 528 return m.propertyCache.get(name); 529 530 return invokeGetter(bean, pName); 531 532 } catch (Throwable e) { 533 if (beanContext.ignoreInvocationExceptionsOnGetters) { 534 if (rawTypeMeta.isPrimitive()) 535 return rawTypeMeta.getPrimitiveDefault(); 536 return null; 537 } 538 throw new BeanRuntimeException(beanMeta.c, "Exception occurred while getting property ''{0}''", name).initCause(e); 539 } 540 } 541 542 /** 543 * Converts a raw bean property value to serialized form. 544 * Applies transforms and child property filters. 545 */ 546 final Object toSerializedForm(BeanSession session, Object o) { 547 try { 548 o = transform(session, o); 549 if (o == null) 550 return null; 551 if (properties != null) { 552 if (rawTypeMeta.isArray()) { 553 Object[] a = (Object[])o; 554 List l = new DelegateList(rawTypeMeta); 555 ClassMeta childType = rawTypeMeta.getElementType(); 556 for (Object c : a) 557 l.add(applyChildPropertiesFilter(session, childType, c)); 558 return l; 559 } else if (rawTypeMeta.isCollection()) { 560 Collection c = (Collection)o; 561 List l = new ArrayList(c.size()); 562 ClassMeta childType = rawTypeMeta.getElementType(); 563 for (Object cc : c) 564 l.add(applyChildPropertiesFilter(session, childType, cc)); 565 return l; 566 } else { 567 return applyChildPropertiesFilter(session, rawTypeMeta, o); 568 } 569 } 570 return o; 571 } catch (SerializeException e) { 572 throw new BeanRuntimeException(e); 573 } 574 } 575 576 /** 577 * Equivalent to calling {@link BeanMap#put(String, Object)}, but is faster since it avoids looking up the property 578 * meta. 579 * 580 * @param m The bean map to set the property value on. 581 * @param pName The property name. 582 * @param value The value to set. 583 * @return The previous property value. 584 * @throws BeanRuntimeException If property could not be set. 585 */ 586 public Object set(BeanMap<?> m, String pName, Object value) throws BeanRuntimeException { 587 try { 588 589 BeanSession session = m.getBeanSession(); 590 591 // Convert to raw form. 592 value = unswap(session, value); 593 594 if (m.bean == null) { 595 596 // Read-only beans get their properties stored in a cache. 597 if (m.propertyCache != null) 598 return m.propertyCache.put(name, value); 599 600 throw new BeanRuntimeException("Non-existent bean instance on bean."); 601 } 602 603 boolean isMap = rawTypeMeta.isMap(); 604 boolean isCollection = rawTypeMeta.isCollection(); 605 606 if ((! isDyna) && field == null && setter == null && ! (isMap || isCollection)) { 607 if ((value == null && beanContext.ignoreUnknownNullBeanProperties) || beanContext.ignorePropertiesWithoutSetters) 608 return null; 609 throw new BeanRuntimeException(beanMeta.c, "Setter or public field not defined on property ''{0}''", name); 610 } 611 612 Object bean = m.getBean(true); // Don't use getBean() because it triggers array creation! 613 614 try { 615 616 Object r = beanContext.beanMapPutReturnsOldValue || isMap || isCollection ? get(m, pName) : null; 617 Class<?> propertyClass = rawTypeMeta.getInnerClass(); 618 619 if (value == null && (isMap || isCollection)) { 620 invokeSetter(bean, pName, null); 621 return r; 622 } 623 624 if (isMap) { 625 626 if (! (value instanceof Map)) { 627 if (value instanceof CharSequence) 628 value = new ObjectMap((CharSequence)value).setBeanSession(session); 629 else 630 throw new BeanRuntimeException(beanMeta.c, "Cannot set property ''{0}'' of type ''{1}'' to object of type ''{2}''", name, propertyClass.getName(), findClassName(value)); 631 } 632 633 Map valueMap = (Map)value; 634 Map propMap = (Map)r; 635 ClassMeta<?> valueType = rawTypeMeta.getValueType(); 636 637 // If the property type is abstract, then we either need to reuse the existing 638 // map (if it's not null), or try to assign the value directly. 639 if (! rawTypeMeta.canCreateNewInstance()) { 640 if (propMap == null) { 641 if (setter == null && field == null) 642 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)); 643 644 if (propertyClass.isInstance(valueMap)) { 645 if (! valueType.isObject()) { 646 for (Map.Entry e : (Set<Map.Entry>)valueMap.entrySet()) { 647 Object v = e.getValue(); 648 if (v != null && ! valueType.getInnerClass().isInstance(v)) 649 throw new BeanRuntimeException(beanMeta.c, "Cannot set property ''{0}'' of type ''{1}'' to object of type ''{2}'' because the value types in the assigned map do not match the specified ''elementClass'' attribute on the property, and the property value is currently null", name, propertyClass.getName(), findClassName(value)); 650 } 651 } 652 invokeSetter(bean, pName, valueMap); 653 return r; 654 } 655 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)); 656 } 657 } else { 658 if (propMap == null) { 659 propMap = beanContext.newInstance(Map.class, propertyClass); 660 } else { 661 propMap.clear(); 662 } 663 } 664 665 // Set the values. 666 for (Map.Entry e : (Set<Map.Entry>)valueMap.entrySet()) { 667 Object k = e.getKey(); 668 Object v = e.getValue(); 669 if (! valueType.isObject()) 670 v = session.convertToType(v, valueType); 671 propMap.put(k, v); 672 } 673 if (setter != null || field != null) 674 invokeSetter(bean, pName, propMap); 675 676 } else if (isCollection) { 677 678 if (! (value instanceof Collection)) { 679 if (value instanceof CharSequence) 680 value = new ObjectList((CharSequence)value).setBeanSession(session); 681 else 682 throw new BeanRuntimeException(beanMeta.c, "Cannot set property ''{0}'' of type ''{1}'' to object of type ''{2}''", name, propertyClass.getName(), findClassName(value)); 683 } 684 685 Collection valueList = (Collection)value; 686 Collection propList = (Collection)r; 687 ClassMeta elementType = rawTypeMeta.getElementType(); 688 689 // If the property type is abstract, then we either need to reuse the existing 690 // collection (if it's not null), or try to assign the value directly. 691 if (! rawTypeMeta.canCreateNewInstance()) { 692 if (propList == null) { 693 if (setter == null && field == null) 694 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)); 695 696 if (propertyClass.isInstance(valueList)) { 697 if (! elementType.isObject()) { 698 List l = new ObjectList(valueList); 699 for (ListIterator<Object> i = l.listIterator(); i.hasNext(); ) { 700 Object v = i.next(); 701 if (v != null && (! elementType.getInnerClass().isInstance(v))) { 702 i.set(session.convertToType(v, elementType)); 703 } 704 } 705 valueList = l; 706 } 707 invokeSetter(bean, pName, valueList); 708 return r; 709 } 710 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)); 711 } 712 propList.clear(); 713 } else { 714 if (propList == null) { 715 propList = beanContext.newInstance(Collection.class, propertyClass); 716 invokeSetter(bean, pName, propList); 717 } else { 718 propList.clear(); 719 } 720 } 721 722 // Set the values. 723 for (Object v : valueList) { 724 if (! elementType.isObject()) 725 v = session.convertToType(v, elementType); 726 propList.add(v); 727 } 728 729 } else { 730 if (swap != null && value != null && isParentClass(swap.getSwapClass(), value.getClass())) { 731 value = swap.unswap(session, value, rawTypeMeta); 732 } else { 733 value = session.convertToType(value, rawTypeMeta); 734 } 735 invokeSetter(bean, pName, value); 736 } 737 738 return r; 739 740 } catch (BeanRuntimeException e) { 741 throw e; 742 } catch (Exception e) { 743 e.printStackTrace(); 744 if (beanContext.ignoreInvocationExceptionsOnSetters) { 745 if (rawTypeMeta.isPrimitive()) 746 return rawTypeMeta.getPrimitiveDefault(); 747 return null; 748 } 749 throw new BeanRuntimeException(beanMeta.c, "Error occurred trying to set property ''{0}''", name).initCause(e); 750 } 751 } catch (ParseException e) { 752 throw new BeanRuntimeException(e); 753 } 754 } 755 756 private Object invokeGetter(Object bean, String pName) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException { 757 if (isDyna) { 758 Map m = null; 759 if (getter != null) 760 m = (Map)getter.invoke(bean); 761 else if (field != null) 762 m = (Map)field.get(bean); 763 else 764 throw new BeanRuntimeException(beanMeta.c, "Getter or public field not defined on property ''{0}''", name); 765 return (m == null ? null : m.get(pName)); 766 } 767 if (getter != null) 768 return getter.invoke(bean); 769 if (field != null) 770 return field.get(bean); 771 throw new BeanRuntimeException(beanMeta.c, "Getter or public field not defined on property ''{0}''", name); 772 } 773 774 private Object invokeSetter(Object bean, String pName, Object val) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException { 775 if (isDyna) { 776 if (setter != null) 777 return setter.invoke(bean, pName, val); 778 Map m = null; 779 if (field != null) 780 m = (Map<String,Object>)field.get(bean); 781 else if (getter != null) 782 m = (Map<String,Object>)getter.invoke(bean); 783 else 784 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)); 785 return (m == null ? null : m.put(pName, val)); 786 } 787 if (setter != null) 788 return setter.invoke(bean, val); 789 if (field != null) { 790 field.set(bean, val); 791 return null; 792 } 793 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)); 794 } 795 796 /** 797 * Returns the {@link Map} object returned by the DynaBean getter. 798 * 799 * <p> 800 * The DynaBean property is the property whose name is <js>"*"</js> and returns a map of "extra" properties on the 801 * bean. 802 * 803 * @param bean The bean. 804 * @return 805 * The map returned by the getter, or an empty map if the getter returned <jk>null</jk> or this isn't a DynaBean 806 * property. 807 * @throws IllegalArgumentException Thrown by method invocation. 808 * @throws IllegalAccessException Thrown by method invocation. 809 * @throws InvocationTargetException Thrown by method invocation. 810 */ 811 public Map<String,Object> getDynaMap(Object bean) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException { 812 if (isDyna) { 813 if (getter != null) 814 return (Map)getter.invoke(bean); 815 if (field != null) 816 return (Map)field.get(bean); 817 throw new BeanRuntimeException(beanMeta.c, "Getter or public field not defined on property ''{0}''", name); 818 } 819 return Collections.EMPTY_MAP; 820 } 821 822 /** 823 * Sets an array field on this bean. 824 * 825 * <p> 826 * Works on both <code>Object</code> and primitive arrays. 827 * 828 * @param bean The bean of the field. 829 * @param l The collection to use to set the array field. 830 * @throws IllegalArgumentException Thrown by method invocation. 831 * @throws IllegalAccessException Thrown by method invocation. 832 * @throws InvocationTargetException Thrown by method invocation. 833 */ 834 protected void setArray(Object bean, List l) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException { 835 Object array = toArray(l, this.rawTypeMeta.getElementType().getInnerClass()); 836 invokeSetter(bean, name, array); 837 } 838 839 /** 840 * Adds a value to a {@link Collection} or array property. 841 * 842 * <p> 843 * Note that adding values to an array property is inefficient for large arrays since it must copy the array into a 844 * larger array on each operation. 845 * 846 * @param m The bean of the field being set. 847 * @param pName The property name. 848 * @param value The value to add to the field. 849 * @throws BeanRuntimeException If field is not a collection or array. 850 */ 851 public void add(BeanMap<?> m, String pName, Object value) throws BeanRuntimeException { 852 853 // Read-only beans get their properties stored in a cache. 854 if (m.bean == null) { 855 if (! m.propertyCache.containsKey(name)) 856 m.propertyCache.put(name, new ObjectList(m.getBeanSession())); 857 ((ObjectList)m.propertyCache.get(name)).add(value); 858 return; 859 } 860 861 BeanSession session = m.getBeanSession(); 862 863 boolean isCollection = rawTypeMeta.isCollection(); 864 boolean isArray = rawTypeMeta.isArray(); 865 866 if (! (isCollection || isArray)) 867 throw new BeanRuntimeException(beanMeta.c, "Attempt to add element to property ''{0}'' which is not a collection or array", name); 868 869 Object bean = m.getBean(true); 870 871 ClassMeta<?> elementType = rawTypeMeta.getElementType(); 872 873 try { 874 Object v = session.convertToType(value, elementType); 875 876 if (isCollection) { 877 Collection c = (Collection)invokeGetter(bean, pName); 878 879 if (c != null) { 880 c.add(v); 881 return; 882 } 883 884 if (rawTypeMeta.canCreateNewInstance()) 885 c = (Collection)rawTypeMeta.newInstance(); 886 else 887 c = new ObjectList(session); 888 889 c.add(v); 890 891 invokeSetter(bean, pName, c); 892 893 } else /* isArray() */ { 894 895 if (m.arrayPropertyCache == null) 896 m.arrayPropertyCache = new TreeMap<>(); 897 898 List l = m.arrayPropertyCache.get(name); 899 if (l == null) { 900 l = new LinkedList(); // ArrayLists and LinkLists appear to perform equally. 901 m.arrayPropertyCache.put(name, l); 902 903 // Copy any existing array values into the temporary list. 904 Object oldArray = invokeGetter(bean, pName); 905 copyToList(oldArray, l); 906 } 907 908 // Add new entry to our array. 909 l.add(v); 910 } 911 912 } catch (BeanRuntimeException e) { 913 throw e; 914 } catch (Exception e) { 915 throw new BeanRuntimeException(e); 916 } 917 } 918 919 /** 920 * Adds a value to a {@link Map} or bean property. 921 * 922 * @param m The bean of the field being set. 923 * @param pName The property name. 924 * @param key The key to add to the field. 925 * @param value The value to add to the field. 926 * @throws BeanRuntimeException If field is not a map or array. 927 */ 928 public void add(BeanMap<?> m, String pName, String key, Object value) throws BeanRuntimeException { 929 930 // Read-only beans get their properties stored in a cache. 931 if (m.bean == null) { 932 if (! m.propertyCache.containsKey(name)) 933 m.propertyCache.put(name, new ObjectMap(m.getBeanSession())); 934 ((ObjectMap)m.propertyCache.get(name)).append(key.toString(), value); 935 return; 936 } 937 938 BeanSession session = m.getBeanSession(); 939 940 boolean isMap = rawTypeMeta.isMap(); 941 boolean isBean = rawTypeMeta.isBean(); 942 943 if (! (isBean || isMap)) 944 throw new BeanRuntimeException(beanMeta.c, "Attempt to add key/value to property ''{0}'' which is not a map or bean", name); 945 946 Object bean = m.getBean(true); 947 948 ClassMeta<?> elementType = rawTypeMeta.getElementType(); 949 950 try { 951 Object v = session.convertToType(value, elementType); 952 953 if (isMap) { 954 Map map = (Map)invokeGetter(bean, pName); 955 956 if (map != null) { 957 map.put(key, v); 958 return; 959 } 960 961 if (rawTypeMeta.canCreateNewInstance()) 962 map = (Map)rawTypeMeta.newInstance(); 963 else 964 map = new ObjectMap(session); 965 966 map.put(key, v); 967 968 invokeSetter(bean, pName, map); 969 970 } else /* isBean() */ { 971 972 Object b = invokeGetter(bean, pName); 973 974 if (b != null) { 975 BeanMap bm = session.toBeanMap(b); 976 bm.put(key, v); 977 return; 978 } 979 980 if (rawTypeMeta.canCreateNewInstance(m.getBean(false))) { 981 b = rawTypeMeta.newInstance(); 982 BeanMap bm = session.toBeanMap(b); 983 bm.put(key, v); 984 } 985 986 invokeSetter(bean, pName, b); 987 } 988 989 } catch (BeanRuntimeException e) { 990 throw e; 991 } catch (Exception e) { 992 throw new BeanRuntimeException(e); 993 } 994 } 995 996 /** 997 * Returns all instances of the specified annotation in the hierarchy of this bean property. 998 * 999 * <p> 1000 * Searches through the class hierarchy (e.g. superclasses, interfaces, packages) for all instances of the 1001 * specified annotation. 1002 * 1003 * @param a The class to find annotations for. 1004 * @return A list of annotations ordered in child-to-parent order. Never <jk>null</jk>. 1005 */ 1006 public <A extends Annotation> List<A> findAnnotations(Class<A> a) { 1007 List<A> l = new LinkedList<>(); 1008 if (field != null) { 1009 addIfNotNull(l, field.getAnnotation(a)); 1010 appendAnnotations(a, field.getType(), l); 1011 } 1012 if (getter != null) { 1013 addIfNotNull(l, getMethodAnnotation(a, getter)); 1014 appendAnnotations(a, getter.getReturnType(), l); 1015 } 1016 if (setter != null) { 1017 addIfNotNull(l, getMethodAnnotation(a, setter)); 1018 appendAnnotations(a, setter.getReturnType(), l); 1019 } 1020 1021 appendAnnotations(a, this.getBeanMeta().getClassMeta().getInnerClass(), l); 1022 return l; 1023 } 1024 1025 /** 1026 * Returns the specified annotation on the field or methods that define this property. 1027 * 1028 * <p> 1029 * This method will search up the parent class/interface hierarchy chain to search for the annotation on 1030 * overridden getters and setters. 1031 * 1032 * @param a The annotation to search for. 1033 * @return The annotation, or <jk>null</jk> if it wasn't found. 1034 */ 1035 public <A extends Annotation> A getAnnotation(Class<A> a) { 1036 A t = null; 1037 if (field != null) 1038 t = field.getAnnotation(a); 1039 if (t == null && getter != null) 1040 t = getMethodAnnotation(a, getter); 1041 if (t == null && setter != null) 1042 t = getMethodAnnotation(a, setter); 1043 return t; 1044 } 1045 1046 private Object transform(BeanSession session, Object o) throws SerializeException { 1047 try { 1048 // First use swap defined via @BeanProperty. 1049 if (swap != null) 1050 return swap.swap(session, o); 1051 if (o == null) 1052 return null; 1053 // Otherwise, look it up via bean context. 1054 if (rawTypeMeta.hasChildPojoSwaps()) { 1055 PojoSwap f = rawTypeMeta.getChildPojoSwapForSwap(o.getClass()); 1056 if (f != null) 1057 return f.swap(session, o); 1058 } 1059 return o; 1060 } catch (SerializeException e) { 1061 throw e; 1062 } catch (Exception e) { 1063 throw new SerializeException(e); 1064 } 1065 } 1066 1067 private Object unswap(BeanSession session, Object o) throws ParseException { 1068 try { 1069 if (swap != null) 1070 return swap.unswap(session, o, rawTypeMeta); 1071 if (o == null) 1072 return null; 1073 if (rawTypeMeta.hasChildPojoSwaps()) { 1074 PojoSwap f = rawTypeMeta.getChildPojoSwapForUnswap(o.getClass()); 1075 if (f != null) 1076 return f.unswap(session, o, rawTypeMeta); 1077 } 1078 return o; 1079 } catch (ParseException e) { 1080 throw e; 1081 } catch (Exception e) { 1082 throw new ParseException(e); 1083 } 1084 } 1085 1086 private Object applyChildPropertiesFilter(BeanSession session, ClassMeta cm, Object o) { 1087 if (o == null) 1088 return null; 1089 if (cm.isBean()) 1090 return new BeanMap(session, o, new BeanMetaFiltered(cm.getBeanMeta(), properties)); 1091 if (cm.isMap()) 1092 return new FilteredMap(cm, (Map)o, properties); 1093 if (cm.isObject()) { 1094 if (o instanceof Map) 1095 return new FilteredMap(cm, (Map)o, properties); 1096 BeanMeta bm = beanContext.getBeanMeta(o.getClass()); 1097 if (bm != null) 1098 return new BeanMap(session, o, new BeanMetaFiltered(cm.getBeanMeta(), properties)); 1099 } 1100 return o; 1101 } 1102 1103 private static String findClassName(Object o) { 1104 if (o == null) 1105 return null; 1106 if (o instanceof Class) 1107 return ((Class<?>)o).getName(); 1108 return o.getClass().getName(); 1109 } 1110 1111 @Override /* Object */ 1112 public String toString() { 1113 return name + ": " + this.rawTypeMeta.getInnerClass().getName() + ", field=["+field+"], getter=["+getter+"], setter=["+setter+"]"; 1114 } 1115 1116 /** 1117 * Returns <jk>true</jk> if this property can be read. 1118 * 1119 * @return <jk>true</jk> if this property can be read. 1120 */ 1121 public boolean canRead() { 1122 return canRead; 1123 } 1124 1125 /** 1126 * Returns <jk>true</jk> if this property can be written. 1127 * 1128 * @return <jk>true</jk> if this property can be written. 1129 */ 1130 public boolean canWrite() { 1131 return canWrite; 1132 } 1133}