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}