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.field == null) 351 p.setInnerField(findInnerBeanField(c, stopClass, p.name)); 352 353 if (p.validate(ctx, beanRegistry, typeVarImpls)) { 354 355 if (p.getter != null) 356 getterProps.put(p.getter, p.name); 357 358 if (p.setter != null) 359 setterProps.put(p.setter, p.name); 360 361 } else { 362 i.remove(); 363 } 364 } catch (ClassNotFoundException e) { 365 throw new BeanRuntimeException(c, e.getLocalizedMessage()); 366 } 367 } 368 369 // Check for missing properties. 370 for (String fp : fixedBeanProps) 371 if (! normalProps.containsKey(fp)) 372 throw new BeanRuntimeException(c, "The property ''{0}'' was defined on the @Bean(properties=X) annotation but was not found on the class definition.", fp); 373 374 // Mark constructor arg properties. 375 for (String fp : constructorArgs) { 376 BeanPropertyMeta.Builder m = normalProps.get(fp); 377 if (m == null) 378 throw new BeanRuntimeException(c, "The property ''{0}'' was defined on the @BeanConstructor(properties=X) annotation but was not found on the class definition.", fp); 379 m.setAsConstructorArg(); 380 } 381 382 // Make sure at least one property was found. 383 if (beanFilter == null && ctx.isBeansRequireSomeProperties() && normalProps.size() == 0) 384 return "No properties detected on bean class"; 385 386 sortProperties = (ctx.isSortProperties() || (beanFilter != null && beanFilter.isSortProperties())) && fixedBeanProps.isEmpty(); 387 388 properties = sortProperties ? new TreeMap<String,BeanPropertyMeta>() : new LinkedHashMap<String,BeanPropertyMeta>(); 389 390 if (beanFilter != null && beanFilter.getTypeName() != null) 391 dictionaryName = beanFilter.getTypeName(); 392 if (dictionaryName == null) 393 dictionaryName = findDictionaryName(this.classMeta); 394 395 for (Map.Entry<String,BeanPropertyMeta.Builder> e : normalProps.entrySet()) { 396 BeanPropertyMeta pMeta = e.getValue().build(); 397 if (pMeta.isDyna()) 398 dynaProperty = pMeta; 399 properties.put(e.getKey(), pMeta); 400 } 401 402 // If a beanFilter is defined, look for inclusion and exclusion lists. 403 if (beanFilter != null) { 404 405 // Eliminated excluded properties if BeanFilter.excludeKeys is specified. 406 String[] includeKeys = beanFilter.getProperties(); 407 String[] excludeKeys = beanFilter.getExcludeProperties(); 408 if (excludeKeys != null && excludeProperties == null) { 409 for (String k : excludeKeys) 410 properties.remove(k); 411 412 // Only include specified properties if BeanFilter.includeKeys is specified. 413 // Note that the order must match includeKeys. 414 } else if (includeKeys != null) { 415 Map<String,BeanPropertyMeta> properties2 = new LinkedHashMap<>(); 416 for (String k : includeKeys) { 417 if (properties.containsKey(k)) 418 properties2.put(k, properties.get(k)); 419 } 420 properties = properties2; 421 } 422 } 423 424 if (excludeProperties != null) 425 for (String ep : excludeProperties) 426 properties.remove(ep); 427 428 if (pNames != null) { 429 Map<String,BeanPropertyMeta> properties2 = new LinkedHashMap<>(); 430 for (String k : pNames) { 431 if (properties.containsKey(k)) 432 properties2.put(k, properties.get(k)); 433 } 434 properties = properties2; 435 } 436 437 } catch (BeanRuntimeException e) { 438 throw e; 439 } catch (Exception e) { 440 return "Exception: " + getStackTrace(e); 441 } 442 443 return null; 444 } 445 446 private String findDictionaryName(ClassMeta<?> cm) { 447 BeanRegistry br = cm.getBeanRegistry(); 448 if (br != null) { 449 String s = br.getTypeName(this.classMeta); 450 if (s != null) 451 return s; 452 } 453 Class<?> pcm = cm.innerClass.getSuperclass(); 454 if (pcm != null) { 455 String s = findDictionaryName(ctx.getClassMeta(pcm)); 456 if (s != null) 457 return s; 458 } 459 for (Class<?> icm : cm.innerClass.getInterfaces()) { 460 String s = findDictionaryName(ctx.getClassMeta(icm)); 461 if (s != null) 462 return s; 463 } 464 return null; 465 } 466 467 /* 468 * Returns the property name of the specified field if it's a valid property. 469 * Returns null if the field isn't a valid property. 470 */ 471 private String findPropertyName(Field f, Set<String> fixedBeanProps) { 472 BeanProperty bp = f.getAnnotation(BeanProperty.class); 473 String name = bpName(bp); 474 if (isNotEmpty(name)) { 475 if (fixedBeanProps.isEmpty() || fixedBeanProps.contains(name)) 476 return name; 477 return null; // Could happen if filtered via BEAN_includeProperties/BEAN_excludeProperties. 478 } 479 name = propertyNamer.getPropertyName(f.getName()); 480 if (fixedBeanProps.isEmpty() || fixedBeanProps.contains(name)) 481 return name; 482 return null; 483 } 484 } 485 486 /** 487 * Returns the {@link ClassMeta} of this bean. 488 * 489 * @return The {@link ClassMeta} of this bean. 490 */ 491 @BeanIgnore 492 public final ClassMeta<T> getClassMeta() { 493 return classMeta; 494 } 495 496 /** 497 * Returns the dictionary name for this bean as defined through the {@link Bean#typeName() @Bean(typeName)} annotation. 498 * 499 * @return The dictionary name for this bean, or <jk>null</jk> if it has no dictionary name defined. 500 */ 501 public final String getDictionaryName() { 502 return dictionaryName; 503 } 504 505 /** 506 * Returns a mock bean property that resolves to the name <js>"_type"</js> and whose value always resolves to the 507 * dictionary name of the bean. 508 * 509 * @return The type name property. 510 */ 511 public final BeanPropertyMeta getTypeProperty() { 512 return typeProperty; 513 } 514 515 /** 516 * Possible property method types. 517 */ 518 static enum MethodType { 519 UNKNOWN, 520 GETTER, 521 SETTER, 522 EXTRAKEYS; 523 } 524 525 /* 526 * Temporary getter/setter method struct. 527 */ 528 private static final class BeanMethod { 529 String propertyName; 530 MethodType methodType; 531 Method method; 532 Class<?> type; 533 534 BeanMethod(String propertyName, MethodType type, Method method) { 535 this.propertyName = propertyName; 536 this.methodType = type; 537 this.method = method; 538 if (type == MethodType.SETTER) 539 this.type = method.getParameterTypes()[0]; 540 else 541 this.type = method.getReturnType(); 542 } 543 544 /* 545 * Returns true if this method matches the class type of the specified property. 546 * Only meant to be used for setters. 547 */ 548 boolean matchesPropertyType(BeanPropertyMeta.Builder b) { 549 if (b == null) 550 return false; 551 552 // Don't do further validation if this is the "*" bean property. 553 if ("*".equals(b.name)) 554 return true; 555 556 // Get the bean property type from the getter/field. 557 Class<?> pt = null; 558 if (b.getter != null) 559 pt = b.getter.getReturnType(); 560 else if (b.field != null) 561 pt = b.field.getType(); 562 563 // Matches if only a setter is defined. 564 if (pt == null) 565 return true; 566 567 // Doesn't match if not same type or super type as getter/field. 568 if (! isParentClass(type, pt)) 569 return false; 570 571 // If a setter was previously set, only use this setter if it's a closer 572 // match (e.g. prev type is a superclass of this type). 573 if (b.setter == null) 574 return true; 575 576 Class<?> prevType = b.setter.getParameterTypes()[0]; 577 return isParentClass(prevType, type, true); 578 } 579 580 @Override /* Object */ 581 public String toString() { 582 return method.toString(); 583 } 584 } 585 586 /* 587 * Find all the bean methods on this class. 588 * 589 * @param c The transformed class. 590 * @param stopClass Don't look above this class in the hierarchy. 591 * @param v The minimum method visibility. 592 * @param fixedBeanProps Only include methods whose properties are in this list. 593 * @param pn Use this property namer to determine property names from the method names. 594 */ 595 static final List<BeanMethod> findBeanMethods(Class<?> c, Class<?> stopClass, Visibility v, Set<String> fixedBeanProps, Set<String> filterProps, PropertyNamer pn, boolean fluentSetters) { 596 List<BeanMethod> l = new LinkedList<>(); 597 598 for (Class<?> c2 : findClasses(c, stopClass)) { 599 for (Method m : c2.getDeclaredMethods()) { 600 if (isStatic(m)) 601 continue; 602 if (m.isBridge()) // This eliminates methods with covariant return types from parent classes on child classes. 603 continue; 604 605 BeanIgnore bi = getAnnotation(BeanIgnore.class, m); 606 if (bi != null) 607 continue; 608 609 BeanProperty bp = getAnnotation(BeanProperty.class, m); 610 if (! (v.isVisible(m) || bp != null)) 611 continue; 612 613 String n = m.getName(); 614 615 Class<?>[] pt = m.getParameterTypes(); 616 Class<?> rt = m.getReturnType(); 617 MethodType methodType = UNKNOWN; 618 String bpName = bpName(bp); 619 620 if (! (isEmpty(bpName) || filterProps.isEmpty() || filterProps.contains(bpName))) 621 throw new BeanRuntimeException(c, "Found @BeanProperty(\"{0}\") but name was not found in @Bean(properties)", bpName); 622 623 if (pt.length == 0) { 624 if ("*".equals(bpName)) { 625 if (isParentClass(Collection.class, rt)) { 626 methodType = EXTRAKEYS; 627 } else if (isParentClass(Map.class, rt)) { 628 methodType = GETTER; 629 } 630 n = bpName; 631 } else if (n.startsWith("get") && (! rt.equals(Void.TYPE))) { 632 methodType = GETTER; 633 n = n.substring(3); 634 } else if (n.startsWith("is") && (rt.equals(Boolean.TYPE) || rt.equals(Boolean.class))) { 635 methodType = GETTER; 636 n = n.substring(2); 637 } else if (bpName != null) { 638 methodType = GETTER; 639 if (bpName.isEmpty()) { 640 if (n.startsWith("get")) 641 n = n.substring(3); 642 else if (n.startsWith("is")) 643 n = n.substring(2); 644 bpName = n; 645 } else { 646 n = bpName; 647 } 648 } 649 } else if (pt.length == 1) { 650 if ("*".equals(bpName)) { 651 if (isParentClass(Map.class, pt[0])) { 652 methodType = SETTER; 653 n = bpName; 654 } else if (pt[0] == String.class) { 655 methodType = GETTER; 656 n = bpName; 657 } 658 } else if (n.startsWith("set") && (isParentClass(rt, c) || rt.equals(Void.TYPE))) { 659 methodType = SETTER; 660 n = n.substring(3); 661 } else if (bpName != null) { 662 methodType = SETTER; 663 if (bpName.isEmpty()) { 664 if (n.startsWith("set")) 665 n = n.substring(3); 666 bpName = n; 667 } else { 668 n = bpName; 669 } 670 } else if (fluentSetters && isParentClass(rt, c)) { 671 methodType = SETTER; 672 } 673 } else if (pt.length == 2) { 674 if ("*".equals(bpName) && pt[0] == String.class) { 675 if (n.startsWith("set") && (isParentClass(rt, c) || rt.equals(Void.TYPE))) { 676 methodType = SETTER; 677 } else { 678 methodType = GETTER; 679 } 680 n = bpName; 681 } 682 } 683 n = pn.getPropertyName(n); 684 685 if ("*".equals(bpName) && methodType == UNKNOWN) 686 throw new BeanRuntimeException(c, "Found @BeanProperty(\"*\") but could not determine method type on method ''{0}''.", m.getName()); 687 688 if (methodType != UNKNOWN) { 689 if (bpName != null && ! bpName.isEmpty()) { 690 n = bpName; 691 if (! fixedBeanProps.isEmpty()) 692 if (! fixedBeanProps.contains(n)) 693 n = null; // Could happen if filtered via BEAN_includeProperties/BEAN_excludeProperties 694 } 695 if (n != null) 696 l.add(new BeanMethod(n, methodType, m)); 697 } 698 } 699 } 700 return l; 701 } 702 703 static final Collection<Field> findBeanFields(Class<?> c, Class<?> stopClass, Visibility v, Set<String> filterProps) { 704 List<Field> l = new LinkedList<>(); 705 for (Class<?> c2 : findClasses(c, stopClass)) { 706 for (Field f : c2.getDeclaredFields()) { 707 if (isAny(f, STATIC, TRANSIENT)) 708 continue; 709 if (f.isAnnotationPresent(BeanIgnore.class)) 710 continue; 711 712 BeanProperty bp = f.getAnnotation(BeanProperty.class); 713 String bpName = bpName(bp); 714 715 if (! (v.isVisible(f) || bp != null)) 716 continue; 717 718 if (! (isEmpty(bpName) || filterProps.isEmpty() || filterProps.contains(bpName))) 719 throw new BeanRuntimeException(c, "Found @BeanProperty(\"{0}\") but name was not found in @Bean(properties)", bpName); 720 721 l.add(f); 722 } 723 } 724 return l; 725 } 726 727 static final Field findInnerBeanField(Class<?> c, Class<?> stopClass, String name) { 728 for (Class<?> c2 : findClasses(c, stopClass)) { 729 for (Field f : c2.getDeclaredFields()) { 730 if (isAny(f, STATIC, TRANSIENT)) 731 continue; 732 if (f.isAnnotationPresent(BeanIgnore.class)) 733 continue; 734 if (f.getName().equals(name)) 735 return f; 736 } 737 } 738 return null; 739 } 740 741 private static List<Class<?>> findClasses(Class<?> c, Class<?> stopClass) { 742 LinkedList<Class<?>> l = new LinkedList<>(); 743 findClasses(c, l, stopClass); 744 return l; 745 } 746 747 private static void findClasses(Class<?> c, LinkedList<Class<?>> l, Class<?> stopClass) { 748 while (c != null && stopClass != c) { 749 l.addFirst(c); 750 for (Class<?> ci : c.getInterfaces()) 751 findClasses(ci, l, stopClass); 752 c = c.getSuperclass(); 753 } 754 } 755 756 /** 757 * Returns the metadata on all properties associated with this bean. 758 * 759 * @return Metadata on all properties associated with this bean. 760 */ 761 public Collection<BeanPropertyMeta> getPropertyMetas() { 762 return this.properties.values(); 763 } 764 765 /** 766 * Returns the metadata on the specified list of properties. 767 * 768 * @param pNames The list of properties to retrieve. If <jk>null</jk>, returns all properties. 769 * @return The metadata on the specified list of properties. 770 */ 771 public Collection<BeanPropertyMeta> getPropertyMetas(final String...pNames) { 772 if (pNames == null) 773 return getPropertyMetas(); 774 List<BeanPropertyMeta> l = new ArrayList<>(pNames.length); 775 for (int i = 0; i < pNames.length; i++) 776 l.add(getPropertyMeta(pNames[i])); 777 return l; 778 } 779 780 /** 781 * Returns the language-specified extended metadata on this bean class. 782 * 783 * @param metaDataClass The name of the metadata class to create. 784 * @return Extended metadata on this bean class. Never <jk>null</jk>. 785 */ 786 public <M extends BeanMetaExtended> M getExtendedMeta(Class<M> metaDataClass) { 787 return extMeta.get(metaDataClass, this); 788 } 789 790 /** 791 * Returns metadata about the specified property. 792 * 793 * @param name The name of the property on this bean. 794 * @return The metadata about the property, or <jk>null</jk> if no such property exists on this bean. 795 */ 796 public BeanPropertyMeta getPropertyMeta(String name) { 797 BeanPropertyMeta bpm = properties.get(name); 798 if (bpm == null) 799 bpm = dynaProperty; 800 return bpm; 801 } 802 803 /** 804 * Creates a new instance of this bean. 805 * 806 * @param outer The outer object if bean class is a non-static inner member class. 807 * @return A new instance of this bean if possible, or <jk>null</jk> if not. 808 * @throws IllegalArgumentException Thrown by constructor. 809 * @throws InstantiationException Thrown by constructor. 810 * @throws IllegalAccessException Thrown by constructor. 811 * @throws InvocationTargetException Thrown by constructor. 812 */ 813 @SuppressWarnings("unchecked") 814 protected T newBean(Object outer) throws IllegalArgumentException, InstantiationException, IllegalAccessException, InvocationTargetException { 815 if (classMeta.isMemberClass()) { 816 if (constructor != null) 817 return constructor.newInstance(outer); 818 } else { 819 if (constructor != null) 820 return constructor.newInstance((Object[])null); 821 InvocationHandler h = classMeta.getProxyInvocationHandler(); 822 if (h != null) { 823 ClassLoader cl = classMeta.innerClass.getClassLoader(); 824 return (T)Proxy.newProxyInstance(cl, new Class[] { classMeta.innerClass, java.io.Serializable.class }, h); 825 } 826 } 827 return null; 828 } 829 830 /** 831 * Recursively determines the classes represented by parameterized types in the class hierarchy of the specified 832 * type, and puts the results in the specified map. 833 * 834 * <p> 835 * For example, given the following classes... 836 * <p class='bcode w800'> 837 * public static class BeanA<T> { 838 * public T x; 839 * } 840 * public static class BeanB extends BeanA<Integer>} {...} 841 * </p> 842 * <p> 843 * ...calling this method on {@code BeanB.class} will load the following data into {@code m} indicating 844 * that the {@code T} parameter on the BeanA class is implemented with an {@code Integer}: 845 * <p class='bcode w800'> 846 * {BeanA.class:[Integer.class]} 847 * </p> 848 * 849 * <p> 850 * TODO: This code doesn't currently properly handle the following situation: 851 * <p class='bcode w800'> 852 * public static class BeanB<T extends Number> extends BeanA<T>; 853 * public static class BeanC extends BeanB<Integer>; 854 * </p> 855 * 856 * <p> 857 * When called on {@code BeanC}, the variable will be detected as a {@code Number}, not an {@code Integer}. 858 * If anyone can figure out a better way of doing this, please do so! 859 * 860 * @param t The type we're recursing. 861 * @param m Where the results are loaded. 862 */ 863 static final void findTypeVarImpls(Type t, Map<Class<?>,Class<?>[]> m) { 864 if (t instanceof Class) { 865 Class<?> c = (Class<?>)t; 866 findTypeVarImpls(c.getGenericSuperclass(), m); 867 for (Type ci : c.getGenericInterfaces()) 868 findTypeVarImpls(ci, m); 869 } else if (t instanceof ParameterizedType) { 870 ParameterizedType pt = (ParameterizedType)t; 871 Type rt = pt.getRawType(); 872 if (rt instanceof Class) { 873 Type[] gImpls = pt.getActualTypeArguments(); 874 Class<?>[] gTypes = new Class[gImpls.length]; 875 for (int i = 0; i < gImpls.length; i++) { 876 Type gt = gImpls[i]; 877 if (gt instanceof Class) 878 gTypes[i] = (Class<?>)gt; 879 else if (gt instanceof TypeVariable) { 880 TypeVariable<?> tv = (TypeVariable<?>)gt; 881 for (Type upperBound : tv.getBounds()) 882 if (upperBound instanceof Class) 883 gTypes[i] = (Class<?>)upperBound; 884 } 885 } 886 m.put((Class<?>)rt, gTypes); 887 findTypeVarImpls(pt.getRawType(), m); 888 } 889 } 890 } 891 892 static final String bpName(BeanProperty bp) { 893 if (bp == null) 894 return null; 895 if (! bp.name().isEmpty()) 896 return bp.name(); 897 return bp.value(); 898 } 899 900 @Override /* Object */ 901 public String toString() { 902 StringBuilder sb = new StringBuilder(c.getName()); 903 sb.append(" {\n"); 904 for (BeanPropertyMeta pm : this.properties.values()) 905 sb.append('\t').append(pm.toString()).append(",\n"); 906 sb.append('}'); 907 return sb.toString(); 908 } 909}