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