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