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