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