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