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