1   /*
2    * SPDX-FileCopyrightText: none
3    * SPDX-License-Identifier: CC0-1.0
4    */
5   
6   package dev.metaschema.databind.model;
7   
8   import java.lang.reflect.Field;
9   import java.lang.reflect.ParameterizedType;
10  import java.lang.reflect.Type;
11  import java.util.Collection;
12  import java.util.List;
13  import java.util.Map;
14  
15  import dev.metaschema.core.model.IBoundObject;
16  import dev.metaschema.core.model.IModelInstanceAbsolute;
17  import dev.metaschema.core.util.ObjectUtils;
18  import dev.metaschema.databind.io.BindingException;
19  import dev.metaschema.databind.model.info.IModelInstanceCollectionInfo;
20  import edu.umd.cs.findbugs.annotations.NonNull;
21  import edu.umd.cs.findbugs.annotations.Nullable;
22  
23  /**
24   * Represents an assembly or field instance bound to Java data.
25   *
26   * @param <ITEM>
27   *          the Java type for associated bound objects
28   */
29  public interface IBoundInstanceModel<ITEM>
30      extends IBoundInstance<ITEM>, IModelInstanceAbsolute {
31    /**
32     * Get the collection Java item type for the Java field associated with this
33     * instance.
34     *
35     * @param field
36     *          the Java field for the instance
37     * @return the Java item type
38     */
39    @NonNull
40    static Class<?> getItemType(@NonNull Field field) {
41      Type fieldType = field.getGenericType();
42      Class<?> rawType = ObjectUtils.notNull(
43          (Class<?>) (fieldType instanceof ParameterizedType ? ((ParameterizedType) fieldType).getRawType() : fieldType));
44  
45      Class<?> itemType;
46      if (Map.class.isAssignableFrom(rawType)) {
47        // this is a Map so the second generic type is the value
48        itemType = ObjectUtils.notNull((Class<?>) ((ParameterizedType) fieldType).getActualTypeArguments()[1]);
49      } else if (List.class.isAssignableFrom(rawType)) {
50        // this is a List so there is only a single generic type
51        itemType = ObjectUtils.notNull((Class<?>) ((ParameterizedType) fieldType).getActualTypeArguments()[0]);
52      } else {
53        // non-collection
54        itemType = rawType;
55      }
56      return itemType;
57    }
58  
59    /**
60     * Get the collection Java item type for the Java field associated with this
61     * instance.
62     *
63     * @param <TYPE>
64     *          the item's expected Java type
65     * @param field
66     *          the Java field for the instance
67     * @param expectedItemType
68     *          the item's expected Java type, which may be a super type of the
69     *          item's type
70     * @return the Java item type
71     */
72    @NonNull
73    static <TYPE> Class<? extends TYPE> getItemType(@NonNull Field field, @NonNull Class<TYPE> expectedItemType) {
74      Type fieldType = field.getGenericType();
75      Class<?> rawType = ObjectUtils.notNull(
76          (Class<?>) (fieldType instanceof ParameterizedType ? ((ParameterizedType) fieldType).getRawType() : fieldType));
77  
78      Class<?> itemType;
79      if (Map.class.isAssignableFrom(rawType)) {
80        // this is a Map so the second generic type is the value
81        itemType = ObjectUtils.notNull((Class<?>) ((ParameterizedType) fieldType).getActualTypeArguments()[1]);
82      } else if (List.class.isAssignableFrom(rawType)) {
83        // this is a List so there is only a single generic type
84        itemType = ObjectUtils.notNull((Class<?>) ((ParameterizedType) fieldType).getActualTypeArguments()[0]);
85      } else {
86        // non-collection
87        itemType = rawType;
88      }
89      return ObjectUtils.notNull(itemType.asSubclass(expectedItemType));
90    }
91  
92    @Override
93    @NonNull
94    IBoundDefinitionModelAssembly getContainingDefinition();
95  
96    @Override
97    default Object getResolvedDefaultValue() {
98      return getMaxOccurs() == 1 ? getEffectiveDefaultValue() : getCollectionInfo().emptyValue();
99    }
100 
101   /**
102    * Get the item values associated with the provided value.
103    *
104    * @param value
105    *          the value which may be a singleton or a collection
106    * @return the ordered collection of values
107    */
108   @Override
109   @NonNull
110   default Collection<? extends Object> getItemValues(Object value) {
111     return getCollectionInfo().getItemsFromValue(value);
112   }
113 
114   /**
115    * Get the Java binding information for the collection type supported by this
116    * instance.
117    *
118    * @return the collection Java binding information
119    */
120   @NonNull
121   IModelInstanceCollectionInfo<ITEM> getCollectionInfo();
122 
123   /**
124    * Get the JSON key flag for the provided item.
125    *
126    * @param item
127    *          the item to get the JSON key flag for
128    * @return the JSON key flag
129    */
130   @Nullable
131   IBoundInstanceFlag getItemJsonKey(@NonNull Object item);
132 
133   @Override
134   default void deepCopy(@NonNull IBoundObject fromInstance, @NonNull IBoundObject toInstance) throws BindingException {
135     Object value = getValue(fromInstance);
136     if (value != null) {
137       value = getCollectionInfo().deepCopyItems(fromInstance, toInstance);
138     }
139     setValue(toInstance, value);
140   }
141 }