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.Visibility.*;
016import static org.apache.juneau.internal.ClassUtils.*;
017import static org.apache.juneau.internal.CollectionUtils.*;
018import static org.apache.juneau.internal.ReflectionUtils.*;
019import static org.apache.juneau.internal.StringUtils.*;
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
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.extMeta = b.extMeta;
130      this.beanRegistry = b.beanRegistry;
131      this.typePropertyName = b.typePropertyName;
132      this.typeProperty = BeanPropertyMeta.builder(this, typePropertyName).canRead().canWrite().rawMetaType(ctx.string()).beanRegistry(beanRegistry).build();
133      this.sortProperties = b.sortProperties;
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      Constructor<T> constructor;
148      String[] constructorArgs = new String[0];
149      MetadataMap extMeta = new MetadataMap();
150      PropertyNamer propertyNamer;
151      BeanRegistry beanRegistry;
152      String dictionaryName, typePropertyName;
153      boolean sortProperties;
154
155      Builder(ClassMeta<T> classMeta, BeanContext ctx, BeanFilter beanFilter, String[] pNames) {
156         this.classMeta = classMeta;
157         this.ctx = ctx;
158         this.beanFilter = beanFilter;
159         this.pNames = pNames;
160      }
161
162      @SuppressWarnings("unchecked")
163      String init(BeanMeta<T> beanMeta) {
164         Class<?> c = classMeta.getInnerClass();
165
166         try {
167            Visibility
168               conVis = ctx.beanConstructorVisibility,
169               cVis = ctx.beanClassVisibility,
170               mVis = ctx.beanMethodVisibility,
171               fVis = ctx.beanFieldVisibility;
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.innerClass.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 : findAnnotationsParentFirst(Bean.class, classMeta.innerClass))
184               if (! b.typePropertyName().isEmpty())
185                  typePropertyName = b.typePropertyName();
186            if (typePropertyName == null)
187               typePropertyName = ctx.getBeanTypePropertyName();
188
189            // If @Bean.interfaceClass is specified on the parent class, then we want
190            // to use the properties defined on that class, not the subclass.
191            Class<?> c2 = (beanFilter != null && beanFilter.getInterfaceClass() != null ? beanFilter.getInterfaceClass() : c);
192
193            Class<?> stopClass = (beanFilter != null ? beanFilter.getStopClass() : Object.class);
194            if (stopClass == null)
195               stopClass = Object.class;
196
197            Map<String,BeanPropertyMeta.Builder> normalProps = new LinkedHashMap<>();
198
199            /// See if this class matches one the patterns in the exclude-class list.
200            if (ctx.isNotABean(c))
201               return "Class matches exclude-class list";
202
203            if (! (cVis.isVisible(c.getModifiers()) || c.isAnonymousClass()))
204               return "Class is not public";
205
206            if (c.isAnnotationPresent(BeanIgnore.class))
207               return "Class is annotated with @BeanIgnore";
208
209            // Make sure it's serializable.
210            if (beanFilter == null && ctx.beansRequireSerializable && ! isParentClass(Serializable.class, c))
211               return "Class is not serializable";
212
213            // Look for @BeanConstructor constructor.
214            for (Constructor<?> x : c.getConstructors()) {
215               if (x.isAnnotationPresent(BeanConstructor.class)) {
216                  if (constructor != null)
217                     throw new BeanRuntimeException(c, "Multiple instances of '@BeanConstructor' found.");
218                  constructor = (Constructor<T>)x;
219                  constructorArgs = split(x.getAnnotation(BeanConstructor.class).properties());
220                  if (constructorArgs.length != x.getParameterTypes().length)
221                     throw new BeanRuntimeException(c, "Number of properties defined in '@BeanConstructor' annotation does not match number of parameters in constructor.");
222                  if (! setAccessible(constructor))
223                     throw new BeanRuntimeException(c, "Could not set accessibility to true on method with @BeanConstructor annotation.  Method=''{0}''", constructor.getName());
224               }
225            }
226
227            // If this is an interface, look for impl classes defined in the context.
228            if (constructor == null)
229               constructor = (Constructor<T>)ctx.getImplClassConstructor(c, conVis);
230
231            if (constructor == null)
232               constructor = (Constructor<T>)findNoArgConstructor(c, conVis);
233
234            if (constructor == null && beanFilter == null && ctx.beansRequireDefaultConstructor)
235               return "Class does not have the required no-arg constructor";
236
237            if (! setAccessible(constructor))
238               throw new BeanRuntimeException(c, "Could not set accessibility to true on no-arg constructor");
239
240            // Explicitly defined property names in @Bean annotation.
241            Set<String> fixedBeanProps = new LinkedHashSet<>();
242            String[] includeProperties = ctx.getIncludeProperties(c);
243            String[] excludeProperties = ctx.getExcludeProperties(c);
244
245            if (beanFilter != null) {
246
247               // Get the 'properties' attribute if specified.
248               if (beanFilter.getProperties() != null && includeProperties == null)
249                  for (String p : beanFilter.getProperties())
250                     fixedBeanProps.add(p);
251
252               if (beanFilter.getPropertyNamer() != null)
253                  propertyNamer = beanFilter.getPropertyNamer();
254            }
255
256            if (includeProperties != null)
257               fixedBeanProps.addAll(Arrays.asList(includeProperties));
258
259            if (propertyNamer == null)
260               propertyNamer = ctx.propertyNamer;
261
262            // First populate the properties with those specified in the bean annotation to
263            // ensure that ordering first.
264            for (String name : fixedBeanProps)
265               normalProps.put(name, BeanPropertyMeta.builder(beanMeta, name));
266
267            if (ctx.useJavaBeanIntrospector) {
268               BeanInfo bi = null;
269               if (! c2.isInterface())
270                  bi = Introspector.getBeanInfo(c2, stopClass);
271               else
272                  bi = Introspector.getBeanInfo(c2, null);
273               if (bi != null) {
274                  for (PropertyDescriptor pd : bi.getPropertyDescriptors()) {
275                     String name = pd.getName();
276                     if (! normalProps.containsKey(name))
277                        normalProps.put(name, BeanPropertyMeta.builder(beanMeta, name));
278                     normalProps.get(name).setGetter(pd.getReadMethod()).setSetter(pd.getWriteMethod());
279                  }
280               }
281
282            } else /* Use 'better' introspection */ {
283
284               for (Field f : findBeanFields(c2, stopClass, fVis)) {
285                  String name = findPropertyName(f, fixedBeanProps);
286                  if (name != null) {
287                     if (! normalProps.containsKey(name))
288                        normalProps.put(name, BeanPropertyMeta.builder(beanMeta, name));
289                     normalProps.get(name).setField(f);
290                  }
291               }
292
293               List<BeanMethod> bms = findBeanMethods(c2, stopClass, mVis, fixedBeanProps, propertyNamer);
294
295               // Iterate through all the getters.
296               for (BeanMethod bm : bms) {
297                  String pn = bm.propertyName;
298                  Method m = bm.method;
299                  if (! normalProps.containsKey(pn))
300                     normalProps.put(pn, new BeanPropertyMeta.Builder(beanMeta, pn));
301                  BeanPropertyMeta.Builder bpm = normalProps.get(pn);
302                  if (! bm.isSetter)
303                     bpm.setGetter(m);
304               }
305
306               // Now iterate through all the setters.
307               for (BeanMethod bm : bms) {
308                  if (bm.isSetter) {
309                     BeanPropertyMeta.Builder bpm = normalProps.get(bm.propertyName);
310                     if (bm.matchesPropertyType(bpm))
311                        bpm.setSetter(bm.method);
312                  }
313               }
314            }
315
316            typeVarImpls = new HashMap<>();
317            findTypeVarImpls(c, typeVarImpls);
318            if (typeVarImpls.isEmpty())
319               typeVarImpls = null;
320
321            // Eliminate invalid properties, and set the contents of getterProps and setterProps.
322            for (Iterator<BeanPropertyMeta.Builder> i = normalProps.values().iterator(); i.hasNext();) {
323               BeanPropertyMeta.Builder p = i.next();
324               try {
325                  if (p.validate(ctx, beanRegistry, typeVarImpls)) {
326
327                     if (p.getter != null)
328                        getterProps.put(p.getter, p.name);
329
330                     if (p.setter != null)
331                        setterProps.put(p.setter, p.name);
332
333                  } else {
334                     i.remove();
335                  }
336               } catch (ClassNotFoundException e) {
337                  throw new BeanRuntimeException(c, e.getLocalizedMessage());
338               }
339            }
340
341            // Check for missing properties.
342            for (String fp : fixedBeanProps)
343               if (! normalProps.containsKey(fp))
344                  throw new BeanRuntimeException(c, "The property ''{0}'' was defined on the @Bean(properties=X) annotation but was not found on the class definition.", fp);
345
346            // Mark constructor arg properties.
347            for (String fp : constructorArgs) {
348               BeanPropertyMeta.Builder m = normalProps.get(fp);
349               if (m == null)
350                  throw new BeanRuntimeException(c, "The property ''{0}'' was defined on the @BeanConstructor(properties=X) annotation but was not found on the class definition.", fp);
351               m.setAsConstructorArg();
352            }
353
354            // Make sure at least one property was found.
355            if (beanFilter == null && ctx.beansRequireSomeProperties && normalProps.size() == 0)
356               return "No properties detected on bean class";
357
358            sortProperties = (ctx.sortProperties || (beanFilter != null && beanFilter.isSortProperties())) && fixedBeanProps.isEmpty();
359
360            properties = sortProperties ? new TreeMap<String,BeanPropertyMeta>() : new LinkedHashMap<String,BeanPropertyMeta>();
361
362            if (beanFilter != null && beanFilter.getTypeName() != null)
363               dictionaryName = beanFilter.getTypeName();
364            if (dictionaryName == null)
365               dictionaryName = findDictionaryName(this.classMeta);
366
367            for (Map.Entry<String,BeanPropertyMeta.Builder> e : normalProps.entrySet()) {
368               BeanPropertyMeta pMeta = e.getValue().build();
369               if (pMeta.isDyna())
370                  dynaProperty = pMeta;
371               properties.put(e.getKey(), pMeta);
372            }
373
374            // If a beanFilter is defined, look for inclusion and exclusion lists.
375            if (beanFilter != null) {
376
377               // Eliminated excluded properties if BeanFilter.excludeKeys is specified.
378               String[] includeKeys = beanFilter.getProperties();
379               String[] excludeKeys = beanFilter.getExcludeProperties();
380               if (excludeKeys != null && excludeProperties == null) {
381                  for (String k : excludeKeys)
382                     properties.remove(k);
383
384               // Only include specified properties if BeanFilter.includeKeys is specified.
385               // Note that the order must match includeKeys.
386               } else if (includeKeys != null) {
387                  Map<String,BeanPropertyMeta> properties2 = new LinkedHashMap<>();
388                  for (String k : includeKeys) {
389                     if (properties.containsKey(k))
390                        properties2.put(k, properties.get(k));
391                  }
392                  properties = properties2;
393               }
394            }
395
396            if (excludeProperties != null)
397               for (String ep : excludeProperties)
398                  properties.remove(ep);
399
400            if (pNames != null) {
401               Map<String,BeanPropertyMeta> properties2 = new LinkedHashMap<>();
402               for (String k : pNames) {
403                  if (properties.containsKey(k))
404                     properties2.put(k, properties.get(k));
405               }
406               properties = properties2;
407            }
408
409         } catch (BeanRuntimeException e) {
410            throw e;
411         } catch (Exception e) {
412            return "Exception:  " + getStackTrace(e);
413         }
414
415         return null;
416      }
417
418      private String findDictionaryName(ClassMeta<?> cm) {
419         BeanRegistry br = cm.getBeanRegistry();
420         if (br != null) {
421            String s = br.getTypeName(this.classMeta);
422            if (s != null)
423               return s;
424         }
425         Class<?> pcm = cm.innerClass.getSuperclass();
426         if (pcm != null) {
427            String s = findDictionaryName(ctx.getClassMeta(pcm));
428            if (s != null)
429               return s;
430         }
431         for (Class<?> icm : cm.innerClass.getInterfaces()) {
432            String s = findDictionaryName(ctx.getClassMeta(icm));
433            if (s != null)
434               return s;
435         }
436         return null;
437      }
438
439      /*
440       * Returns the property name of the specified field if it's a valid property.
441       * Returns null if the field isn't a valid property.
442       */
443      private String findPropertyName(Field f, Set<String> fixedBeanProps) {
444         BeanProperty bp = f.getAnnotation(BeanProperty.class);
445         String name = bpName(bp);
446         if (name != null && ! name.isEmpty()) {
447            if (fixedBeanProps.isEmpty() || fixedBeanProps.contains(name))
448               return name;
449            return null;  // Could happen if filtered via BEAN_includeProperties/BEAN_excludeProperties.
450         }
451         name = propertyNamer.getPropertyName(f.getName());
452         if (fixedBeanProps.isEmpty() || fixedBeanProps.contains(name))
453            return name;
454         return null;
455      }
456   }
457
458   /**
459    * Returns the {@link ClassMeta} of this bean.
460    * 
461    * @return The {@link ClassMeta} of this bean.
462    */
463   @BeanIgnore
464   public final ClassMeta<T> getClassMeta() {
465      return classMeta;
466   }
467
468   /**
469    * Returns the dictionary name for this bean as defined through the {@link Bean#typeName() @Bean.typeName()} annotation.
470    * 
471    * @return The dictionary name for this bean, or <jk>null</jk> if it has no dictionary name defined.
472    */
473   public final String getDictionaryName() {
474      return dictionaryName;
475   }
476
477   /**
478    * Returns a mock bean property that resolves to the name <js>"_type"</js> and whose value always resolves to the
479    * dictionary name of the bean.
480    * 
481    * @return The type name property.
482    */
483   public final BeanPropertyMeta getTypeProperty() {
484      return typeProperty;
485   }
486
487   /*
488    * Temporary getter/setter method struct.
489    */
490   private static final class BeanMethod {
491      String propertyName;
492      boolean isSetter;
493      Method method;
494      Class<?> type;
495
496      BeanMethod(String propertyName, boolean isSetter, Method method) {
497         this.propertyName = propertyName;
498         this.isSetter = isSetter;
499         this.method = method;
500         if (isSetter)
501            this.type = method.getParameterTypes()[0];
502         else
503            this.type = method.getReturnType();
504      }
505
506      /*
507       * Returns true if this method matches the class type of the specified property.
508       * Only meant to be used for setters.
509       */
510      boolean matchesPropertyType(BeanPropertyMeta.Builder b) {
511         if (b == null)
512            return false;
513
514         // Don't do further validation if this is the "*" bean property.
515         if ("*".equals(b.name))
516            return true;
517
518         // Get the bean property type from the getter/field.
519         Class<?> pt = null;
520         if (b.getter != null)
521            pt = b.getter.getReturnType();
522         else if (b.field != null)
523            pt = b.field.getType();
524
525         // Matches if only a setter is defined.
526         if (pt == null)
527            return true;
528
529         // Doesn't match if not same type or super type as getter/field.
530         if (! isParentClass(type, pt))
531            return false;
532
533         // If a setter was previously set, only use this setter if it's a closer
534         // match (e.g. prev type is a superclass of this type).
535         if (b.setter == null)
536            return true;
537
538         Class<?> prevType = b.setter.getParameterTypes()[0];
539         return isParentClass(prevType, type, true);
540      }
541
542      @Override /* Object */
543      public String toString() {
544         return method.toString();
545      }
546   }
547
548   /*
549    * Find all the bean methods on this class.
550    * 
551    * @param c The transformed class.
552    * @param stopClass Don't look above this class in the hierarchy.
553    * @param v The minimum method visibility.
554    * @param fixedBeanProps Only include methods whose properties are in this list.
555    * @param pn Use this property namer to determine property names from the method names.
556    */
557   static final List<BeanMethod> findBeanMethods(Class<?> c, Class<?> stopClass, Visibility v, Set<String> fixedBeanProps, PropertyNamer pn) {
558      List<BeanMethod> l = new LinkedList<>();
559
560      for (Class<?> c2 : findClasses(c, stopClass)) {
561         for (Method m : c2.getDeclaredMethods()) {
562            int mod = m.getModifiers();
563            if (Modifier.isStatic(mod))
564               continue;
565            if (m.isBridge())   // This eliminates methods with covariant return types from parent classes on child classes.
566               continue;
567
568            BeanIgnore bi = getMethodAnnotation(BeanIgnore.class, c, m);
569            if (bi != null)
570               continue;
571
572            BeanProperty bp = getMethodAnnotation(BeanProperty.class, c, m);
573            if (! (v.isVisible(m) || bp != null))
574               continue;
575
576            String n = m.getName();
577            Class<?>[] pt = m.getParameterTypes();
578            Class<?> rt = m.getReturnType();
579            boolean isGetter = false, isSetter = false;
580            String bpName = bpName(bp);
581            if (pt.length == 0) {
582               if (n.startsWith("get") && (! rt.equals(Void.TYPE))) {
583                  isGetter = true;
584                  n = n.substring(3);
585               } else if (n.startsWith("is") && (rt.equals(Boolean.TYPE) || rt.equals(Boolean.class))) {
586                  isGetter = true;
587                  n = n.substring(2);
588               } else if (bpName != null) {
589                  isGetter = true;
590                  if (bpName.isEmpty()) {
591                     if (n.startsWith("get"))
592                        n = n.substring(3);
593                     else if (n.startsWith("is"))
594                        n = n.substring(2);
595                     bpName = n;
596                  } else {
597                     n = bpName;
598                  }
599               }
600            } else if (pt.length == 1) {
601               if (n.startsWith("set") && (isParentClass(rt, c) || rt.equals(Void.TYPE))) {
602                  isSetter = true;
603                  n = n.substring(3);
604               } else if (bpName != null) {
605                  isSetter = true;
606                  if (bpName.isEmpty()) {
607                     if (n.startsWith("set"))
608                        n = n.substring(3);
609                     bpName = n;
610                  } else {
611                     n = bpName;
612                  }
613               }
614            } else if (pt.length == 2) {
615               if ("*".equals(bpName)) {
616                  isSetter = true;
617                  n = bpName;
618               }
619            }
620            n = pn.getPropertyName(n);
621            if (isGetter || isSetter) {
622               if (bpName != null && ! bpName.isEmpty()) {
623                  n = bpName;
624                  if (! fixedBeanProps.isEmpty())
625                     if (! fixedBeanProps.contains(n))
626                        n = null;  // Could happen if filtered via BEAN_includeProperties/BEAN_excludeProperties
627               }
628               if (n != null)
629                  l.add(new BeanMethod(n, isSetter, m));
630            }
631         }
632      }
633      return l;
634   }
635
636   static final Collection<Field> findBeanFields(Class<?> c, Class<?> stopClass, Visibility v) {
637      List<Field> l = new LinkedList<>();
638      for (Class<?> c2 : findClasses(c, stopClass)) {
639         for (Field f : c2.getDeclaredFields()) {
640            int m = f.getModifiers();
641            if (Modifier.isStatic(m) || Modifier.isTransient(m))
642               continue;
643            if (f.isAnnotationPresent(BeanIgnore.class))
644               continue;
645            if (! (v.isVisible(f) || f.isAnnotationPresent(BeanProperty.class)))
646               continue;
647            l.add(f);
648         }
649      }
650      return l;
651   }
652
653   private static List<Class<?>> findClasses(Class<?> c, Class<?> stopClass) {
654      LinkedList<Class<?>> l = new LinkedList<>();
655      findClasses(c, l, stopClass);
656      return l;
657   }
658
659   private static void findClasses(Class<?> c, LinkedList<Class<?>> l, Class<?> stopClass) {
660      while (c != null && stopClass != c) {
661         l.addFirst(c);
662         for (Class<?> ci : c.getInterfaces())
663            findClasses(ci, l, stopClass);
664         c = c.getSuperclass();
665      }
666   }
667
668   /**
669    * Returns the metadata on all properties associated with this bean.
670    * 
671    * @return Metadata on all properties associated with this bean.
672    */
673   public Collection<BeanPropertyMeta> getPropertyMetas() {
674      return this.properties.values();
675   }
676
677   /**
678    * Returns the metadata on the specified list of properties.
679    * 
680    * @param pNames The list of properties to retrieve.  If <jk>null</jk>, returns all properties.
681    * @return The metadata on the specified list of properties.
682    */
683   public Collection<BeanPropertyMeta> getPropertyMetas(final String...pNames) {
684      if (pNames == null)
685         return getPropertyMetas();
686      List<BeanPropertyMeta> l = new ArrayList<>(pNames.length);
687      for (int i = 0; i < pNames.length; i++)
688         l.add(getPropertyMeta(pNames[i]));
689      return l;
690   }
691
692   /**
693    * Returns the language-specified extended metadata on this bean class.
694    * 
695    * @param metaDataClass The name of the metadata class to create.
696    * @return Extended metadata on this bean class.  Never <jk>null</jk>.
697    */
698   public <M extends BeanMetaExtended> M getExtendedMeta(Class<M> metaDataClass) {
699      return extMeta.get(metaDataClass, this);
700   }
701
702   /**
703    * Returns metadata about the specified property.
704    * 
705    * @param name The name of the property on this bean.
706    * @return The metadata about the property, or <jk>null</jk> if no such property exists on this bean.
707    */
708   public BeanPropertyMeta getPropertyMeta(String name) {
709      BeanPropertyMeta bpm = properties.get(name);
710      if (bpm == null)
711         bpm = dynaProperty;
712      return bpm;
713   }
714
715   /**
716    * Creates a new instance of this bean.
717    * 
718    * @param outer The outer object if bean class is a non-static inner member class.
719    * @return A new instance of this bean if possible, or <jk>null</jk> if not.
720    * @throws IllegalArgumentException Thrown by constructor.
721    * @throws InstantiationException Thrown by constructor.
722    * @throws IllegalAccessException Thrown by constructor.
723    * @throws InvocationTargetException Thrown by constructor.
724    */
725   @SuppressWarnings("unchecked")
726   protected T newBean(Object outer) throws IllegalArgumentException, InstantiationException, IllegalAccessException, InvocationTargetException {
727      if (classMeta.isMemberClass()) {
728         if (constructor != null)
729            return constructor.newInstance(outer);
730      } else {
731         if (constructor != null)
732            return constructor.newInstance((Object[])null);
733         InvocationHandler h = classMeta.getProxyInvocationHandler();
734         if (h != null) {
735            ClassLoader cl = classMeta.innerClass.getClassLoader();
736            return (T)Proxy.newProxyInstance(cl, new Class[] { classMeta.innerClass, java.io.Serializable.class }, h);
737         }
738      }
739      return null;
740   }
741
742   /**
743    * Recursively determines the classes represented by parameterized types in the class hierarchy of the specified
744    * type, and puts the results in the specified map.
745    * 
746    * <p>
747    * For example, given the following classes...
748    * <p class='bcode'>
749    *    public static class BeanA&lt;T&gt; {
750    *       public T x;
751    *    }
752    *    public static class BeanB extends BeanA&lt;Integer>} {...}
753    * </p>
754    * <p>
755    *    ...calling this method on {@code BeanB.class} will load the following data into {@code m} indicating
756    *    that the {@code T} parameter on the BeanA class is implemented with an {@code Integer}:
757    * <p class='bcode'>
758    *    {BeanA.class:[Integer.class]}
759    * </p>
760    * 
761    * <p>
762    * TODO:  This code doesn't currently properly handle the following situation:
763    * <p class='bcode'>
764    *    public static class BeanB&lt;T extends Number&gt; extends BeanA&lt;T&gt;;
765    *    public static class BeanC extends BeanB&lt;Integer&gt;;
766    * </p>
767    * 
768    * <p>
769    * When called on {@code BeanC}, the variable will be detected as a {@code Number}, not an {@code Integer}.
770    * If anyone can figure out a better way of doing this, please do so!
771    * 
772    * @param t The type we're recursing.
773    * @param m Where the results are loaded.
774    */
775   static final void findTypeVarImpls(Type t, Map<Class<?>,Class<?>[]> m) {
776      if (t instanceof Class) {
777         Class<?> c = (Class<?>)t;
778         findTypeVarImpls(c.getGenericSuperclass(), m);
779         for (Type ci : c.getGenericInterfaces())
780            findTypeVarImpls(ci, m);
781      } else if (t instanceof ParameterizedType) {
782         ParameterizedType pt = (ParameterizedType)t;
783         Type rt = pt.getRawType();
784         if (rt instanceof Class) {
785            Type[] gImpls = pt.getActualTypeArguments();
786            Class<?>[] gTypes = new Class[gImpls.length];
787            for (int i = 0; i < gImpls.length; i++) {
788               Type gt = gImpls[i];
789               if (gt instanceof Class)
790                  gTypes[i] = (Class<?>)gt;
791               else if (gt instanceof TypeVariable) {
792                  TypeVariable<?> tv = (TypeVariable<?>)gt;
793                  for (Type upperBound : tv.getBounds())
794                     if (upperBound instanceof Class)
795                        gTypes[i] = (Class<?>)upperBound;
796               }
797            }
798            m.put((Class<?>)rt, gTypes);
799            findTypeVarImpls(pt.getRawType(), m);
800         }
801      }
802   }
803
804   static final String bpName(BeanProperty bp) {
805      if (bp == null)
806         return null;
807      if (! bp.name().isEmpty())
808         return bp.name();
809      return bp.value();
810   }
811
812   @Override /* Object */
813   public String toString() {
814      StringBuilder sb = new StringBuilder(c.getName());
815      sb.append(" {\n");
816      for (BeanPropertyMeta pm : this.properties.values())
817         sb.append('\t').append(pm.toString()).append(",\n");
818      sb.append('}');
819      return sb.toString();
820   }
821}