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