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