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