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