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