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