001/*
002 * SPDX-FileCopyrightText: none
003 * SPDX-License-Identifier: CC0-1.0
004 */
005
006package dev.metaschema.databind.model;
007
008import java.lang.reflect.Field;
009import java.lang.reflect.ParameterizedType;
010import java.lang.reflect.Type;
011import java.util.Collection;
012import java.util.List;
013import java.util.Map;
014
015import dev.metaschema.core.model.IBoundObject;
016import dev.metaschema.core.model.IModelInstanceAbsolute;
017import dev.metaschema.core.util.ObjectUtils;
018import dev.metaschema.databind.io.BindingException;
019import dev.metaschema.databind.model.info.IModelInstanceCollectionInfo;
020import edu.umd.cs.findbugs.annotations.NonNull;
021import edu.umd.cs.findbugs.annotations.Nullable;
022
023/**
024 * Represents an assembly or field instance bound to Java data.
025 *
026 * @param <ITEM>
027 *          the Java type for associated bound objects
028 */
029public interface IBoundInstanceModel<ITEM>
030    extends IBoundInstance<ITEM>, IModelInstanceAbsolute {
031  /**
032   * Get the collection Java item type for the Java field associated with this
033   * instance.
034   *
035   * @param field
036   *          the Java field for the instance
037   * @return the Java item type
038   */
039  @NonNull
040  static Class<?> getItemType(@NonNull Field field) {
041    Type fieldType = field.getGenericType();
042    Class<?> rawType = ObjectUtils.notNull(
043        (Class<?>) (fieldType instanceof ParameterizedType ? ((ParameterizedType) fieldType).getRawType() : fieldType));
044
045    Class<?> itemType;
046    if (Map.class.isAssignableFrom(rawType)) {
047      // this is a Map so the second generic type is the value
048      itemType = ObjectUtils.notNull((Class<?>) ((ParameterizedType) fieldType).getActualTypeArguments()[1]);
049    } else if (List.class.isAssignableFrom(rawType)) {
050      // this is a List so there is only a single generic type
051      itemType = ObjectUtils.notNull((Class<?>) ((ParameterizedType) fieldType).getActualTypeArguments()[0]);
052    } else {
053      // non-collection
054      itemType = rawType;
055    }
056    return itemType;
057  }
058
059  /**
060   * Get the collection Java item type for the Java field associated with this
061   * instance.
062   *
063   * @param <TYPE>
064   *          the item's expected Java type
065   * @param field
066   *          the Java field for the instance
067   * @param expectedItemType
068   *          the item's expected Java type, which may be a super type of the
069   *          item's type
070   * @return the Java item type
071   */
072  @NonNull
073  static <TYPE> Class<? extends TYPE> getItemType(@NonNull Field field, @NonNull Class<TYPE> expectedItemType) {
074    Type fieldType = field.getGenericType();
075    Class<?> rawType = ObjectUtils.notNull(
076        (Class<?>) (fieldType instanceof ParameterizedType ? ((ParameterizedType) fieldType).getRawType() : fieldType));
077
078    Class<?> itemType;
079    if (Map.class.isAssignableFrom(rawType)) {
080      // this is a Map so the second generic type is the value
081      itemType = ObjectUtils.notNull((Class<?>) ((ParameterizedType) fieldType).getActualTypeArguments()[1]);
082    } else if (List.class.isAssignableFrom(rawType)) {
083      // this is a List so there is only a single generic type
084      itemType = ObjectUtils.notNull((Class<?>) ((ParameterizedType) fieldType).getActualTypeArguments()[0]);
085    } else {
086      // non-collection
087      itemType = rawType;
088    }
089    return ObjectUtils.notNull(itemType.asSubclass(expectedItemType));
090  }
091
092  @Override
093  @NonNull
094  IBoundDefinitionModelAssembly getContainingDefinition();
095
096  @Override
097  default Object getResolvedDefaultValue() {
098    return getMaxOccurs() == 1 ? getEffectiveDefaultValue() : getCollectionInfo().emptyValue();
099  }
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}