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