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