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