001/*
002 * SPDX-FileCopyrightText: none
003 * SPDX-License-Identifier: CC0-1.0
004 */
005
006package gov.nist.secauto.metaschema.databind.model.info;
007
008import gov.nist.secauto.metaschema.core.model.IBoundObject;
009import gov.nist.secauto.metaschema.core.model.JsonGroupAsBehavior;
010import gov.nist.secauto.metaschema.databind.io.BindingException;
011import gov.nist.secauto.metaschema.databind.model.IBoundInstanceModel;
012
013import java.io.IOException;
014import java.lang.reflect.Field;
015import java.lang.reflect.ParameterizedType;
016import java.lang.reflect.Type;
017import java.util.Collection;
018import java.util.List;
019import java.util.Map;
020
021import edu.umd.cs.findbugs.annotations.NonNull;
022import edu.umd.cs.findbugs.annotations.Nullable;
023
024// REFACTOR: parameterize the item type?
025public interface IModelInstanceCollectionInfo<ITEM> {
026
027  @SuppressWarnings("PMD.ShortMethodName")
028  @NonNull
029  static <T> IModelInstanceCollectionInfo<T> of(
030      @NonNull IBoundInstanceModel<T> instance) {
031
032    // create the collection info
033    Type type = instance.getType();
034    Field field = instance.getField();
035
036    IModelInstanceCollectionInfo<T> retval;
037    if (instance.getMaxOccurs() == -1 || instance.getMaxOccurs() > 1) {
038      // collection case
039      JsonGroupAsBehavior jsonGroupAs = instance.getJsonGroupAsBehavior();
040
041      // expect a ParameterizedType
042      if (!(type instanceof ParameterizedType)) {
043        switch (jsonGroupAs) {
044        case KEYED:
045          throw new IllegalStateException(
046              String.format("The field '%s' on class '%s' has data type of '%s'," + " but should have a type of '%s'.",
047                  field.getName(),
048                  field.getDeclaringClass().getName(),
049                  field.getType().getName(), Map.class.getName()));
050        case LIST:
051        case SINGLETON_OR_LIST:
052          throw new IllegalStateException(
053              String.format("The field '%s' on class '%s' has data type of '%s'," + " but should have a type of '%s'.",
054                  field.getName(),
055                  field.getDeclaringClass().getName(),
056                  field.getType().getName(), List.class.getName()));
057        default:
058          // this should not occur
059          throw new IllegalStateException(jsonGroupAs.name());
060        }
061      }
062
063      Class<?> rawType = (Class<?>) ((ParameterizedType) type).getRawType();
064      if (JsonGroupAsBehavior.KEYED.equals(jsonGroupAs)) {
065        if (!Map.class.isAssignableFrom(rawType)) {
066          throw new IllegalArgumentException(String.format(
067              "The field '%s' on class '%s' has data type '%s', which is not the expected '%s' derived data type.",
068              field.getName(),
069              field.getDeclaringClass().getName(),
070              field.getType().getName(),
071              Map.class.getName()));
072        }
073        retval = new MapCollectionInfo<>(instance);
074      } else {
075        if (!List.class.isAssignableFrom(rawType)) {
076          throw new IllegalArgumentException(String.format(
077              "The field '%s' on class '%s' has data type '%s', which is not the expected '%s' derived data type.",
078              field.getName(),
079              field.getDeclaringClass().getName(),
080              field.getType().getName(),
081              List.class.getName()));
082        }
083        retval = new ListCollectionInfo<>(instance);
084      }
085    } else {
086      // single value case
087      if (type instanceof ParameterizedType) {
088        throw new IllegalStateException(String.format(
089            "The field '%s' on class '%s' has a data parmeterized type of '%s',"
090                + " but the occurance is not multi-valued.",
091            field.getName(),
092            field.getDeclaringClass().getName(),
093            field.getType().getName()));
094      }
095      retval = new SingletonCollectionInfo<>(instance);
096    }
097    return retval;
098  }
099
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}