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