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}