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