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 Beanp @Beanp} 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 Beanc 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      @SuppressWarnings("deprecation")
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               if (x.hasAnnotation(Beanc.class)) {
242                  if (constructor != null)
243                     throw new BeanRuntimeException(c, "Multiple instances of '@Beanc' found.");
244                  constructor = x;
245                  constructorArgs = split(x.getAnnotation(Beanc.class).properties());
246                  if (constructorArgs.length != x.getParamCount()) {
247                     if (constructorArgs.length != 0)
248                        throw new BeanRuntimeException(c, "Number of properties defined in '@Beanc' annotation does not match number of parameters in constructor.");
249                     constructorArgs = new String[x.getParamCount()];
250                     int i = 0;
251                     for (ParamInfo pi : x.getParams()) {
252                        String pn = pi.getName();
253                        if (pn == null)
254                           throw new BeanRuntimeException(c, "Could not find name for parameter #{0} of constructor ''{1}''", i, x.getFullName());
255                        constructorArgs[i++] = pn;
256                     }
257                  }
258                  constructor.setAccessible();
259               }
260            }
261
262            // If this is an interface, look for impl classes defined in the context.
263            if (constructor == null)
264               constructor = ctx.getImplClassConstructor(c, conVis);
265
266            if (constructor == null)
267               constructor = ci.getNoArgConstructor(conVis);
268
269            if (constructor == null && beanFilter == null && ctx.isBeansRequireDefaultConstructor())
270               return "Class does not have the required no-arg constructor";
271
272            if (constructor != null)
273               constructor.setAccessible();
274
275            // Explicitly defined property names in @Bean annotation.
276            Set<String> fixedBeanProps = new LinkedHashSet<>();
277            Set<String> bpi = new LinkedHashSet<>(ctx.getBpi(c));
278            Set<String> bpx = new LinkedHashSet<>(ctx.getBpx(c));
279            Set<String> bpro = new LinkedHashSet<>(ctx.getBpro(c));
280            Set<String> bpwo = new LinkedHashSet<>(ctx.getBpwo(c));
281
282            Set<String> filterProps = new HashSet<>();  // Names of properties defined in @Bean(properties)
283
284            if (beanFilter != null) {
285
286               Set<String> bfbpi = beanFilter.getBpi();
287
288               filterProps.addAll(bfbpi);
289
290               // Get the 'properties' attribute if specified.
291               if (bpi.isEmpty())
292                  fixedBeanProps.addAll(bfbpi);
293
294               if (beanFilter.getPropertyNamer() != null)
295                  propertyNamer = beanFilter.getPropertyNamer();
296
297               bpro.addAll(beanFilter.getBpro());
298               bpwo.addAll(beanFilter.getBpwo());
299            }
300
301            fixedBeanProps.addAll(bpi);
302
303            if (propertyNamer == null)
304               propertyNamer = ctx.getPropertyNamer();
305
306            // First populate the properties with those specified in the bean annotation to
307            // ensure that ordering first.
308            for (String name : fixedBeanProps)
309               normalProps.put(name, BeanPropertyMeta.builder(beanMeta, name));
310
311            if (ctx.isUseJavaBeanIntrospector()) {
312               BeanInfo bi = null;
313               if (! c2.isInterface())
314                  bi = Introspector.getBeanInfo(c2, stopClass);
315               else
316                  bi = Introspector.getBeanInfo(c2, null);
317               if (bi != null) {
318                  for (PropertyDescriptor pd : bi.getPropertyDescriptors()) {
319                     String name = pd.getName();
320                     if (! normalProps.containsKey(name))
321                        normalProps.put(name, BeanPropertyMeta.builder(beanMeta, name));
322                     normalProps.get(name).setGetter(pd.getReadMethod()).setSetter(pd.getWriteMethod());
323                  }
324               }
325
326            } else /* Use 'better' introspection */ {
327
328               for (Field f : findBeanFields(c2, stopClass, fVis, filterProps)) {
329                  String name = findPropertyName(f, fixedBeanProps);
330                  if (name != null) {
331                     if (! normalProps.containsKey(name))
332                        normalProps.put(name, BeanPropertyMeta.builder(beanMeta, name));
333                     normalProps.get(name).setField(f);
334                  }
335               }
336
337               List<BeanMethod> bms = findBeanMethods(c2, stopClass, mVis, fixedBeanProps, filterProps, propertyNamer, fluentSetters);
338
339               // Iterate through all the getters.
340               for (BeanMethod bm : bms) {
341                  String pn = bm.propertyName;
342                  Method m = bm.method;
343                  if (! normalProps.containsKey(pn))
344                     normalProps.put(pn, new BeanPropertyMeta.Builder(beanMeta, pn));
345                  BeanPropertyMeta.Builder bpm = normalProps.get(pn);
346                  if (bm.methodType == GETTER) {
347                     // Two getters.  Pick the best.
348                     if (bpm.getter != null) {
349
350                        if (m.getAnnotation(BeanProperty.class) == null && bpm.getter.getAnnotation(BeanProperty.class) != null)
351                           m = bpm.getter;  // @BeanProperty annotated method takes precedence.
352
353                        else if (m.getAnnotation(Beanp.class) == null && bpm.getter.getAnnotation(Beanp.class) != null)
354                           m = bpm.getter;  // @Beanp annotated method takes precedence.
355
356                        else if (m.getName().startsWith("is") && bpm.getter.getName().startsWith("get"))
357                           m = bpm.getter;  // getX() overrides isX().
358                     }
359                     bpm.setGetter(m);
360                  }
361               }
362
363               // Now iterate through all the setters.
364               for (BeanMethod bm : bms) {
365                  if (bm.methodType == SETTER) {
366                     BeanPropertyMeta.Builder bpm = normalProps.get(bm.propertyName);
367                     if (bm.matchesPropertyType(bpm))
368                        bpm.setSetter(bm.method);
369                  }
370               }
371
372               // Now iterate through all the extraKeys.
373               for (BeanMethod bm : bms) {
374                  if (bm.methodType == EXTRAKEYS) {
375                     BeanPropertyMeta.Builder bpm = normalProps.get(bm.propertyName);
376                     bpm.setExtraKeys(bm.method);
377                  }
378               }
379            }
380
381            typeVarImpls = new HashMap<>();
382            findTypeVarImpls(c, typeVarImpls);
383            if (typeVarImpls.isEmpty())
384               typeVarImpls = null;
385
386            // Eliminate invalid properties, and set the contents of getterProps and setterProps.
387            for (Iterator<BeanPropertyMeta.Builder> i = normalProps.values().iterator(); i.hasNext();) {
388               BeanPropertyMeta.Builder p = i.next();
389               try {
390                  if (p.field == null)
391                     p.setInnerField(findInnerBeanField(c, stopClass, p.name));
392
393                  if (p.validate(ctx, beanRegistry, typeVarImpls, bpro, bpwo)) {
394
395                     if (p.getter != null)
396                        getterProps.put(p.getter, p.name);
397
398                     if (p.setter != null)
399                        setterProps.put(p.setter, p.name);
400
401                  } else {
402                     i.remove();
403                  }
404               } catch (ClassNotFoundException e) {
405                  throw new BeanRuntimeException(c, e.getLocalizedMessage());
406               }
407            }
408
409            // Check for missing properties.
410            for (String fp : fixedBeanProps)
411               if (! normalProps.containsKey(fp))
412                  throw new BeanRuntimeException(c, "The property ''{0}'' was defined on the @Bean(bpi=X) annotation but was not found on the class definition.", fp);
413
414            // Mark constructor arg properties.
415            for (String fp : constructorArgs) {
416               BeanPropertyMeta.Builder m = normalProps.get(fp);
417               if (m == null)
418                  throw new BeanRuntimeException(c, "The property ''{0}'' was defined on the @Beanc(properties=X) annotation but was not found on the class definition.", fp);
419               m.setAsConstructorArg();
420            }
421
422            // Make sure at least one property was found.
423            if (beanFilter == null && ctx.isBeansRequireSomeProperties() && normalProps.size() == 0)
424               return "No properties detected on bean class";
425
426            sortProperties = (ctx.isSortProperties() || (beanFilter != null && beanFilter.isSortProperties())) && fixedBeanProps.isEmpty();
427
428            if (sortProperties)
429               properties = new TreeMap<>();
430            else
431               properties = new LinkedHashMap<>();
432
433            if (beanFilter != null && beanFilter.getTypeName() != null)
434               dictionaryName = beanFilter.getTypeName();
435            if (dictionaryName == null)
436               dictionaryName = findDictionaryName(this.classMeta);
437
438            for (Map.Entry<String,BeanPropertyMeta.Builder> e : normalProps.entrySet()) {
439               BeanPropertyMeta pMeta = e.getValue().build();
440               if (pMeta.isDyna())
441                  dynaProperty = pMeta;
442               properties.put(e.getKey(), pMeta);
443            }
444
445            // If a beanFilter is defined, look for inclusion and exclusion lists.
446            if (beanFilter != null) {
447
448               // Eliminated excluded properties if BeanFilter.excludeKeys is specified.
449               Set<String> bfbpi = beanFilter.getBpi();
450               Set<String> bfbpx = beanFilter.getBpx();
451
452               if (bpx.isEmpty() && ! bfbpx.isEmpty()) {
453
454                  for (String k : bfbpx)
455                     properties.remove(k);
456
457               // Only include specified properties if BeanFilter.includeKeys is specified.
458               // Note that the order must match includeKeys.
459               } else if (! bfbpi.isEmpty()) {
460                  Map<String,BeanPropertyMeta> properties2 = new LinkedHashMap<>();
461                  for (String k : bfbpi) {
462                     if (properties.containsKey(k))
463                        properties2.put(k, properties.get(k));
464                  }
465                  properties = properties2;
466               }
467            }
468
469            for (String ep : bpx)
470               properties.remove(ep);
471
472            if (pNames != null) {
473               Map<String,BeanPropertyMeta> properties2 = new LinkedHashMap<>();
474               for (String k : pNames) {
475                  if (properties.containsKey(k))
476                     properties2.put(k, properties.get(k));
477               }
478               properties = properties2;
479            }
480
481         } catch (BeanRuntimeException e) {
482            throw e;
483         } catch (Exception e) {
484            return "Exception:  " + getStackTrace(e);
485         }
486
487         return null;
488      }
489
490      private String findDictionaryName(ClassMeta<?> cm) {
491         BeanRegistry br = cm.getBeanRegistry();
492         if (br != null) {
493            String s = br.getTypeName(this.classMeta);
494            if (s != null)
495               return s;
496         }
497         Class<?> pcm = cm.innerClass.getSuperclass();
498         if (pcm != null) {
499            String s = findDictionaryName(ctx.getClassMeta(pcm));
500            if (s != null)
501               return s;
502         }
503         for (Class<?> icm : cm.innerClass.getInterfaces()) {
504            String s = findDictionaryName(ctx.getClassMeta(icm));
505            if (s != null)
506               return s;
507         }
508         return null;
509      }
510
511      /*
512       * Returns the property name of the specified field if it's a valid property.
513       * Returns null if the field isn't a valid property.
514       */
515      private String findPropertyName(Field f, Set<String> fixedBeanProps) {
516         @SuppressWarnings("deprecation")
517         BeanProperty px = f.getAnnotation(BeanProperty.class);
518         Beanp p = f.getAnnotation(Beanp.class);
519         Name n = f.getAnnotation(Name.class);
520         String name = bpName(px, p, n);
521         if (isNotEmpty(name)) {
522            if (fixedBeanProps.isEmpty() || fixedBeanProps.contains(name))
523               return name;
524            return null;  // Could happen if filtered via BEAN_bpi/BEAN_bpx.
525         }
526         name = propertyNamer.getPropertyName(f.getName());
527         if (fixedBeanProps.isEmpty() || fixedBeanProps.contains(name))
528            return name;
529         return null;
530      }
531   }
532
533   /**
534    * Returns the {@link ClassMeta} of this bean.
535    *
536    * @return The {@link ClassMeta} of this bean.
537    */
538   @BeanIgnore
539   public final ClassMeta<T> getClassMeta() {
540      return classMeta;
541   }
542
543   /**
544    * Returns the dictionary name for this bean as defined through the {@link Bean#typeName() @Bean(typeName)} annotation.
545    *
546    * @return The dictionary name for this bean, or <jk>null</jk> if it has no dictionary name defined.
547    */
548   public final String getDictionaryName() {
549      return dictionaryName;
550   }
551
552   /**
553    * Returns a mock bean property that resolves to the name <js>"_type"</js> and whose value always resolves to the
554    * dictionary name of the bean.
555    *
556    * @return The type name property.
557    */
558   public final BeanPropertyMeta getTypeProperty() {
559      return typeProperty;
560   }
561
562   /**
563    * Possible property method types.
564    */
565   static enum MethodType {
566      UNKNOWN,
567      GETTER,
568      SETTER,
569      EXTRAKEYS;
570   }
571
572   /*
573    * Temporary getter/setter method struct.
574    */
575   private static final class BeanMethod {
576      String propertyName;
577      MethodType methodType;
578      Method method;
579      ClassInfo type;
580
581      BeanMethod(String propertyName, MethodType type, Method method) {
582         this.propertyName = propertyName;
583         this.methodType = type;
584         this.method = method;
585         if (type == MethodType.SETTER)
586            this.type = ClassInfo.of(method.getParameterTypes()[0]);
587         else
588            this.type = ClassInfo.of(method.getReturnType());
589      }
590
591      /*
592       * Returns true if this method matches the class type of the specified property.
593       * Only meant to be used for setters.
594       */
595      boolean matchesPropertyType(BeanPropertyMeta.Builder b) {
596         if (b == null)
597            return false;
598
599         // Don't do further validation if this is the "*" bean property.
600         if ("*".equals(b.name))
601            return true;
602
603         // Get the bean property type from the getter/field.
604         Class<?> pt = null;
605         if (b.getter != null)
606            pt = b.getter.getReturnType();
607         else if (b.field != null)
608            pt = b.field.getType();
609
610         // Matches if only a setter is defined.
611         if (pt == null)
612            return true;
613
614         // Doesn't match if not same type or super type as getter/field.
615         if (! type.isParentOf(pt))
616            return false;
617
618         // If a setter was previously set, only use this setter if it's a closer
619         // match (e.g. prev type is a superclass of this type).
620         if (b.setter == null)
621            return true;
622
623         return type.isStrictChildOf(b.setter.getParameterTypes()[0]);
624      }
625
626      @Override /* Object */
627      public String toString() {
628         return method.toString();
629      }
630   }
631
632   /*
633    * Find all the bean methods on this class.
634    *
635    * @param c The transformed class.
636    * @param stopClass Don't look above this class in the hierarchy.
637    * @param v The minimum method visibility.
638    * @param fixedBeanProps Only include methods whose properties are in this list.
639    * @param pn Use this property namer to determine property names from the method names.
640    */
641   static final List<BeanMethod> findBeanMethods(Class<?> c, Class<?> stopClass, Visibility v, Set<String> fixedBeanProps, Set<String> filterProps, PropertyNamer pn, boolean fluentSetters) {
642      List<BeanMethod> l = new LinkedList<>();
643
644      for (ClassInfo c2 : findClasses(c, stopClass)) {
645         for (MethodInfo m : c2.getDeclaredMethods()) {
646            if (m.isStatic())
647               continue;
648            if (m.isBridge())   // This eliminates methods with covariant return types from parent classes on child classes.
649               continue;
650            if (m.getParamCount() > 2)
651               continue;
652
653            BeanIgnore bi = m.getAnnotation(BeanIgnore.class);
654            if (bi != null)
655               continue;
656
657            @SuppressWarnings("deprecation")
658            BeanProperty px = m.getAnnotation(BeanProperty.class);
659            Beanp p = m.getAnnotation(Beanp.class);
660            Name n2 = m.getAnnotation(Name.class);
661            if (! (m.isVisible(v) || px != null || p != null || n2 != null))
662               continue;
663
664            String n = m.getSimpleName();
665
666            List<ClassInfo> pt = m.getParamTypes();
667            ClassInfo rt = m.getReturnType();
668            MethodType methodType = UNKNOWN;
669            String bpName = bpName(px, p, n2);
670
671            if (! (isEmpty(bpName) || filterProps.isEmpty() || filterProps.contains(bpName)))
672               throw new BeanRuntimeException(c, "Found @Beanp(\"{0}\") but name was not found in @Bean(properties)", bpName);
673
674            if (pt.size() == 0) {
675               if ("*".equals(bpName)) {
676                  if (rt.isChildOf(Collection.class)) {
677                     methodType = EXTRAKEYS;
678                  } else if (rt.isChildOf(Map.class)) {
679                     methodType = GETTER;
680                  }
681                  n = bpName;
682               } else if (n.startsWith("get") && (! rt.is(Void.TYPE))) {
683                  methodType = GETTER;
684                  n = n.substring(3);
685               } else if (n.startsWith("is") && (rt.is(Boolean.TYPE) || rt.is(Boolean.class))) {
686                  methodType = GETTER;
687                  n = n.substring(2);
688               } else if (bpName != null) {
689                  methodType = GETTER;
690                  if (bpName.isEmpty()) {
691                     if (n.startsWith("get"))
692                        n = n.substring(3);
693                     else if (n.startsWith("is"))
694                        n = n.substring(2);
695                     bpName = n;
696                  } else {
697                     n = bpName;
698                  }
699               }
700            } else if (pt.size() == 1) {
701               if ("*".equals(bpName)) {
702                  if (pt.get(0).isChildOf(Map.class)) {
703                     methodType = SETTER;
704                     n = bpName;
705                  } else if (pt.get(0).is(String.class)) {
706                     methodType = GETTER;
707                     n = bpName;
708                  }
709               } else if (n.startsWith("set") && (rt.isParentOf(c) || rt.is(Void.TYPE))) {
710                  methodType = SETTER;
711                  n = n.substring(3);
712               } else if (bpName != null) {
713                  methodType = SETTER;
714                  if (bpName.isEmpty()) {
715                     if (n.startsWith("set"))
716                        n = n.substring(3);
717                     bpName = n;
718                  } else {
719                     n = bpName;
720                  }
721               } else if (fluentSetters && rt.isParentOf(c)) {
722                  methodType = SETTER;
723               }
724            } else if (pt.size() == 2) {
725               if ("*".equals(bpName) && pt.get(0).is(String.class)) {
726                  if (n.startsWith("set") && (rt.isParentOf(c) || rt.is(Void.TYPE))) {
727                     methodType = SETTER;
728                  } else {
729                     methodType = GETTER;
730                  }
731                  n = bpName;
732               }
733            }
734            n = pn.getPropertyName(n);
735
736            if ("*".equals(bpName) && methodType == UNKNOWN)
737               throw new BeanRuntimeException(c, "Found @Beanp(\"*\") but could not determine method type on method ''{0}''.", m.getSimpleName());
738
739            if (methodType != UNKNOWN) {
740               if (bpName != null && ! bpName.isEmpty()) {
741                  n = bpName;
742                  if (! fixedBeanProps.isEmpty())
743                     if (! fixedBeanProps.contains(n))
744                        n = null;  // Could happen if filtered via BEAN_bpi/BEAN_bpx
745               }
746               if (n != null)
747                  l.add(new BeanMethod(n, methodType, m.inner()));
748            }
749         }
750      }
751      return l;
752   }
753
754   static final Collection<Field> findBeanFields(Class<?> c, Class<?> stopClass, Visibility v, Set<String> filterProps) {
755      List<Field> l = new LinkedList<>();
756      for (ClassInfo c2 : findClasses(c, stopClass)) {
757         for (FieldInfo f : c2.getDeclaredFields()) {
758            if (f.isAny(STATIC, TRANSIENT))
759               continue;
760            if (f.hasAnnotation(BeanIgnore.class))
761               continue;
762
763            @SuppressWarnings("deprecation")
764            BeanProperty px = f.getAnnotation(BeanProperty.class);
765            Beanp p = f.getAnnotation(Beanp.class);
766            Name n = f.getAnnotation(Name.class);
767            String bpName = bpName(px, p, n);
768
769            if (! (v.isVisible(f.inner()) || px != null || p != null))
770               continue;
771
772            if (! (isEmpty(bpName) || filterProps.isEmpty() || filterProps.contains(bpName)))
773               throw new BeanRuntimeException(c, "Found @Beanp(\"{0}\") but name was not found in @Bean(properties)", bpName);
774
775            l.add(f.inner());
776         }
777      }
778      return l;
779   }
780
781   static final Field findInnerBeanField(Class<?> c, Class<?> stopClass, String name) {
782      for (ClassInfo c2 : findClasses(c, stopClass)) {
783         for (FieldInfo f : c2.getDeclaredFields()) {
784            if (f.isAny(STATIC, TRANSIENT))
785               continue;
786            if (f.hasAnnotation(BeanIgnore.class))
787               continue;
788            if (f.hasName(name))
789               return f.inner();
790         }
791      }
792      return null;
793   }
794
795   private static List<ClassInfo> findClasses(Class<?> c, Class<?> stopClass) {
796      LinkedList<ClassInfo> l = new LinkedList<>();
797      findClasses(c, l, stopClass);
798      return l;
799   }
800
801   private static void findClasses(Class<?> c, LinkedList<ClassInfo> l, Class<?> stopClass) {
802      while (c != null && stopClass != c) {
803         l.addFirst(ClassInfo.of(c));
804         for (Class<?> ci : c.getInterfaces())
805            findClasses(ci, l, stopClass);
806         c = c.getSuperclass();
807      }
808   }
809
810   /**
811    * Returns the metadata on all properties associated with this bean.
812    *
813    * @return Metadata on all properties associated with this bean.
814    */
815   public Collection<BeanPropertyMeta> getPropertyMetas() {
816      return this.properties.values();
817   }
818
819   /**
820    * Returns the metadata on the specified list of properties.
821    *
822    * @param pNames The list of properties to retrieve.  If <jk>null</jk>, returns all properties.
823    * @return The metadata on the specified list of properties.
824    */
825   public Collection<BeanPropertyMeta> getPropertyMetas(final String...pNames) {
826      if (pNames == null)
827         return getPropertyMetas();
828      List<BeanPropertyMeta> l = new ArrayList<>(pNames.length);
829      for (int i = 0; i < pNames.length; i++)
830         l.add(getPropertyMeta(pNames[i]));
831      return l;
832   }
833
834   /**
835    * Returns the language-specified extended metadata on this bean class.
836    *
837    * @param metaDataClass The name of the metadata class to create.
838    * @return Extended metadata on this bean class.  Never <jk>null</jk>.
839    */
840   public <M extends BeanMetaExtended> M getExtendedMeta(Class<M> metaDataClass) {
841      return extMeta.get(metaDataClass, this);
842   }
843
844   /**
845    * Returns metadata about the specified property.
846    *
847    * @param name The name of the property on this bean.
848    * @return The metadata about the property, or <jk>null</jk> if no such property exists on this bean.
849    */
850   public BeanPropertyMeta getPropertyMeta(String name) {
851      BeanPropertyMeta bpm = properties.get(name);
852      if (bpm == null)
853         bpm = dynaProperty;
854      return bpm;
855   }
856
857   /**
858    * Creates a new instance of this bean.
859    *
860    * @param outer The outer object if bean class is a non-static inner member class.
861    * @return A new instance of this bean if possible, or <jk>null</jk> if not.
862    * @throws ExecutableException Exception occurred on invoked constructor/method/field.
863    */
864   @SuppressWarnings("unchecked")
865   protected T newBean(Object outer) throws ExecutableException {
866      if (classMeta.isMemberClass()) {
867         if (constructor != null)
868            return constructor.<T>invoke(outer);
869      } else {
870         if (constructor != null)
871            return constructor.<T>invoke((Object[])null);
872         InvocationHandler h = classMeta.getProxyInvocationHandler();
873         if (h != null) {
874            ClassLoader cl = classMeta.innerClass.getClassLoader();
875            return (T)Proxy.newProxyInstance(cl, new Class[] { classMeta.innerClass, java.io.Serializable.class }, h);
876         }
877      }
878      return null;
879   }
880
881   /**
882    * Recursively determines the classes represented by parameterized types in the class hierarchy of the specified
883    * type, and puts the results in the specified map.
884    *
885    * <p>
886    * For example, given the following classes...
887    * <p class='bcode w800'>
888    *    public static class BeanA&lt;T&gt; {
889    *       public T x;
890    *    }
891    *    public static class BeanB extends BeanA&lt;Integer>} {...}
892    * </p>
893    * <p>
894    *    ...calling this method on {@code BeanB.class} will load the following data into {@code m} indicating
895    *    that the {@code T} parameter on the BeanA class is implemented with an {@code Integer}:
896    * <p class='bcode w800'>
897    *    {BeanA.class:[Integer.class]}
898    * </p>
899    *
900    * <p>
901    * TODO:  This code doesn't currently properly handle the following situation:
902    * <p class='bcode w800'>
903    *    public static class BeanB&lt;T extends Number&gt; extends BeanA&lt;T&gt;;
904    *    public static class BeanC extends BeanB&lt;Integer&gt;;
905    * </p>
906    *
907    * <p>
908    * When called on {@code BeanC}, the variable will be detected as a {@code Number}, not an {@code Integer}.
909    * If anyone can figure out a better way of doing this, please do so!
910    *
911    * @param t The type we're recursing.
912    * @param m Where the results are loaded.
913    */
914   static final void findTypeVarImpls(Type t, Map<Class<?>,Class<?>[]> m) {
915      if (t instanceof Class) {
916         Class<?> c = (Class<?>)t;
917         findTypeVarImpls(c.getGenericSuperclass(), m);
918         for (Type ci : c.getGenericInterfaces())
919            findTypeVarImpls(ci, m);
920      } else if (t instanceof ParameterizedType) {
921         ParameterizedType pt = (ParameterizedType)t;
922         Type rt = pt.getRawType();
923         if (rt instanceof Class) {
924            Type[] gImpls = pt.getActualTypeArguments();
925            Class<?>[] gTypes = new Class[gImpls.length];
926            for (int i = 0; i < gImpls.length; i++) {
927               Type gt = gImpls[i];
928               if (gt instanceof Class)
929                  gTypes[i] = (Class<?>)gt;
930               else if (gt instanceof TypeVariable) {
931                  TypeVariable<?> tv = (TypeVariable<?>)gt;
932                  for (Type upperBound : tv.getBounds())
933                     if (upperBound instanceof Class)
934                        gTypes[i] = (Class<?>)upperBound;
935               }
936            }
937            m.put((Class<?>)rt, gTypes);
938            findTypeVarImpls(pt.getRawType(), m);
939         }
940      }
941   }
942
943   @SuppressWarnings("deprecation")
944   static final String bpName(BeanProperty px, Beanp p, Name n) {
945      if (px == null && p == null && n == null)
946         return null;
947      if (n != null)
948         return n.value();
949      if (p != null) {
950         if (! p.name().isEmpty())
951            return p.name();
952         return p.value();
953      }
954      if (px != null) {
955         if (! px.name().isEmpty())
956            return px.name();
957         return px.value();
958      }
959      return null;
960   }
961
962   @Override /* Object */
963   public String toString() {
964      StringBuilder sb = new StringBuilder(c.getName());
965      sb.append(" {\n");
966      for (BeanPropertyMeta pm : this.properties.values())
967         sb.append('\t').append(pm.toString()).append(",\n");
968      sb.append('}');
969      return sb.toString();
970   }
971}