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