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