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