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