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.BeanMeta.MethodType.*; 020import static org.apache.juneau.common.utils.ThrowableUtils.*; 021import static org.apache.juneau.common.utils.Utils.*; 022import static org.apache.juneau.internal.ConsumerUtils.*; 023 024import java.beans.*; 025import java.io.*; 026import java.lang.reflect.*; 027import java.util.*; 028import java.util.function.*; 029 030import org.apache.juneau.annotation.*; 031import org.apache.juneau.common.utils.*; 032import org.apache.juneau.internal.*; 033import org.apache.juneau.reflect.*; 034 035/** 036 * Encapsulates all access to the properties of a bean class (like a souped-up {@link java.beans.BeanInfo}). 037 * 038 * <h5 class='topic'>Description</h5> 039 * 040 * Uses introspection to find all the properties associated with this class. If the {@link Bean @Bean} annotation 041 * is present on the class, then that information is used to determine the properties on the class. 042 * Otherwise, the {@code BeanInfo} functionality in Java is used to determine the properties on the class. 043 * 044 * <h5 class='topic'>Bean property ordering</h5> 045 * 046 * The order of the properties are as follows: 047 * <ul class='spaced-list'> 048 * <li> 049 * If {@link Bean @Bean} annotation is specified on class, then the order is the same as the list of properties 050 * in the annotation. 051 * <li> 052 * If {@link Bean @Bean} annotation is not specified on the class, then the order is based on the following. 053 * <ul> 054 * <li>Public fields (same order as {@code Class.getFields()}). 055 * <li>Properties returned by {@code BeanInfo.getPropertyDescriptors()}. 056 * <li>Non-standard getters/setters with {@link Beanp @Beanp} annotation defined on them. 057 * </ul> 058 * </ul> 059 * 060 * <h5 class='section'>See Also:</h5><ul> 061 062 * </ul> 063 * 064 * @param <T> The class type that this metadata applies to. 065 */ 066public class BeanMeta<T> { 067 068 private static final BeanPropertyMeta[] EMPTY_PROPERTIES = {}; 069 070 /** The target class type that this meta object describes. */ 071 protected final ClassMeta<T> classMeta; 072 073 /** The target class that this meta object describes. */ 074 protected final Class<T> c; 075 076 /** The properties on the target class. */ 077 protected final Map<String,BeanPropertyMeta> properties; 078 079 /** The properties on the target class. */ 080 protected final BeanPropertyMeta[] propertyArray; 081 082 /** The hidden properties on the target class. */ 083 protected final Map<String,BeanPropertyMeta> hiddenProperties; 084 085 /** The getter properties on the target class. */ 086 protected final Map<Method,String> getterProps; 087 088 /** The setter properties on the target class. */ 089 protected final Map<Method,String> setterProps; 090 091 /** The bean context that created this metadata object. */ 092 protected final BeanContext ctx; 093 094 /** Optional bean filter associated with the target class. */ 095 protected final BeanFilter beanFilter; 096 097 /** Type variables implemented by this bean. */ 098 protected final Map<Class<?>,Class<?>[]> typeVarImpls; 099 100 /** The constructor for this bean. */ 101 protected final ConstructorInfo constructor; 102 103 /** For beans with constructors with Beanc annotation, this is the list of constructor arg properties. */ 104 protected final String[] constructorArgs; 105 106 // Other fields 107 final String typePropertyName; // "_type" property actual name. 108 private final BeanPropertyMeta typeProperty; // "_type" mock bean property. 109 final BeanPropertyMeta dynaProperty; // "extras" property. 110 private final String dictionaryName; // The @Bean(typeName) annotation defined on this bean class. 111 final String notABeanReason; // Readable string explaining why this class wasn't a bean. 112 final BeanRegistry beanRegistry; 113 final boolean sortProperties; 114 final boolean fluentSetters; 115 116 /** 117 * Constructor. 118 * 119 * @param classMeta The target class. 120 * @param ctx The bean context that created this object. 121 * @param beanFilter Optional bean filter associated with the target class. Can be <jk>null</jk>. 122 * @param pNames Explicit list of property names and order of properties. If <jk>null</jk>, determine automatically. 123 * @param implClassConstructor The constructor to use if one cannot be found. Can be <jk>null</jk>. 124 */ 125 protected BeanMeta(final ClassMeta<T> classMeta, BeanContext ctx, BeanFilter beanFilter, String[] pNames, ConstructorInfo implClassConstructor) { 126 this.classMeta = classMeta; 127 this.ctx = ctx; 128 this.c = classMeta.getInnerClass(); 129 130 Builder<T> b = new Builder<>(classMeta, ctx, beanFilter, pNames, implClassConstructor); 131 this.notABeanReason = b.init(this); 132 133 this.beanFilter = beanFilter; 134 this.dictionaryName = b.dictionaryName; 135 this.properties = u(b.properties); 136 this.propertyArray = properties == null ? EMPTY_PROPERTIES : Utils.array(properties.values(), BeanPropertyMeta.class); 137 this.hiddenProperties = u(b.hiddenProperties); 138 this.getterProps = u(b.getterProps); 139 this.setterProps = u(b.setterProps); 140 this.dynaProperty = b.dynaProperty; 141 this.typeVarImpls = u(b.typeVarImpls); 142 this.constructor = b.constructor; 143 this.constructorArgs = b.constructorArgs; 144 this.beanRegistry = b.beanRegistry; 145 this.typePropertyName = b.typePropertyName; 146 this.typeProperty = BeanPropertyMeta.builder(this, typePropertyName).canRead().canWrite().rawMetaType(ctx.string()).beanRegistry(beanRegistry).build(); 147 this.sortProperties = b.sortProperties; 148 this.fluentSetters = b.fluentSetters; 149 150 if (sortProperties) 151 Arrays.sort(propertyArray); 152 } 153 154 private static class Builder<T> { 155 ClassMeta<T> classMeta; 156 BeanContext ctx; 157 BeanFilter beanFilter; 158 String[] pNames; 159 Map<String,BeanPropertyMeta> properties; 160 Map<String,BeanPropertyMeta> hiddenProperties = map(); 161 Map<Method,String> getterProps = map(); 162 Map<Method,String> setterProps = map(); 163 BeanPropertyMeta dynaProperty; 164 165 Map<Class<?>,Class<?>[]> typeVarImpls; 166 ConstructorInfo constructor, implClassConstructor; 167 String[] constructorArgs = {}; 168 PropertyNamer propertyNamer; 169 BeanRegistry beanRegistry; 170 String dictionaryName, typePropertyName; 171 boolean sortProperties, fluentSetters; 172 173 Builder(ClassMeta<T> classMeta, BeanContext ctx, BeanFilter beanFilter, String[] pNames, ConstructorInfo implClassConstructor) { 174 this.classMeta = classMeta; 175 this.ctx = ctx; 176 this.beanFilter = beanFilter; 177 this.pNames = pNames; 178 this.implClassConstructor = implClassConstructor; 179 } 180 181 String init(BeanMeta<T> beanMeta) { 182 Class<?> c = classMeta.getInnerClass(); 183 ClassInfo ci = classMeta.getInfo(); 184 185 try { 186 Visibility 187 conVis = ctx.getBeanConstructorVisibility(), 188 cVis = ctx.getBeanClassVisibility(), 189 mVis = ctx.getBeanMethodVisibility(), 190 fVis = ctx.getBeanFieldVisibility(); 191 192 List<Class<?>> bdClasses = list(); 193 if (beanFilter != null && beanFilter.getBeanDictionary() != null) 194 CollectionUtils.addAll(bdClasses, beanFilter.getBeanDictionary()); 195 196 Value<String> typeName = Value.empty(); 197 classMeta.forEachAnnotation(Bean.class, x -> isNotEmpty(x.typeName()), x -> typeName.set(x.typeName())); 198 if (typeName.isPresent()) 199 bdClasses.add(classMeta.innerClass); 200 this.beanRegistry = new BeanRegistry(ctx, null, bdClasses.toArray(new Class<?>[bdClasses.size()])); 201 202 Value<String> typePropertyName = Value.empty(); 203 classMeta.forEachAnnotation(Bean.class, x -> isNotEmpty(x.typePropertyName()), x -> typePropertyName.set(x.typePropertyName())); 204 this.typePropertyName = typePropertyName.orElseGet(()->ctx.getBeanTypePropertyName()); 205 206 fluentSetters = (ctx.isFindFluentSetters() || (beanFilter != null && beanFilter.isFluentSetters())); 207 208 // If @Bean.interfaceClass is specified on the parent class, then we want 209 // to use the properties defined on that class, not the subclass. 210 Class<?> c2 = (beanFilter != null && beanFilter.getInterfaceClass() != null ? beanFilter.getInterfaceClass() : c); 211 212 Class<?> stopClass = (beanFilter != null ? beanFilter.getStopClass() : Object.class); 213 if (stopClass == null) 214 stopClass = Object.class; 215 216 Map<String,BeanPropertyMeta.Builder> normalProps = map(); 217 218 boolean hasBean = ci.hasAnnotation(ctx, Bean.class); 219 boolean hasBeanIgnore = ci.hasAnnotation(ctx, BeanIgnore.class); 220 221 /// See if this class matches one the patterns in the exclude-class list. 222 if (ctx.isNotABean(c)) 223 return "Class matches exclude-class list"; 224 225 if (! hasBean && ! (cVis.isVisible(c.getModifiers()) || c.isAnonymousClass())) 226 return "Class is not public"; 227 228 if (hasBeanIgnore) 229 return "Class is annotated with @BeanIgnore"; 230 231 // Make sure it's serializable. 232 if (beanFilter == null && ctx.isBeansRequireSerializable() && ! ci.isChildOf(Serializable.class)) 233 return "Class is not serializable"; 234 235 // Look for @Beanc constructor on public constructors. 236 ci.forEachPublicConstructor(x -> x.hasAnnotation(ctx, Beanc.class), x -> { 237 if (constructor != null) 238 throw new BeanRuntimeException(c, "Multiple instances of '@Beanc' found."); 239 constructor = x; 240 constructorArgs = new String[0]; 241 ctx.forEachAnnotation(Beanc.class, x.inner(), y -> ! y.properties().isEmpty(), z -> constructorArgs = splita(z.properties())); 242 if (! x.hasNumParams(constructorArgs.length)) { 243 if (constructorArgs.length != 0) 244 throw new BeanRuntimeException(c, "Number of properties defined in '@Beanc' annotation does not match number of parameters in constructor."); 245 constructorArgs = new String[x.getParamCount()]; 246 IntValue i = IntValue.create(); 247 x.forEachParam(null, pi -> { 248 String pn = pi.getName(); 249 if (pn == null) 250 throw new BeanRuntimeException(c, "Could not find name for parameter #{0} of constructor ''{1}''", i, x.getFullName()); 251 constructorArgs[i.getAndIncrement()] = pn; 252 }); 253 } 254 constructor.setAccessible(); 255 }); 256 257 // Look for @Beanc on all other constructors. 258 if (constructor == null) { 259 ci.forEachDeclaredConstructor(x -> x.hasAnnotation(ctx, Beanc.class), x -> { 260 if (constructor != null) 261 throw new BeanRuntimeException(c, "Multiple instances of '@Beanc' found."); 262 constructor = x; 263 constructorArgs = new String[0]; 264 ctx.forEachAnnotation(Beanc.class, x.inner(), y -> ! y.properties().isEmpty(), z -> constructorArgs = splita(z.properties())); 265 if (! x.hasNumParams(constructorArgs.length)) { 266 if (constructorArgs.length != 0) 267 throw new BeanRuntimeException(c, "Number of properties defined in '@Beanc' annotation does not match number of parameters in constructor."); 268 constructorArgs = new String[x.getParamCount()]; 269 IntValue i = IntValue.create(); 270 x.forEachParam(null, y -> { 271 String pn = y.getName(); 272 if (pn == null) 273 throw new BeanRuntimeException(c, "Could not find name for parameter #{0} of constructor ''{1}''", i, x.getFullName()); 274 constructorArgs[i.getAndIncrement()] = pn; 275 }); 276 } 277 constructor.setAccessible(); 278 }); 279 } 280 281 // If this is an interface, look for impl classes defined in the context. 282 if (constructor == null) 283 constructor = implClassConstructor; 284 285 if (constructor == null) 286 constructor = ci.getNoArgConstructor(hasBean ? Visibility.PRIVATE : conVis); 287 288 if (constructor == null && beanFilter == null && ctx.isBeansRequireDefaultConstructor()) 289 return "Class does not have the required no-arg constructor"; 290 291 if (constructor != null) 292 constructor.setAccessible(); 293 294 // Explicitly defined property names in @Bean annotation. 295 Set<String> fixedBeanProps = set(); 296 Set<String> bpi = set(); 297 Set<String> bpx = set(); 298 Set<String> bpro = set(); 299 Set<String> bpwo = set(); 300 301 Set<String> filterProps = set(); // Names of properties defined in @Bean(properties) 302 303 if (beanFilter != null) { 304 305 Set<String> bfbpi = beanFilter.getProperties(); 306 307 filterProps.addAll(bfbpi); 308 309 // Get the 'properties' attribute if specified. 310 if (bpi.isEmpty()) 311 fixedBeanProps.addAll(bfbpi); 312 313 if (beanFilter.getPropertyNamer() != null) 314 propertyNamer = beanFilter.getPropertyNamer(); 315 316 bpro.addAll(beanFilter.getReadOnlyProperties()); 317 bpwo.addAll(beanFilter.getWriteOnlyProperties()); 318 } 319 320 fixedBeanProps.addAll(bpi); 321 322 if (propertyNamer == null) 323 propertyNamer = ctx.getPropertyNamer(); 324 325 // First populate the properties with those specified in the bean annotation to 326 // ensure that ordering first. 327 fixedBeanProps.forEach(x -> normalProps.put(x, BeanPropertyMeta.builder(beanMeta, x))); 328 329 if (ctx.isUseJavaBeanIntrospector()) { 330 BeanInfo bi = null; 331 if (! c2.isInterface()) 332 bi = Introspector.getBeanInfo(c2, stopClass); 333 else 334 bi = Introspector.getBeanInfo(c2, null); 335 if (bi != null) { 336 for (PropertyDescriptor pd : bi.getPropertyDescriptors()) { 337 String name = pd.getName(); 338 if (! normalProps.containsKey(name)) 339 normalProps.put(name, BeanPropertyMeta.builder(beanMeta, name)); 340 normalProps.get(name).setGetter(pd.getReadMethod()).setSetter(pd.getWriteMethod()); 341 } 342 } 343 344 } else /* Use 'better' introspection */ { 345 346 findBeanFields(ctx, c2, stopClass, fVis).forEach(x -> { 347 String name = findPropertyName(x); 348 if (name != null) { 349 if (! normalProps.containsKey(name)) 350 normalProps.put(name, BeanPropertyMeta.builder(beanMeta, name)); 351 normalProps.get(name).setField(x); 352 } 353 }); 354 355 List<BeanMethod> bms = findBeanMethods(ctx, c2, stopClass, mVis, propertyNamer, fluentSetters); 356 357 // Iterate through all the getters. 358 bms.forEach(x -> { 359 String pn = x.propertyName; 360 Method m = x.method; 361 if (! normalProps.containsKey(pn)) 362 normalProps.put(pn, new BeanPropertyMeta.Builder(beanMeta, pn)); 363 BeanPropertyMeta.Builder bpm = normalProps.get(pn); 364 if (x.methodType == GETTER) { 365 // Two getters. Pick the best. 366 if (bpm.getter != null) { 367 368 if (! ctx.hasAnnotation(Beanp.class, m) && ctx.hasAnnotation(Beanp.class, bpm.getter)) 369 m = bpm.getter; // @Beanp annotated method takes precedence. 370 371 else if (m.getName().startsWith("is") && bpm.getter.getName().startsWith("get")) 372 m = bpm.getter; // getX() overrides isX(). 373 } 374 bpm.setGetter(m); 375 } 376 }); 377 378 // Now iterate through all the setters. 379 bms.forEach(x -> { 380 if (x.methodType == SETTER) { 381 BeanPropertyMeta.Builder bpm = normalProps.get(x.propertyName); 382 if (x.matchesPropertyType(bpm)) 383 bpm.setSetter(x.method); 384 } 385 }); 386 387 // Now iterate through all the extraKeys. 388 bms.forEach(x -> { 389 if (x.methodType == EXTRAKEYS) { 390 BeanPropertyMeta.Builder bpm = normalProps.get(x.propertyName); 391 bpm.setExtraKeys(x.method); 392 } 393 }); 394 } 395 396 typeVarImpls = map(); 397 findTypeVarImpls(c, typeVarImpls); 398 if (typeVarImpls.isEmpty()) 399 typeVarImpls = null; 400 401 // Eliminate invalid properties, and set the contents of getterProps and setterProps. 402 for (Iterator<BeanPropertyMeta.Builder> i = normalProps.values().iterator(); i.hasNext();) { 403 BeanPropertyMeta.Builder p = i.next(); 404 try { 405 if (p.field == null) 406 p.setInnerField(findInnerBeanField(ctx, c, stopClass, p.name)); 407 408 if (p.validate(ctx, beanRegistry, typeVarImpls, bpro, bpwo)) { 409 410 if (p.getter != null) 411 getterProps.put(p.getter, p.name); 412 413 if (p.setter != null) 414 setterProps.put(p.setter, p.name); 415 416 } else { 417 i.remove(); 418 } 419 } catch (ClassNotFoundException e) { 420 throw new BeanRuntimeException(c, e.getLocalizedMessage()); 421 } 422 } 423 424 // Check for missing properties. 425 fixedBeanProps.forEach(x -> { 426 if (! normalProps.containsKey(x)) 427 throw new BeanRuntimeException(c, "The property ''{0}'' was defined on the @Bean(properties=X) annotation of class ''{1}'' but was not found on the class definition.", x, ci.getSimpleName()); 428 }); 429 430 // Mark constructor arg properties. 431 for (String fp : constructorArgs) { 432 BeanPropertyMeta.Builder m = normalProps.get(fp); 433 if (m == null) 434 throw new BeanRuntimeException(c, "The property ''{0}'' was defined on the @Beanc(properties=X) annotation but was not found on the class definition.", fp); 435 m.setAsConstructorArg(); 436 } 437 438 // Make sure at least one property was found. 439 if (beanFilter == null && ctx.isBeansRequireSomeProperties() && normalProps.isEmpty()) 440 return "No properties detected on bean class"; 441 442 sortProperties = (ctx.isSortProperties() || (beanFilter != null && beanFilter.isSortProperties())) && fixedBeanProps.isEmpty(); 443 444 properties = sortProperties ? CollectionUtils.sortedMap() : map(); 445 446 if (beanFilter != null && beanFilter.getTypeName() != null) 447 dictionaryName = beanFilter.getTypeName(); 448 if (dictionaryName == null) 449 dictionaryName = findDictionaryName(this.classMeta); 450 451 normalProps.forEach((k,v) -> { 452 BeanPropertyMeta pMeta = v.build(); 453 if (pMeta.isDyna()) 454 dynaProperty = pMeta; 455 properties.put(k, pMeta); 456 }); 457 458 // If a beanFilter is defined, look for inclusion and exclusion lists. 459 if (beanFilter != null) { 460 461 // Eliminated excluded properties if BeanFilter.excludeKeys is specified. 462 Set<String> bfbpi = beanFilter.getProperties(); 463 Set<String> bfbpx = beanFilter.getExcludeProperties(); 464 465 if (bpi.isEmpty() && ! bfbpi.isEmpty()) { 466 // Only include specified properties if BeanFilter.includeKeys is specified. 467 // Note that the order must match includeKeys. 468 Map<String,BeanPropertyMeta> properties2 = map(); 469 bfbpi.forEach(x -> { 470 if (properties.containsKey(x)) 471 properties2.put(x, properties.remove(x)); 472 }); 473 hiddenProperties.putAll(properties); 474 properties = properties2; 475 } 476 if (bpx.isEmpty() && ! bfbpx.isEmpty()) { 477 bfbpx.forEach(x -> hiddenProperties.put(x, properties.remove(x))); 478 } 479 } 480 481 if (! bpi.isEmpty()) { 482 Map<String,BeanPropertyMeta> properties2 = map(); 483 bpi.forEach(x -> { 484 if (properties.containsKey(x)) 485 properties2.put(x, properties.remove(x)); 486 }); 487 hiddenProperties.putAll(properties); 488 properties = properties2; 489 } 490 491 bpx.forEach(x -> hiddenProperties.put(x, properties.remove(x))); 492 493 if (pNames != null) { 494 Map<String,BeanPropertyMeta> properties2 = map(); 495 for (String k : pNames) { 496 if (properties.containsKey(k)) 497 properties2.put(k, properties.get(k)); 498 else 499 hiddenProperties.put(k, properties.get(k)); 500 } 501 properties = properties2; 502 } 503 504 } catch (BeanRuntimeException e) { 505 throw e; 506 } catch (Exception e) { 507 return "Exception: " + getStackTrace(e); 508 } 509 510 return null; 511 } 512 513 private String findDictionaryName(ClassMeta<?> cm) { 514 BeanRegistry br = cm.getBeanRegistry(); 515 if (br != null) { 516 String s = br.getTypeName(this.classMeta); 517 if (s != null) 518 return s; 519 } 520 Class<?> pcm = cm.innerClass.getSuperclass(); 521 if (pcm != null) { 522 String s = findDictionaryName(ctx.getClassMeta(pcm)); 523 if (s != null) 524 return s; 525 } 526 for (Class<?> icm : cm.innerClass.getInterfaces()) { 527 String s = findDictionaryName(ctx.getClassMeta(icm)); 528 if (s != null) 529 return s; 530 } 531 return null; 532 } 533 534 /* 535 * Returns the property name of the specified field if it's a valid property. 536 * Returns null if the field isn't a valid property. 537 */ 538 private String findPropertyName(Field f) { 539 List<Beanp> lp = list(); 540 List<Name> ln = list(); 541 ctx.forEachAnnotation(Beanp.class, f, x -> true, x -> lp.add(x)); 542 ctx.forEachAnnotation(Name.class, f, x -> true, x -> ln.add(x)); 543 String name = bpName(lp, ln); 544 if (isNotEmpty(name)) 545 return name; 546 return propertyNamer.getPropertyName(f.getName()); 547 } 548 } 549 550 /** 551 * Returns the {@link ClassMeta} of this bean. 552 * 553 * @return The {@link ClassMeta} of this bean. 554 */ 555 public final ClassMeta<T> getClassMeta() { 556 return classMeta; 557 } 558 559 /** 560 * Returns the dictionary name for this bean as defined through the {@link Bean#typeName() @Bean(typeName)} annotation. 561 * 562 * @return The dictionary name for this bean, or <jk>null</jk> if it has no dictionary name defined. 563 */ 564 public final String getDictionaryName() { 565 return dictionaryName; 566 } 567 568 /** 569 * Returns a mock bean property that resolves to the name <js>"_type"</js> and whose value always resolves to the 570 * dictionary name of the bean. 571 * 572 * @return The type name property. 573 */ 574 public final BeanPropertyMeta getTypeProperty() { 575 return typeProperty; 576 } 577 578 /** 579 * Possible property method types. 580 */ 581 enum MethodType { 582 UNKNOWN, 583 GETTER, 584 SETTER, 585 EXTRAKEYS; 586 } 587 588 /* 589 * Temporary getter/setter method struct. 590 */ 591 private static class BeanMethod { 592 String propertyName; 593 MethodType methodType; 594 Method method; 595 ClassInfo type; 596 597 BeanMethod(String propertyName, MethodType type, Method method) { 598 this.propertyName = propertyName; 599 this.methodType = type; 600 this.method = method; 601 if (type == MethodType.SETTER) 602 this.type = ClassInfo.of(method.getParameterTypes()[0]); 603 else 604 this.type = ClassInfo.of(method.getReturnType()); 605 } 606 607 /* 608 * Returns true if this method matches the class type of the specified property. 609 * Only meant to be used for setters. 610 */ 611 boolean matchesPropertyType(BeanPropertyMeta.Builder b) { 612 if (b == null) 613 return false; 614 615 // Don't do further validation if this is the "*" bean property. 616 if ("*".equals(b.name)) 617 return true; 618 619 // Get the bean property type from the getter/field. 620 Class<?> pt = null; 621 if (b.getter != null) 622 pt = b.getter.getReturnType(); 623 else if (b.field != null) 624 pt = b.field.getType(); 625 626 // Matches if only a setter is defined. 627 if (pt == null) 628 return true; 629 630 // Doesn't match if not same type or super type as getter/field. 631 if (! type.isParentOf(pt)) 632 return false; 633 634 // If a setter was previously set, only use this setter if it's a closer 635 // match (e.g. prev type is a superclass of this type). 636 if (b.setter == null) 637 return true; 638 639 return type.isStrictChildOf(b.setter.getParameterTypes()[0]); 640 } 641 642 @Override /* Object */ 643 public String toString() { 644 return method.toString(); 645 } 646 } 647 648 /* 649 * Find all the bean methods on this class. 650 * 651 * @param c The transformed class. 652 * @param stopClass Don't look above this class in the hierarchy. 653 * @param v The minimum method visibility. 654 * @param fixedBeanProps Only include methods whose properties are in this list. 655 * @param pn Use this property namer to determine property names from the method names. 656 */ 657 static final List<BeanMethod> findBeanMethods(BeanContext ctx, Class<?> c, Class<?> stopClass, Visibility v, PropertyNamer pn, boolean fluentSetters) { 658 List<BeanMethod> l = new LinkedList<>(); 659 660 forEachClass(ClassInfo.of(c), stopClass, c2 -> { 661 for (MethodInfo m : c2.getDeclaredMethods()) { 662 if (m.isStatic() || m.isBridge() || m.getParamCount() > 2 || m.hasAnnotation(ctx, BeanIgnore.class)) 663 continue; 664 Transient t = m.getAnnotation(ctx, Transient.class); 665 if (t != null && t.value()) 666 continue; 667 668 List<Beanp> lp = list(); 669 List<Name> ln = list(); 670 ctx.forEachAnnotation(Beanp.class, m.inner(), x -> true, x -> lp.add(x)); 671 ctx.forEachAnnotation(Name.class, m.inner(), x -> true, x -> ln.add(x)); 672 if (! (m.isVisible(v) || isNotEmpty(lp) || isNotEmpty(ln))) 673 continue; 674 675 String n = m.getSimpleName(); 676 677 List<ClassInfo> pt = m.getParamTypes(); 678 ClassInfo rt = m.getReturnType(); 679 MethodType methodType = UNKNOWN; 680 String bpName = bpName(lp, ln); 681 682 if (pt.isEmpty()) { 683 if ("*".equals(bpName)) { 684 if (rt.isChildOf(Collection.class)) { 685 methodType = EXTRAKEYS; 686 } else if (rt.isChildOf(Map.class)) { 687 methodType = GETTER; 688 } 689 n = bpName; 690 } else if (n.startsWith("get") && (! rt.is(Void.TYPE))) { 691 methodType = GETTER; 692 n = n.substring(3); 693 } else if (n.startsWith("is") && (rt.is(Boolean.TYPE) || rt.is(Boolean.class))) { 694 methodType = GETTER; 695 n = n.substring(2); 696 } else if (bpName != null) { 697 methodType = GETTER; 698 if (bpName.isEmpty()) { 699 if (n.startsWith("get")) 700 n = n.substring(3); 701 else if (n.startsWith("is")) 702 n = n.substring(2); 703 bpName = n; 704 } else { 705 n = bpName; 706 } 707 } 708 } else if (pt.size() == 1) { 709 if ("*".equals(bpName)) { 710 if (pt.get(0).isChildOf(Map.class)) { 711 methodType = SETTER; 712 n = bpName; 713 } else if (pt.get(0).is(String.class)) { 714 methodType = GETTER; 715 n = bpName; 716 } 717 } else if (n.startsWith("set") && (rt.isParentOf(c) || rt.is(Void.TYPE))) { 718 methodType = SETTER; 719 n = n.substring(3); 720 } else if (n.startsWith("with") && (rt.isParentOf(c))) { 721 methodType = SETTER; 722 n = n.substring(4); 723 } else if (bpName != null) { 724 methodType = SETTER; 725 if (bpName.isEmpty()) { 726 if (n.startsWith("set")) 727 n = n.substring(3); 728 bpName = n; 729 } else { 730 n = bpName; 731 } 732 } else if (fluentSetters && rt.isParentOf(c)) { 733 methodType = SETTER; 734 } 735 } else if (pt.size() == 2) { 736 if ("*".equals(bpName) && pt.get(0).is(String.class)) { 737 if (n.startsWith("set") && (rt.isParentOf(c) || rt.is(Void.TYPE))) { 738 methodType = SETTER; 739 } else { 740 methodType = GETTER; 741 } 742 n = bpName; 743 } 744 } 745 n = pn.getPropertyName(n); 746 747 if ("*".equals(bpName) && methodType == UNKNOWN) 748 throw new BeanRuntimeException(c, "Found @Beanp(\"*\") but could not determine method type on method ''{0}''.", m.getSimpleName()); 749 750 if (methodType != UNKNOWN) { 751 if (bpName != null && ! bpName.isEmpty()) 752 n = bpName; 753 if (n != null) 754 l.add(new BeanMethod(n, methodType, m.inner())); 755 } 756 } 757 }); 758 return l; 759 } 760 761 static final Collection<Field> findBeanFields(BeanContext ctx, Class<?> c, Class<?> stopClass, Visibility v) { 762 List<Field> l = new LinkedList<>(); 763 boolean noIgnoreTransients = ! ctx.isIgnoreTransientFields(); 764 forEachClass(ClassInfo.of(c), stopClass, c2 -> { 765 c2.forEachDeclaredField( 766 x -> x.isNotStatic() 767 && (x.isNotTransient() || noIgnoreTransients) 768 && (x.hasNoAnnotation(Transient.class) || noIgnoreTransients) 769 && x.hasNoAnnotation(ctx, BeanIgnore.class) 770 && (v.isVisible(x.inner()) || x.hasAnnotation(ctx, Beanp.class)), 771 x -> l.add(x.inner()) 772 ); 773 }); 774 return l; 775 } 776 777 static final Field findInnerBeanField(BeanContext ctx, Class<?> c, Class<?> stopClass, String name) { 778 boolean noIgnoreTransients = ! ctx.isIgnoreTransientFields(); 779 Value<Field> value = Value.empty(); 780 forEachClass(ClassInfo.of(c), stopClass, c2 -> { 781 FieldInfo f = c2.getDeclaredField( 782 x -> x.isNotStatic() 783 && (x.isNotTransient() || noIgnoreTransients) 784 && (x.hasNoAnnotation(Transient.class) || noIgnoreTransients) 785 && x.hasNoAnnotation(ctx, BeanIgnore.class) 786 && x.hasName(name) 787 ); 788 if (f != null) 789 value.set(f.inner()); 790 }); 791 return value.get(); 792 } 793 794// private static List<ClassInfo> findClasses(Class<?> c, Class<?> stopClass) { 795// LinkedList<ClassInfo> l = new LinkedList<>(); 796// forEachClass(ClassInfo.of(c), stopClass, x -> l.add(x)); 797// return l; 798// } 799 800 private static void forEachClass(ClassInfo c, Class<?> stopClass, Consumer<ClassInfo> consumer) { 801 ClassInfo sc = c.getSuperclass(); 802 if (sc != null && ! sc.is(stopClass)) 803 forEachClass(sc, stopClass, consumer); 804 c.getInterfaces().forEach(x -> forEachClass(x, stopClass, consumer)); 805 consumer.accept(c); 806 } 807 808 /** 809 * Returns the metadata on all properties associated with this bean. 810 * 811 * @return Metadata on all properties associated with this bean. 812 */ 813 public Collection<BeanPropertyMeta> getPropertyMetas() { 814 return u(alist(propertyArray)); 815 } 816 817 /** 818 * Performs an action on all matching properties. 819 * 820 * @param filter The filter to apply. 821 * @param action The action to apply. 822 */ 823 public void forEachProperty(Predicate<BeanPropertyMeta> filter, Consumer<BeanPropertyMeta> action) { 824 for (BeanPropertyMeta x : propertyArray) 825 if (test(filter, x)) 826 action.accept(x); 827 } 828 829 /** 830 * Performs a function on the first property that matches the specified filter. 831 * 832 * @param <T2> The type to convert the property to. 833 * @param filter The filter to apply. 834 * @param function The function to apply to the matching property. 835 * @return The result of the function. Never <jk>null</jk>. 836 */ 837 public <T2> Optional<T2> firstProperty(Predicate<BeanPropertyMeta> filter, Function<BeanPropertyMeta,T2> function) { 838 for (BeanPropertyMeta x : propertyArray) 839 if (test(filter, x)) 840 return Optional.ofNullable(function.apply(x)); 841 return Optional.empty(); 842 } 843 844 /** 845 * Returns metadata about the specified property. 846 * 847 * @param name The name of the property on this bean. 848 * @return The metadata about the property, or <jk>null</jk> if no such property exists on this bean. 849 */ 850 public BeanPropertyMeta getPropertyMeta(String name) { 851 BeanPropertyMeta bpm = properties.get(name); 852 if (bpm == null) 853 bpm = hiddenProperties.get(name); 854 if (bpm == null) 855 bpm = dynaProperty; 856 return bpm; 857 } 858 859 /** 860 * Creates a new instance of this bean. 861 * 862 * @param outer The outer object if bean class is a non-static inner member class. 863 * @return A new instance of this bean if possible, or <jk>null</jk> if not. 864 * @throws ExecutableException Exception occurred on invoked constructor/method/field. 865 */ 866 @SuppressWarnings("unchecked") 867 protected T newBean(Object outer) throws ExecutableException { 868 if (classMeta.isMemberClass()) { 869 if (constructor != null) 870 return constructor.<T>invoke(outer); 871 } else { 872 if (constructor != null) 873 return constructor.<T>invoke(); 874 InvocationHandler h = classMeta.getProxyInvocationHandler(); 875 if (h != null) { 876 ClassLoader cl = classMeta.innerClass.getClassLoader(); 877 return (T)Proxy.newProxyInstance(cl, new Class[] { classMeta.innerClass, java.io.Serializable.class }, h); 878 } 879 } 880 return null; 881 } 882 883 /** 884 * Recursively determines the classes represented by parameterized types in the class hierarchy of the specified 885 * type, and puts the results in the specified map. 886 * 887 * <p> 888 * For example, given the following classes... 889 * <p class='bjava'> 890 * <jk>public static class</jk> BeanA<T> { 891 * <jk>public</jk> T <jf>x</jf>; 892 * } 893 * <jk>public static class</jk> BeanB <jk>extends</jk> BeanA<Integer> {...} 894 * </p> 895 * <p> 896 * ...calling this method on {@code BeanB.class} will load the following data into {@code m} indicating 897 * that the {@code T} parameter on the BeanA class is implemented with an {@code Integer}: 898 * <p class='bcode'> 899 * {BeanA.class:[Integer.class]} 900 * </p> 901 * 902 * <p> 903 * TODO: This code doesn't currently properly handle the following situation: 904 * <p class='bjava'> 905 * <jk>public static class</jk> BeanB<T <jk>extends</jk> Number> <jk>extends</jk> BeanA<>; 906 * <jk>public static class</jk> BeanC <jk>extends</jk> BeanB<Integer>; 907 * </p> 908 * 909 * <p> 910 * When called on {@code BeanC}, the variable will be detected as a {@code Number}, not an {@code Integer}. 911 * If anyone can figure out a better way of doing this, please do so! 912 * 913 * @param t The type we're recursing. 914 * @param m Where the results are loaded. 915 */ 916 static final void findTypeVarImpls(Type t, Map<Class<?>,Class<?>[]> m) { 917 if (t instanceof Class) { 918 Class<?> c = (Class<?>)t; 919 findTypeVarImpls(c.getGenericSuperclass(), m); 920 for (Type ci : c.getGenericInterfaces()) 921 findTypeVarImpls(ci, m); 922 } else if (t instanceof ParameterizedType) { 923 ParameterizedType pt = (ParameterizedType)t; 924 Type rt = pt.getRawType(); 925 if (rt instanceof Class) { 926 Type[] gImpls = pt.getActualTypeArguments(); 927 Class<?>[] gTypes = new Class[gImpls.length]; 928 for (int i = 0; i < gImpls.length; i++) { 929 Type gt = gImpls[i]; 930 if (gt instanceof Class) 931 gTypes[i] = (Class<?>)gt; 932 else if (gt instanceof TypeVariable) { 933 TypeVariable<?> tv = (TypeVariable<?>)gt; 934 for (Type upperBound : tv.getBounds()) 935 if (upperBound instanceof Class) 936 gTypes[i] = (Class<?>)upperBound; 937 } 938 } 939 m.put((Class<?>)rt, gTypes); 940 findTypeVarImpls(pt.getRawType(), m); 941 } 942 } 943 } 944 945 /** 946 * Property read interceptor. 947 * 948 * <p> 949 * Called immediately after calling the getter to allow the value to be overridden. 950 * 951 * @param bean The bean from which the property was read. 952 * @param name The property name. 953 * @param value The value just extracted from calling the bean getter. 954 * @return The value to serialize. Default is just to return the existing value. 955 */ 956 public Object onReadProperty(Object bean, String name, Object value) { 957 return beanFilter == null ? value : beanFilter.readProperty(bean, name, value); 958 } 959 960 /** 961 * Property write interceptor. 962 * 963 * <p> 964 * Called immediately before calling theh setter to allow value to be overwridden. 965 * 966 * @param bean The bean from which the property was read. 967 * @param name The property name. 968 * @param value The value just parsed. 969 * @return The value to serialize. Default is just to return the existing value. 970 */ 971 public Object onWriteProperty(Object bean, String name, Object value) { 972 return beanFilter == null ? value : beanFilter.writeProperty(bean, name, value); 973 } 974 975 static final String bpName(List<Beanp> p, List<Name> n) { 976 if (p.isEmpty() && n.isEmpty()) 977 return null; 978 if (! n.isEmpty()) 979 return CollectionUtils.last(n).value(); 980 981 Value<String> name = Value.of(p.isEmpty() ? null : ""); 982 p.forEach(x -> { 983 if (! x.value().isEmpty()) 984 name.set(x.value()); 985 if (! x.name().isEmpty()) 986 name.set(x.name()); 987 }); 988 989 return name.orElse(null); 990 } 991 992 @Override /* Object */ 993 public String toString() { 994 StringBuilder sb = new StringBuilder(c.getName()); 995 sb.append(" {\n"); 996 for (BeanPropertyMeta pm : propertyArray) 997 sb.append('\t').append(pm.toString()).append(",\n"); 998 sb.append('}'); 999 return sb.toString(); 1000 } 1001 1002 @Override /* Object */ 1003 public int hashCode() { 1004 return classMeta.hashCode(); 1005 } 1006 1007 @Override /* Object */ 1008 public boolean equals(Object o) { 1009 return (o instanceof BeanMeta) && Utils.eq(this, (BeanMeta<?>)o, (x,y)->Utils.eq(x.classMeta, y.classMeta)); 1010 } 1011}