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