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