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