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