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