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