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