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