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