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