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