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