1   /*
2    * SPDX-FileCopyrightText: none
3    * SPDX-License-Identifier: CC0-1.0
4    */
5   
6   package gov.nist.secauto.metaschema.databind.model.info;
7   
8   import gov.nist.secauto.metaschema.core.model.IBoundObject;
9   import gov.nist.secauto.metaschema.core.model.JsonGroupAsBehavior;
10  import gov.nist.secauto.metaschema.databind.io.BindingException;
11  import gov.nist.secauto.metaschema.databind.model.IBoundInstanceModel;
12  
13  import java.io.IOException;
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  // REFACTOR: parameterize the item type?
25  public interface IModelInstanceCollectionInfo<ITEM> {
26  
27    @SuppressWarnings("PMD.ShortMethodName")
28    @NonNull
29    static <T> IModelInstanceCollectionInfo<T> of(
30        @NonNull IBoundInstanceModel<T> instance) {
31  
32      // create the collection info
33      Type type = instance.getType();
34      Field field = instance.getField();
35  
36      IModelInstanceCollectionInfo<T> retval;
37      if (instance.getMaxOccurs() == -1 || instance.getMaxOccurs() > 1) {
38        // collection case
39        JsonGroupAsBehavior jsonGroupAs = instance.getJsonGroupAsBehavior();
40  
41        // expect a ParameterizedType
42        if (!(type instanceof ParameterizedType)) {
43          switch (jsonGroupAs) {
44          case KEYED:
45            throw new IllegalStateException(
46                String.format("The field '%s' on class '%s' has data type of '%s'," + " but should have a type of '%s'.",
47                    field.getName(),
48                    field.getDeclaringClass().getName(),
49                    field.getType().getName(), Map.class.getName()));
50          case LIST:
51          case SINGLETON_OR_LIST:
52            throw new IllegalStateException(
53                String.format("The field '%s' on class '%s' has data type of '%s'," + " but should have a type of '%s'.",
54                    field.getName(),
55                    field.getDeclaringClass().getName(),
56                    field.getType().getName(), List.class.getName()));
57          default:
58            // this should not occur
59            throw new IllegalStateException(jsonGroupAs.name());
60          }
61        }
62  
63        Class<?> rawType = (Class<?>) ((ParameterizedType) type).getRawType();
64        if (JsonGroupAsBehavior.KEYED.equals(jsonGroupAs)) {
65          if (!Map.class.isAssignableFrom(rawType)) {
66            throw new IllegalArgumentException(String.format(
67                "The field '%s' on class '%s' has data type '%s', which is not the expected '%s' derived data type.",
68                field.getName(),
69                field.getDeclaringClass().getName(),
70                field.getType().getName(),
71                Map.class.getName()));
72          }
73          retval = new MapCollectionInfo<>(instance);
74        } else {
75          if (!List.class.isAssignableFrom(rawType)) {
76            throw new IllegalArgumentException(String.format(
77                "The field '%s' on class '%s' has data type '%s', which is not the expected '%s' derived data type.",
78                field.getName(),
79                field.getDeclaringClass().getName(),
80                field.getType().getName(),
81                List.class.getName()));
82          }
83          retval = new ListCollectionInfo<>(instance);
84        }
85      } else {
86        // single value case
87        if (type instanceof ParameterizedType) {
88          throw new IllegalStateException(String.format(
89              "The field '%s' on class '%s' has a data parmeterized type of '%s',"
90                  + " but the occurance is not multi-valued.",
91              field.getName(),
92              field.getDeclaringClass().getName(),
93              field.getType().getName()));
94        }
95        retval = new SingletonCollectionInfo<>(instance);
96      }
97      return retval;
98    }
99  
100   /**
101    * Get the associated instance binding for which this info is for.
102    *
103    * @return the instance binding
104    */
105   @NonNull
106   IBoundInstanceModel<ITEM> getInstance();
107 
108   /**
109    * Get the number of items associated with the value.
110    *
111    * @param value
112    *          the value to identify items for
113    * @return the number of items, which will be {@code 0} if value is {@code null}
114    */
115   int size(@Nullable Object value);
116 
117   /**
118    * Determine if the value is empty.
119    *
120    * @param value
121    *          the value representing a collection
122    * @return {@code true} if the value represents a collection with no items or
123    *         {@code false} otherwise
124    */
125   boolean isEmpty(@Nullable Object value);
126 
127   /**
128    * Get the type of the bound object.
129    *
130    * @return the raw type of the bound object
131    */
132   @NonNull
133   Class<? extends ITEM> getItemType();
134 
135   @NonNull
136   default Collection<? extends ITEM> getItemsFromParentInstance(@NonNull Object parentInstance) {
137     Object value = getInstance().getValue(parentInstance);
138     return getItemsFromValue(value);
139   }
140 
141   @NonNull
142   Collection<? extends ITEM> getItemsFromValue(Object value);
143 
144   Object emptyValue();
145 
146   Object deepCopyItems(@NonNull IBoundObject fromObject, @NonNull IBoundObject toObject) throws BindingException;
147 
148   /**
149    * Read the value data for the model instance.
150    * <p>
151    * This method will return a value based on the instance's value type.
152    *
153    * @param handler
154    *          the item parsing handler
155    * @return the item collection object or {@code null} if the instance is not
156    *         defined
157    * @throws IOException
158    *           if there was an error when reading the data
159    */
160   @Nullable
161   Object readItems(@NonNull IModelInstanceReadHandler<ITEM> handler) throws IOException;
162 
163   void writeItems(
164       @NonNull IModelInstanceWriteHandler<ITEM> handler,
165       @NonNull Object value) throws IOException;
166 }