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