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