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