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.ArrayUtils.*;
016import static org.apache.juneau.internal.ClassUtils.*;
017import static org.apache.juneau.internal.CollectionUtils.*;
018import static org.apache.juneau.internal.StringUtils.*;
019
020import java.lang.annotation.*;
021import java.lang.reflect.*;
022import java.net.*;
023import java.net.URI;
024import java.util.*;
025
026import org.apache.juneau.annotation.*;
027import org.apache.juneau.internal.*;
028import org.apache.juneau.parser.*;
029import org.apache.juneau.serializer.*;
030import org.apache.juneau.transform.*;
031import org.apache.juneau.transforms.*;
032import org.apache.juneau.utils.*;
033
034/**
035 * Contains metadata about a bean property.
036 *
037 * <p>
038 * Contains information such as type of property (e.g. field/getter/setter), class type of property value, and whether
039 * any transforms are associated with this property.
040 *
041 * <p>
042 * Developers will typically not need access to this class.  The information provided by it is already exposed through
043 * several methods on the {@link BeanMap} API.
044 */
045@SuppressWarnings({ "rawtypes", "unchecked" })
046public final class BeanPropertyMeta {
047
048   final BeanMeta<?> beanMeta;                               // The bean that this property belongs to.
049   private final BeanContext beanContext;                    // The context that created this meta.
050
051   private final String name;                                // The name of the property.
052   private final Field field;                                // The bean property field (if it has one).
053   private final Field innerField;                                // The bean property field (if it has one).
054   private final Method getter, setter, extraKeys;           // The bean property getter and setter.
055   private final boolean isUri;                              // True if this is a URL/URI or annotated with @URI.
056   private final boolean isDyna, isDynaGetterMap;            // This is a dyna property (i.e. name="*")
057
058   private final ClassMeta<?>
059      rawTypeMeta,                                           // The real class type of the bean property.
060      typeMeta;                                              // The transformed class type of the bean property.
061
062   private final String[] properties;                        // The value of the @BeanProperty(properties) annotation.
063   private final PojoSwap swap;                              // PojoSwap defined only via @BeanProperty annotation.
064
065   private final MetadataMap extMeta;                        // Extended metadata
066   private final BeanRegistry beanRegistry;
067
068   private final Object overrideValue;                       // The bean property value (if it's an overridden delegate).
069   private final BeanPropertyMeta delegateFor;               // The bean property that this meta is a delegate for.
070   private final boolean canRead, canWrite;
071
072   /**
073    * Creates a builder for {@link #BeanPropertyMeta} objects.
074    *
075    * @param beanMeta The metadata on the bean
076    * @param name The bean property name.
077    * @return A new builder.
078    */
079   public static Builder builder(BeanMeta<?> beanMeta, String name) {
080      return new Builder(beanMeta, name);
081   }
082
083   /**
084    * BeanPropertyMeta builder class.
085    */
086   public static final class Builder {
087      BeanMeta<?> beanMeta;
088      BeanContext beanContext;
089      String name;
090      Field field, innerField;
091      Method getter, setter, extraKeys;
092      boolean isConstructorArg, isUri, isDyna, isDynaGetterMap;
093      ClassMeta<?> rawTypeMeta, typeMeta;
094      String[] properties;
095      PojoSwap swap;
096      BeanRegistry beanRegistry;
097      Object overrideValue;
098      BeanPropertyMeta delegateFor;
099      MetadataMap extMeta = new MetadataMap();
100      boolean canRead, canWrite;
101
102      Builder(BeanMeta<?> beanMeta, String name) {
103         this.beanMeta = beanMeta;
104         this.beanContext = beanMeta.ctx;
105         this.name = name;
106      }
107
108      /**
109       * Sets the raw metadata type for this bean property.
110       *
111       * @param rawMetaType The raw metadata type for this bean property.
112       * @return This object (for method chaining().
113       */
114      public Builder rawMetaType(ClassMeta<?> rawMetaType) {
115         this.rawTypeMeta = rawMetaType;
116         this.typeMeta = rawTypeMeta;
117         return this;
118      }
119
120      /**
121       * Sets the bean registry to use with this bean property.
122       *
123       * @param beanRegistry The bean registry to use with this bean property.
124       * @return This object (for method chaining().
125       */
126      public Builder beanRegistry(BeanRegistry beanRegistry) {
127         this.beanRegistry = beanRegistry;
128         return this;
129      }
130
131      /**
132       * Sets the overridden value of this bean property.
133       *
134       * @param overrideValue The overridden value of this bean property.
135       * @return This object (for method chaining().
136       */
137      public Builder overrideValue(Object overrideValue) {
138         this.overrideValue = overrideValue;
139         return this;
140      }
141
142      /**
143       * Sets the original bean property that this one is overriding.
144       *
145       * @param delegateFor The original bean property that this one is overriding.
146       * @return This object (for method chaining().
147       */
148      public Builder delegateFor(BeanPropertyMeta delegateFor) {
149         this.delegateFor = delegateFor;
150         return this;
151      }
152
153      Builder canRead() {
154         this.canRead = true;
155         return this;
156      }
157
158      Builder canWrite() {
159         this.canWrite = true;
160         return this;
161      }
162
163      boolean validate(BeanContext f, BeanRegistry parentBeanRegistry, Map<Class<?>,Class<?>[]> typeVarImpls) throws Exception {
164
165         List<Class<?>> bdClasses = new ArrayList<>();
166
167         if (field == null && getter == null && setter == null)
168            return false;
169
170         if (field == null && setter == null && f.isBeansRequireSettersForGetters() && ! isConstructorArg)
171            return false;
172
173         canRead |= (field != null || getter != null);
174         canWrite |= (field != null || setter != null);
175
176         if (innerField != null) {
177            BeanProperty p = innerField.getAnnotation(BeanProperty.class);
178            if (field != null || p != null) {
179               // Only use field type if it's a bean property or has @BeanProperty annotation.
180               // Otherwise, we want to infer the type from the getter or setter.
181               rawTypeMeta = f.resolveClassMeta(p, innerField.getGenericType(), typeVarImpls);
182               isUri |= (rawTypeMeta.isUri() || innerField.isAnnotationPresent(org.apache.juneau.annotation.URI.class));
183            }
184            if (p != null) {
185               if (! p.properties().isEmpty())
186                  properties = split(p.properties());
187               bdClasses.addAll(Arrays.asList(p.beanDictionary()));
188            }
189            Swap s = innerField.getAnnotation(Swap.class);
190            if (s != null) {
191               swap = getPropertyPojoSwap(s);
192            }
193         }
194
195         if (getter != null) {
196            BeanProperty p = ClassUtils.getAnnotation(BeanProperty.class, getter);
197            if (rawTypeMeta == null)
198               rawTypeMeta = f.resolveClassMeta(p, getter.getGenericReturnType(), typeVarImpls);
199            isUri |= (rawTypeMeta.isUri() || getter.isAnnotationPresent(org.apache.juneau.annotation.URI.class));
200            if (p != null) {
201               if (properties != null && ! p.properties().isEmpty())
202                  properties = split(p.properties());
203               bdClasses.addAll(Arrays.asList(p.beanDictionary()));
204            }
205            Swap s = getter.getAnnotation(Swap.class);
206            if (s != null && swap == null) {
207               swap = getPropertyPojoSwap(s);
208            }
209         }
210
211         if (setter != null) {
212            BeanProperty p = ClassUtils.getAnnotation(BeanProperty.class, setter);
213            if (rawTypeMeta == null)
214               rawTypeMeta = f.resolveClassMeta(p, setter.getGenericParameterTypes()[0], typeVarImpls);
215            isUri |= (rawTypeMeta.isUri() || setter.isAnnotationPresent(org.apache.juneau.annotation.URI.class));
216            if (p != null) {
217               if (swap == null)
218                  swap = getPropertyPojoSwap(p);
219               if (properties != null && ! p.properties().isEmpty())
220                  properties = split(p.properties());
221               bdClasses.addAll(Arrays.asList(p.beanDictionary()));
222            }
223            Swap s = setter.getAnnotation(Swap.class);
224            if (s != null && swap == null) {
225               swap = getPropertyPojoSwap(s);
226            }
227         }
228
229         if (rawTypeMeta == null)
230            return false;
231
232         this.beanRegistry = new BeanRegistry(beanContext, parentBeanRegistry, bdClasses.toArray(new Class<?>[0]));
233
234         isDyna = "*".equals(name);
235
236         // Do some annotation validation.
237         Class<?> c = rawTypeMeta.getInnerClass();
238         if (getter != null) {
239            Class<?>[] pt = getter.getParameterTypes();
240            if (isDyna) {
241               if (isParentClass(Map.class, c) && pt.length == 0) {
242                  isDynaGetterMap = true;
243               } else if (pt.length == 1 && pt[0] == String.class) {
244                  // OK.
245               } else {
246                  return false;
247               }
248            } else {
249               if (! isParentClass(getter.getReturnType(), c))
250                  return false;
251            }
252         }
253         if (setter != null) {
254            Class<?>[] pt = setter.getParameterTypes();
255            if (isDyna) {
256               if (pt.length == 2 && pt[0] == String.class) {
257                  // OK.
258               } else {
259                  return false;
260               }
261            } else {
262               if (pt.length != 1)
263                  return false;
264               if (! isParentClass(pt[0], c))
265                  return false;
266            }
267         }
268         if (field != null) {
269            if (isDyna) {
270               if (! isParentClass(Map.class, field.getType()))
271                  return false;
272            } else {
273               if (! isParentClass(field.getType(), c))
274                  return false;
275            }
276         }
277
278         if (isDyna) {
279            rawTypeMeta = rawTypeMeta.getValueType();
280            if (rawTypeMeta == null)
281               rawTypeMeta = beanContext.object();
282         }
283         if (rawTypeMeta == null)
284            return false;
285
286         if (typeMeta == null)
287            typeMeta = (swap != null ? beanContext.getClassMeta(swap.getSwapClass()) : rawTypeMeta == null ? beanContext.object() : rawTypeMeta);
288         if (typeMeta == null)
289            typeMeta = rawTypeMeta;
290
291         return true;
292      }
293
294      /**
295       * @return A new BeanPropertyMeta object using this builder.
296       */
297      public BeanPropertyMeta build() {
298         return new BeanPropertyMeta(this);
299      }
300
301      private PojoSwap getPropertyPojoSwap(BeanProperty p) throws Exception {
302         if (! p.format().isEmpty())
303            return beanContext.newInstance(PojoSwap.class, StringFormatSwap.class, false, p.format());
304         return null;
305      }
306
307      private PojoSwap getPropertyPojoSwap(Swap s) throws Exception {
308         Class<?> c = s.value();
309         if (c == Null.class)
310            c = s.impl();
311         if (c == Null.class)
312            return null;
313         if (isParentClass(PojoSwap.class, c)) {
314            PojoSwap ps = beanContext.newInstance(PojoSwap.class, c);
315            if (ps.forMediaTypes() != null)
316               throw new RuntimeException("TODO - Media types on swaps not yet supported on bean properties.");
317            if (ps.withTemplate() != null)
318               throw new RuntimeException("TODO - Templates on swaps not yet supported on bean properties.");
319            return ps;
320         }
321         if (isParentClass(Surrogate.class, c))
322            throw new RuntimeException("TODO - Surrogate swaps not yet supported on bean properties.");
323         throw new FormattedRuntimeException("Invalid class used in @Swap annotation.  Must be a subclass of PojoSwap or Surrogate.", c);
324      }
325
326      BeanPropertyMeta.Builder setGetter(Method getter) {
327         setAccessible(getter, false);
328         this.getter = getter;
329         return this;
330      }
331
332      BeanPropertyMeta.Builder setSetter(Method setter) {
333         setAccessible(setter, false);
334         this.setter = setter;
335         return this;
336      }
337
338      BeanPropertyMeta.Builder setField(Field field) {
339         setAccessible(field, false);
340         this.field = field;
341         this.innerField = field;
342         return this;
343      }
344
345      BeanPropertyMeta.Builder setInnerField(Field innerField) {
346         this.innerField = innerField;
347         return this;
348      }
349
350      BeanPropertyMeta.Builder setExtraKeys(Method extraKeys) {
351         setAccessible(extraKeys, false);
352         this.extraKeys = extraKeys;
353         return this;
354      }
355
356      BeanPropertyMeta.Builder setAsConstructorArg() {
357         this.isConstructorArg = true;
358         return this;
359      }
360
361   }
362
363   /**
364    * Creates a new BeanPropertyMeta using the contents of the specified builder.
365    *
366    * @param b The builder to copy fields from.
367    */
368   protected BeanPropertyMeta(BeanPropertyMeta.Builder b) {
369      this.field = b.field;
370      this.innerField = b.innerField;
371      this.getter = b.getter;
372      this.setter = b.setter;
373      this.extraKeys = b.extraKeys;
374      this.isUri = b.isUri;
375      this.beanMeta = b.beanMeta;
376      this.beanContext = b.beanContext;
377      this.name = b.name;
378      this.rawTypeMeta = b.rawTypeMeta;
379      this.typeMeta = b.typeMeta;
380      this.properties = b.properties;
381      this.swap = b.swap;
382      this.beanRegistry = b.beanRegistry;
383      this.overrideValue = b.overrideValue;
384      this.delegateFor = b.delegateFor;
385      this.extMeta = b.extMeta;
386      this.isDyna = b.isDyna;
387      this.isDynaGetterMap = b.isDynaGetterMap;
388      this.canRead = b.canRead;
389      this.canWrite = b.canWrite;
390   }
391
392   /**
393    * Returns the name of this bean property.
394    *
395    * @return The name of the bean property.
396    */
397   public String getName() {
398      return name;
399   }
400
401   /**
402    * Returns the bean meta that this property belongs to.
403    *
404    * @return The bean meta that this property belongs to.
405    */
406   @BeanIgnore
407   public BeanMeta<?> getBeanMeta() {
408      return beanMeta;
409   }
410
411   /**
412    * Returns the getter method for this property.
413    *
414    * @return The getter method for this bean property, or <jk>null</jk> if there is no getter method.
415    */
416   public Method getGetter() {
417      return getter;
418   }
419
420   /**
421    * Returns the setter method for this property.
422    *
423    * @return The setter method for this bean property, or <jk>null</jk> if there is no setter method.
424    */
425   public Method getSetter() {
426      return setter;
427   }
428
429   /**
430    * Returns the field for this property.
431    *
432    * @return The field for this bean property, or <jk>null</jk> if there is no field associated with this bean property.
433    */
434   public Field getField() {
435      return field;
436   }
437
438   /**
439    * Returns the field for this property even if the field is private.
440    *
441    * @return The field for this bean property, or <jk>null</jk> if there is no field associated with this bean property.
442    */
443   public Field getInnerField() {
444      return innerField;
445   }
446
447   /**
448    * Returns the {@link ClassMeta} of the class of this property.
449    *
450    * <p>
451    * If this property or the property type class has a {@link PojoSwap} associated with it, this method returns the
452    * transformed class meta.
453    * This matches the class type that is used by the {@link #get(BeanMap,String)} and
454    * {@link #set(BeanMap,String,Object)} methods.
455    *
456    * @return The {@link ClassMeta} of the class of this property.
457    */
458   public ClassMeta<?> getClassMeta() {
459      return typeMeta;
460   }
461
462   /**
463    * Returns the bean dictionary in use for this bean property.
464    *
465    * <p>
466    * The order of lookup for the dictionary is as follows:
467    * <ol>
468    *    <li>Dictionary defined via {@link BeanProperty#beanDictionary() @BeanProperty(beanDictionary)}.
469    *    <li>Dictionary defined via {@link BeanContext#BEAN_beanDictionary} context property.
470    * </ol>
471    *
472    * @return The bean dictionary in use for this bean property.  Never <jk>null</jk>.
473    */
474   public BeanRegistry getBeanRegistry() {
475      return beanRegistry;
476   }
477
478   /**
479    * Returns <jk>true</jk> if this bean property is a URI.
480    *
481    * <p>
482    * A bean property can be considered a URI if any of the following are true:
483    * <ul>
484    *    <li>Property class type is {@link URL} or {@link URI}.
485    *    <li>Property class type is annotated with {@link org.apache.juneau.annotation.URI @URI}.
486    *    <li>Property getter, setter, or field is annotated with {@link org.apache.juneau.annotation.URI @URI}.
487    * </ul>
488    *
489    * @return <jk>true</jk> if this bean property is a URI.
490    */
491   public boolean isUri() {
492      return isUri;
493   }
494
495   /**
496    * Returns <jk>true</jk> if this bean property is named <js>"*"</js>.
497    *
498    * @return <jk>true</jk> if this bean property is named <js>"*"</js>.
499    */
500   public boolean isDyna() {
501      return isDyna;
502   }
503
504   /**
505    * Returns the override list of properties defined through a {@link BeanProperty#properties() @BeanProperty(properties)} annotation
506    * on this property.
507    *
508    * @return The list of override properties, or <jk>null</jk> if annotation not specified.
509    */
510   public String[] getProperties() {
511      return properties;
512   }
513
514   /**
515    * Returns the language-specified extended metadata on this bean property.
516    *
517    * @param c The name of the metadata class to create.
518    * @return Extended metadata on this bean property.  Never <jk>null</jk>.
519    */
520   public <M extends BeanPropertyMetaExtended> M getExtendedMeta(Class<M> c) {
521      if (delegateFor != null)
522         return delegateFor.getExtendedMeta(c);
523      return extMeta.get(c, this);
524   }
525
526   /**
527    * Equivalent to calling {@link BeanMap#get(Object)}, but is faster since it avoids looking up the property meta.
528    *
529    * @param m The bean map to get the transformed value from.
530    * @param pName The property name.
531    * @return The property value.
532    */
533   public Object get(BeanMap<?> m, String pName) {
534      try {
535         if (overrideValue != null)
536            return overrideValue;
537
538         // Read-only beans have their properties stored in a cache until getBean() is called.
539         Object bean = m.bean;
540         if (bean == null)
541            return m.propertyCache.get(name);
542
543         return toSerializedForm(m.getBeanSession(), getRaw(m, pName));
544
545      } catch (Throwable e) {
546         if (beanContext.isIgnoreInvocationExceptionsOnGetters()) {
547            if (rawTypeMeta.isPrimitive())
548               return rawTypeMeta.getPrimitiveDefault();
549            return null;
550         }
551         throw new BeanRuntimeException(e, beanMeta.c, "Exception occurred while getting property ''{0}''", name);
552      }
553   }
554
555   /**
556    * Equivalent to calling {@link BeanMap#getRaw(Object)}, but is faster since it avoids looking up the property meta.
557    *
558    * @param m The bean map to get the transformed value from.
559    * @param pName The property name.
560    * @return The raw property value.
561    */
562   public Object getRaw(BeanMap<?> m, String pName) {
563      try {
564         // Read-only beans have their properties stored in a cache until getBean() is called.
565         Object bean = m.bean;
566         if (bean == null)
567            return m.propertyCache.get(name);
568
569         return invokeGetter(bean, pName);
570
571      } catch (Throwable e) {
572         if (beanContext.isIgnoreInvocationExceptionsOnGetters()) {
573            if (rawTypeMeta.isPrimitive())
574               return rawTypeMeta.getPrimitiveDefault();
575            return null;
576         }
577         throw new BeanRuntimeException(e, beanMeta.c, "Exception occurred while getting property ''{0}''", name);
578      }
579   }
580
581   /**
582    * Converts a raw bean property value to serialized form.
583    * Applies transforms and child property filters.
584    */
585   final Object toSerializedForm(BeanSession session, Object o) {
586      try {
587         o = transform(session, o);
588         if (o == null)
589            return null;
590         if (properties != null) {
591            if (rawTypeMeta.isArray()) {
592               Object[] a = (Object[])o;
593               List l = new DelegateList(rawTypeMeta);
594               ClassMeta childType = rawTypeMeta.getElementType();
595               for (Object c : a)
596                  l.add(applyChildPropertiesFilter(session, childType, c));
597               return l;
598            } else if (rawTypeMeta.isCollection()) {
599               Collection c = (Collection)o;
600               List l = new ArrayList(c.size());
601               ClassMeta childType = rawTypeMeta.getElementType();
602               for (Object cc : c)
603                  l.add(applyChildPropertiesFilter(session, childType, cc));
604               return l;
605            } else {
606               return applyChildPropertiesFilter(session, rawTypeMeta, o);
607            }
608         }
609         return o;
610      } catch (SerializeException e) {
611         throw new BeanRuntimeException(e);
612      }
613   }
614
615   /**
616    * Equivalent to calling {@link BeanMap#put(String, Object)}, but is faster since it avoids looking up the property
617    * meta.
618    *
619    * @param m The bean map to set the property value on.
620    * @param pName The property name.
621    * @param value The value to set.
622    * @return The previous property value.
623    * @throws BeanRuntimeException If property could not be set.
624    */
625   public Object set(BeanMap<?> m, String pName, Object value) throws BeanRuntimeException {
626      try {
627
628         BeanSession session = m.getBeanSession();
629
630         // Convert to raw form.
631         value = unswap(session, value);
632
633         if (m.bean == null) {
634
635            // Read-only beans get their properties stored in a cache.
636            if (m.propertyCache != null)
637               return m.propertyCache.put(name, value);
638
639            throw new BeanRuntimeException("Non-existent bean instance on bean.");
640         }
641
642         boolean isMap = rawTypeMeta.isMap();
643         boolean isCollection = rawTypeMeta.isCollection();
644
645         if ((! isDyna) && field == null && setter == null && ! (isMap || isCollection)) {
646            if ((value == null && beanContext.isIgnoreUnknownNullBeanProperties()) || beanContext.isIgnorePropertiesWithoutSetters())
647               return null;
648            throw new BeanRuntimeException(beanMeta.c, "Setter or public field not defined on property ''{0}''", name);
649         }
650
651         Object bean = m.getBean(true);  // Don't use getBean() because it triggers array creation!
652
653         try {
654
655            Object r = (beanContext.isBeanMapPutReturnsOldValue() || isMap || isCollection) && (getter != null || field != null) ? get(m, pName) : null;
656            Class<?> propertyClass = rawTypeMeta.getInnerClass();
657
658            if (value == null && (isMap || isCollection)) {
659               invokeSetter(bean, pName, null);
660               return r;
661            }
662
663            if (isMap) {
664
665               if (! (value instanceof Map)) {
666                  if (value instanceof CharSequence)
667                     value = new ObjectMap((CharSequence)value).setBeanSession(session);
668                  else
669                     throw new BeanRuntimeException(beanMeta.c, "Cannot set property ''{0}'' of type ''{1}'' to object of type ''{2}''", name, propertyClass.getName(), findClassName(value));
670               }
671
672               Map valueMap = (Map)value;
673               Map propMap = (Map)r;
674               ClassMeta<?> valueType = rawTypeMeta.getValueType();
675
676               // If the property type is abstract, then we either need to reuse the existing
677               // map (if it's not null), or try to assign the value directly.
678               if (! rawTypeMeta.canCreateNewInstance()) {
679                  if (propMap == null) {
680                     if (setter == null && field == null)
681                        throw new BeanRuntimeException(beanMeta.c, "Cannot set property ''{0}'' of type ''{1}'' to object of type ''{2}'' because no setter or public field is defined, and the current value is null", name, propertyClass.getName(), findClassName(value));
682
683                     if (propertyClass.isInstance(valueMap)) {
684                        if (! valueType.isObject()) {
685                           boolean needsConversion = false;
686                           for (Map.Entry e : (Set<Map.Entry>)valueMap.entrySet()) {
687                              Object v = e.getValue();
688                              if (v != null && ! valueType.getInnerClass().isInstance(v)) {
689                                 needsConversion = true;
690                                 break;
691                              }
692                           }
693                           if (needsConversion)
694                              valueMap = (Map)session.convertToType(valueMap, rawTypeMeta);
695                        }
696                        invokeSetter(bean, pName, valueMap);
697                        return r;
698                     }
699                     throw new BeanRuntimeException(beanMeta.c, "Cannot set property ''{0}'' of type ''{2}'' to object of type ''{2}'' because the assigned map cannot be converted to the specified type because the property type is abstract, and the property value is currently null", name, propertyClass.getName(), findClassName(value));
700                  }
701               } else {
702                  if (propMap == null) {
703                     propMap = beanContext.newInstance(Map.class, propertyClass);
704                  } else {
705                     propMap.clear();
706                  }
707               }
708
709               // Set the values.
710               for (Map.Entry e : (Set<Map.Entry>)valueMap.entrySet()) {
711                  Object k = e.getKey();
712                  Object v = e.getValue();
713                  if (! valueType.isObject())
714                     v = session.convertToType(v, valueType);
715                  propMap.put(k, v);
716               }
717               if (setter != null || field != null)
718                  invokeSetter(bean, pName, propMap);
719
720            } else if (isCollection) {
721
722               if (! (value instanceof Collection)) {
723                  if (value instanceof CharSequence)
724                     value = new ObjectList((CharSequence)value).setBeanSession(session);
725                  else
726                     throw new BeanRuntimeException(beanMeta.c, "Cannot set property ''{0}'' of type ''{1}'' to object of type ''{2}''", name, propertyClass.getName(), findClassName(value));
727               }
728
729               Collection valueList = (Collection)value;
730               Collection propList = (Collection)r;
731                  ClassMeta elementType = rawTypeMeta.getElementType();
732
733               // If the property type is abstract, then we either need to reuse the existing
734               // collection (if it's not null), or try to assign the value directly.
735               if (! rawTypeMeta.canCreateNewInstance()) {
736                  if (propList == null) {
737                     if (setter == null && field == null)
738                        throw new BeanRuntimeException(beanMeta.c, "Cannot set property ''{0}'' of type ''{1}'' to object of type ''{2}'' because no setter or public field is defined, and the current value is null", name, propertyClass.getName(), findClassName(value));
739
740                     if (propertyClass.isInstance(valueList)) {
741                        if (! elementType.isObject()) {
742                              List l = new ObjectList(valueList);
743                              for (ListIterator<Object> i = l.listIterator(); i.hasNext(); ) {
744                                 Object v = i.next();
745                                 if (v != null && (! elementType.getInnerClass().isInstance(v))) {
746                                    i.set(session.convertToType(v, elementType));
747                                 }
748                              }
749                              valueList = l;
750                           }
751                        invokeSetter(bean, pName, valueList);
752                        return r;
753                     }
754                     throw new BeanRuntimeException(beanMeta.c, "Cannot set property ''{0}'' of type ''{1}'' to object of type ''{2}'' because the assigned map cannot be converted to the specified type because the property type is abstract, and the property value is currently null", name, propertyClass.getName(), findClassName(value));
755                  }
756                  propList.clear();
757               } else {
758                  if (propList == null) {
759                     propList = beanContext.newInstance(Collection.class, propertyClass);
760                     invokeSetter(bean, pName, propList);
761                  } else {
762                     propList.clear();
763                  }
764               }
765
766               // Set the values.
767               for (Object v : valueList) {
768                  if (! elementType.isObject())
769                     v = session.convertToType(v, elementType);
770                  propList.add(v);
771               }
772
773            } else {
774               if (swap != null && value != null && isParentClass(swap.getSwapClass(), value.getClass())) {
775                  value = swap.unswap(session, value, rawTypeMeta);
776               } else {
777                  value = session.convertToType(value, rawTypeMeta);
778               }
779               invokeSetter(bean, pName, value);
780            }
781
782            return r;
783
784         } catch (BeanRuntimeException e) {
785            throw e;
786         } catch (Exception e) {
787            e.printStackTrace();
788            if (beanContext.isIgnoreInvocationExceptionsOnSetters()) {
789                  if (rawTypeMeta.isPrimitive())
790                     return rawTypeMeta.getPrimitiveDefault();
791               return null;
792            }
793            throw new BeanRuntimeException(e, beanMeta.c, "Error occurred trying to set property ''{0}''", name);
794         }
795      } catch (ParseException e) {
796         throw new BeanRuntimeException(e);
797      }
798   }
799
800   private Object invokeGetter(Object bean, String pName) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException {
801      if (isDyna) {
802         Map m = null;
803         if (getter != null) {
804            if (! isDynaGetterMap)
805               return getter.invoke(bean, pName);
806            m = (Map)getter.invoke(bean);
807         }
808         else if (field != null)
809            m = (Map)field.get(bean);
810         else
811            throw new BeanRuntimeException(beanMeta.c, "Getter or public field not defined on property ''{0}''", name);
812         return (m == null ? null : m.get(pName));
813      }
814      if (getter != null)
815         return getter.invoke(bean);
816      if (field != null)
817         return field.get(bean);
818      throw new BeanRuntimeException(beanMeta.c, "Getter or public field not defined on property ''{0}''", name);
819   }
820
821   private Object invokeSetter(Object bean, String pName, Object val) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException {
822      if (isDyna) {
823         if (setter != null)
824            return setter.invoke(bean, pName, val);
825         Map m = null;
826         if (field != null)
827            m = (Map<String,Object>)field.get(bean);
828         else if (getter != null)
829            m = (Map<String,Object>)getter.invoke(bean);
830         else
831            throw new BeanRuntimeException(beanMeta.c, "Cannot set property ''{0}'' of type ''{1}'' to object of type ''{2}'' because no setter is defined on this property, and the existing property value is null", name, this.getClassMeta().getInnerClass().getName(), findClassName(val));
832         return (m == null ? null : m.put(pName, val));
833      }
834      if (setter != null)
835         return setter.invoke(bean, val);
836      if (field != null) {
837         field.set(bean, val);
838         return null;
839      }
840      throw new BeanRuntimeException(beanMeta.c, "Cannot set property ''{0}'' of type ''{1}'' to object of type ''{2}'' because no setter is defined on this property, and the existing property value is null", name, this.getClassMeta().getInnerClass().getName(), findClassName(val));
841   }
842
843   /**
844    * Returns the {@link Map} object returned by the DynaBean getter.
845    *
846    * <p>
847    * The DynaBean property is the property whose name is <js>"*"</js> and returns a map of "extra" properties on the
848    * bean.
849    *
850    * @param bean The bean.
851    * @return
852    *    The map returned by the getter, or an empty map if the getter returned <jk>null</jk> or this isn't a DynaBean
853    *    property.
854    * @throws IllegalArgumentException Thrown by method invocation.
855    * @throws IllegalAccessException Thrown by method invocation.
856    * @throws InvocationTargetException Thrown by method invocation.
857    */
858   public Map<String,Object> getDynaMap(Object bean) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException {
859      if (isDyna) {
860         if (extraKeys != null && getter != null && ! isDynaGetterMap) {
861            Map<String,Object> m = new LinkedHashMap<>();
862            for (String key : (Collection<String>)extraKeys.invoke(bean))
863               m.put(key, getter.invoke(bean, key));
864            return m;
865         }
866         if (getter != null && isDynaGetterMap)
867            return (Map)getter.invoke(bean);
868         if (field != null)
869            return (Map)field.get(bean);
870         throw new BeanRuntimeException(beanMeta.c, "Getter or public field not defined on property ''{0}''", name);
871      }
872      return Collections.EMPTY_MAP;
873   }
874
875   /**
876    * Sets an array field on this bean.
877    *
878    * <p>
879    * Works on both <code>Object</code> and primitive arrays.
880    *
881    * @param bean The bean of the field.
882    * @param l The collection to use to set the array field.
883    * @throws IllegalArgumentException Thrown by method invocation.
884    * @throws IllegalAccessException Thrown by method invocation.
885    * @throws InvocationTargetException Thrown by method invocation.
886    */
887   protected void setArray(Object bean, List l) throws IllegalArgumentException, IllegalAccessException, InvocationTargetException {
888      Object array = toArray(l, this.rawTypeMeta.getElementType().getInnerClass());
889      invokeSetter(bean, name, array);
890   }
891
892   /**
893    * Adds a value to a {@link Collection} or array property.
894    *
895    * <p>
896    * Note that adding values to an array property is inefficient for large arrays since it must copy the array into a
897    * larger array on each operation.
898    *
899    * @param m The bean of the field being set.
900    * @param pName The property name.
901    * @param value The value to add to the field.
902    * @throws BeanRuntimeException If field is not a collection or array.
903    */
904   public void add(BeanMap<?> m, String pName, Object value) throws BeanRuntimeException {
905
906      // Read-only beans get their properties stored in a cache.
907      if (m.bean == null) {
908         if (! m.propertyCache.containsKey(name))
909            m.propertyCache.put(name, new ObjectList(m.getBeanSession()));
910         ((ObjectList)m.propertyCache.get(name)).add(value);
911         return;
912      }
913
914      BeanSession session = m.getBeanSession();
915
916      boolean isCollection = rawTypeMeta.isCollection();
917      boolean isArray = rawTypeMeta.isArray();
918
919      if (! (isCollection || isArray))
920         throw new BeanRuntimeException(beanMeta.c, "Attempt to add element to property ''{0}'' which is not a collection or array", name);
921
922      Object bean = m.getBean(true);
923
924      ClassMeta<?> elementType = rawTypeMeta.getElementType();
925
926      try {
927         Object v = session.convertToType(value, elementType);
928
929         if (isCollection) {
930            Collection c = (Collection)invokeGetter(bean, pName);
931
932            if (c != null) {
933               c.add(v);
934               return;
935            }
936
937            if (rawTypeMeta.canCreateNewInstance())
938               c = (Collection)rawTypeMeta.newInstance();
939            else
940               c = new ObjectList(session);
941
942            c.add(v);
943
944            invokeSetter(bean, pName, c);
945
946         } else /* isArray() */ {
947
948            if (m.arrayPropertyCache == null)
949               m.arrayPropertyCache = new TreeMap<>();
950
951            List l = m.arrayPropertyCache.get(name);
952            if (l == null) {
953               l = new LinkedList();  // ArrayLists and LinkLists appear to perform equally.
954               m.arrayPropertyCache.put(name, l);
955
956               // Copy any existing array values into the temporary list.
957               Object oldArray = invokeGetter(bean, pName);
958               copyToList(oldArray, l);
959            }
960
961            // Add new entry to our array.
962            l.add(v);
963         }
964
965      } catch (BeanRuntimeException e) {
966         throw e;
967      } catch (Exception e) {
968         throw new BeanRuntimeException(e);
969      }
970   }
971
972   /**
973    * Adds a value to a {@link Map} or bean property.
974    *
975    * @param m The bean of the field being set.
976    * @param pName The property name.
977    * @param key The key to add to the field.
978    * @param value The value to add to the field.
979    * @throws BeanRuntimeException If field is not a map or array.
980    */
981   public void add(BeanMap<?> m, String pName, String key, Object value) throws BeanRuntimeException {
982
983      // Read-only beans get their properties stored in a cache.
984      if (m.bean == null) {
985         if (! m.propertyCache.containsKey(name))
986            m.propertyCache.put(name, new ObjectMap(m.getBeanSession()));
987         ((ObjectMap)m.propertyCache.get(name)).append(key.toString(), value);
988         return;
989      }
990
991      BeanSession session = m.getBeanSession();
992
993      boolean isMap = rawTypeMeta.isMap();
994      boolean isBean = rawTypeMeta.isBean();
995
996      if (! (isBean || isMap))
997         throw new BeanRuntimeException(beanMeta.c, "Attempt to add key/value to property ''{0}'' which is not a map or bean", name);
998
999      Object bean = m.getBean(true);
1000
1001      ClassMeta<?> elementType = rawTypeMeta.getElementType();
1002
1003      try {
1004         Object v = session.convertToType(value, elementType);
1005
1006         if (isMap) {
1007            Map map = (Map)invokeGetter(bean, pName);
1008
1009            if (map != null) {
1010               map.put(key, v);
1011               return;
1012            }
1013
1014            if (rawTypeMeta.canCreateNewInstance())
1015               map = (Map)rawTypeMeta.newInstance();
1016            else
1017               map = new ObjectMap(session);
1018
1019            map.put(key, v);
1020
1021            invokeSetter(bean, pName, map);
1022
1023         } else /* isBean() */ {
1024
1025            Object b = invokeGetter(bean, pName);
1026
1027            if (b != null) {
1028               BeanMap bm = session.toBeanMap(b);
1029               bm.put(key, v);
1030               return;
1031            }
1032
1033            if (rawTypeMeta.canCreateNewInstance(m.getBean(false))) {
1034               b = rawTypeMeta.newInstance();
1035               BeanMap bm = session.toBeanMap(b);
1036               bm.put(key, v);
1037            }
1038
1039            invokeSetter(bean, pName, b);
1040         }
1041
1042      } catch (BeanRuntimeException e) {
1043         throw e;
1044      } catch (Exception e) {
1045         throw new BeanRuntimeException(e);
1046      }
1047   }
1048
1049   /**
1050    * Returns all instances of the specified annotation in the hierarchy of this bean property.
1051    *
1052    * <p>
1053    * Searches through the class hierarchy (e.g. superclasses, interfaces, packages) for all instances of the
1054    * specified annotation.
1055    *
1056    * @param a The class to find annotations for.
1057    * @return A list of annotations ordered in child-to-parent order.  Never <jk>null</jk>.
1058    */
1059   public <A extends Annotation> List<A> findAnnotations(Class<A> a) {
1060      List<A> l = new LinkedList<>();
1061      if (field != null) {
1062         addIfNotNull(l, field.getAnnotation(a));
1063         appendAnnotations(a, field.getType(), l);
1064      }
1065      if (getter != null) {
1066         addIfNotNull(l, ClassUtils.getAnnotation(a, getter));
1067         appendAnnotations(a, getter.getReturnType(), l);
1068      }
1069      if (setter != null) {
1070         addIfNotNull(l, ClassUtils.getAnnotation(a, setter));
1071         appendAnnotations(a, setter.getReturnType(), l);
1072      }
1073      if (extraKeys != null) {
1074         addIfNotNull(l, ClassUtils.getAnnotation(a, extraKeys));
1075         appendAnnotations(a, extraKeys.getReturnType(), l);
1076      }
1077
1078      appendAnnotations(a, this.getBeanMeta().getClassMeta().getInnerClass(), l);
1079      return l;
1080   }
1081
1082   /**
1083    * Returns the specified annotation on the field or methods that define this property.
1084    *
1085    * <p>
1086    * This method will search up the parent class/interface hierarchy chain to search for the annotation on
1087    * overridden getters and setters.
1088    *
1089    * @param a The annotation to search for.
1090    * @return The annotation, or <jk>null</jk> if it wasn't found.
1091    */
1092   public <A extends Annotation> A findAnnotation(Class<A> a) {
1093      A t = null;
1094      if (field != null)
1095         t = field.getAnnotation(a);
1096      if (t == null && getter != null)
1097         t = ClassUtils.getAnnotation(a, getter);
1098      if (t == null && setter != null)
1099         t = ClassUtils.getAnnotation(a, setter);
1100      if (t == null && extraKeys != null)
1101         t = ClassUtils.getAnnotation(a, extraKeys);
1102      if (t == null)
1103         t = ClassUtils.getAnnotation(a, typeMeta.getInnerClass());
1104      return t;
1105   }
1106
1107   private Object transform(BeanSession session, Object o) throws SerializeException {
1108      try {
1109         // First use swap defined via @BeanProperty.
1110         if (swap != null)
1111            return swap.swap(session, o);
1112         if (o == null)
1113            return null;
1114         // Otherwise, look it up via bean context.
1115         if (rawTypeMeta.hasChildPojoSwaps()) {
1116            PojoSwap f = rawTypeMeta.getChildPojoSwapForSwap(o.getClass());
1117            if (f != null)
1118               return f.swap(session, o);
1119         }
1120         return o;
1121      } catch (SerializeException e) {
1122         throw e;
1123      } catch (Exception e) {
1124         throw new SerializeException(e);
1125      }
1126   }
1127
1128   private Object unswap(BeanSession session, Object o) throws ParseException {
1129      try {
1130         if (swap != null)
1131            return swap.unswap(session, o, rawTypeMeta);
1132         if (o == null)
1133            return null;
1134         if (rawTypeMeta.hasChildPojoSwaps()) {
1135            PojoSwap f = rawTypeMeta.getChildPojoSwapForUnswap(o.getClass());
1136            if (f != null)
1137               return f.unswap(session, o, rawTypeMeta);
1138         }
1139         return o;
1140      } catch (ParseException e) {
1141         throw e;
1142      } catch (Exception e) {
1143         throw new ParseException((Throwable)e);
1144      }
1145   }
1146
1147   private Object applyChildPropertiesFilter(BeanSession session, ClassMeta cm, Object o) {
1148      if (o == null)
1149         return null;
1150      if (cm.isBean())
1151         return new BeanMap(session, o, new BeanMetaFiltered(cm.getBeanMeta(), properties));
1152      if (cm.isMap())
1153         return new FilteredMap(cm, (Map)o, properties);
1154      if (cm.isObject()) {
1155         if (o instanceof Map)
1156            return new FilteredMap(cm, (Map)o, properties);
1157         BeanMeta bm = beanContext.getBeanMeta(o.getClass());
1158         if (bm != null)
1159            return new BeanMap(session, o, new BeanMetaFiltered(cm.getBeanMeta(), properties));
1160      }
1161      return o;
1162   }
1163
1164   private static String findClassName(Object o) {
1165      if (o == null)
1166         return null;
1167      if (o instanceof Class)
1168         return ((Class<?>)o).getName();
1169      return o.getClass().getName();
1170   }
1171
1172   @Override /* Object */
1173   public String toString() {
1174      return name + ": " + this.rawTypeMeta.getInnerClass().getName() + ", field=["+field+"], getter=["+getter+"], setter=["+setter+"]";
1175   }
1176
1177   /**
1178    * Returns <jk>true</jk> if this property can be read.
1179    *
1180    * @return <jk>true</jk> if this property can be read.
1181    */
1182   public boolean canRead() {
1183      return canRead;
1184   }
1185
1186   /**
1187    * Returns <jk>true</jk> if this property can be written.
1188    *
1189    * @return <jk>true</jk> if this property can be written.
1190    */
1191   public boolean canWrite() {
1192      return canWrite;
1193   }
1194
1195   /**
1196    * @deprecated Use {@link #findAnnotation(Class)}
1197    */
1198   @SuppressWarnings("javadoc")
1199   @Deprecated
1200   public <A extends Annotation> A getAnnotation(Class<A> a) {
1201      return findAnnotation(a);
1202   }
1203}