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