001/* 002 * Portions of this software was developed by employees of the National Institute 003 * of Standards and Technology (NIST), an agency of the Federal Government and is 004 * being made available as a public service. Pursuant to title 17 United States 005 * Code Section 105, works of NIST employees are not subject to copyright 006 * protection in the United States. This software may be subject to foreign 007 * copyright. Permission in the United States and in foreign countries, to the 008 * extent that NIST may hold copyright, to use, copy, modify, create derivative 009 * works, and distribute this software and its documentation without fee is hereby 010 * granted on a non-exclusive basis, provided that this notice and disclaimer 011 * of warranty appears in all copies. 012 * 013 * THE SOFTWARE IS PROVIDED 'AS IS' WITHOUT ANY WARRANTY OF ANY KIND, EITHER 014 * EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT LIMITED TO, ANY WARRANTY 015 * THAT THE SOFTWARE WILL CONFORM TO SPECIFICATIONS, ANY IMPLIED WARRANTIES OF 016 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND FREEDOM FROM 017 * INFRINGEMENT, AND ANY WARRANTY THAT THE DOCUMENTATION WILL CONFORM TO THE 018 * SOFTWARE, OR ANY WARRANTY THAT THE SOFTWARE WILL BE ERROR FREE. IN NO EVENT 019 * SHALL NIST BE LIABLE FOR ANY DAMAGES, INCLUDING, BUT NOT LIMITED TO, DIRECT, 020 * INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES, ARISING OUT OF, RESULTING FROM, 021 * OR IN ANY WAY CONNECTED WITH THIS SOFTWARE, WHETHER OR NOT BASED UPON WARRANTY, 022 * CONTRACT, TORT, OR OTHERWISE, WHETHER OR NOT INJURY WAS SUSTAINED BY PERSONS OR 023 * PROPERTY OR OTHERWISE, AND WHETHER OR NOT LOSS WAS SUSTAINED FROM, OR AROSE OUT 024 * OF THE RESULTS OF, OR USE OF, THE SOFTWARE OR SERVICES PROVIDED HEREUNDER. 025 */ 026 027package gov.nist.secauto.metaschema.databind.model.info; 028 029import gov.nist.secauto.metaschema.core.model.JsonGroupAsBehavior; 030import gov.nist.secauto.metaschema.databind.io.BindingException; 031import gov.nist.secauto.metaschema.databind.io.json.IJsonParsingContext; 032import gov.nist.secauto.metaschema.databind.io.json.IJsonWritingContext; 033import gov.nist.secauto.metaschema.databind.io.xml.IXmlParsingContext; 034import gov.nist.secauto.metaschema.databind.io.xml.IXmlWritingContext; 035import gov.nist.secauto.metaschema.databind.model.IBoundNamedModelInstance; 036 037import java.io.IOException; 038import java.lang.reflect.Field; 039import java.lang.reflect.ParameterizedType; 040import java.lang.reflect.Type; 041import java.util.Collection; 042import java.util.List; 043import java.util.Map; 044 045import javax.xml.namespace.QName; 046import javax.xml.stream.XMLStreamException; 047import javax.xml.stream.events.StartElement; 048 049import edu.umd.cs.findbugs.annotations.NonNull; 050import edu.umd.cs.findbugs.annotations.Nullable; 051 052// TODO: make all read and write methods take the value, not the parent instance as an argument 053public interface IModelPropertyInfo { 054 055 @NonNull 056 static IModelPropertyInfo newPropertyInfo( 057 @NonNull IBoundNamedModelInstance instance) { 058 // create the property info 059 Type type = instance.getType(); 060 Field field = instance.getField(); 061 062 IModelPropertyInfo retval; 063 if (instance.getMaxOccurs() == -1 || instance.getMaxOccurs() > 1) { 064 // collection case 065 JsonGroupAsBehavior jsonGroupAs = instance.getJsonGroupAsBehavior(); 066 067 // expect a ParameterizedType 068 if (!(type instanceof ParameterizedType)) { 069 switch (jsonGroupAs) { 070 case KEYED: 071 throw new IllegalStateException( 072 String.format("The field '%s' on class '%s' has data type of '%s'," + " but should have a type of '%s'.", 073 field.getName(), instance.getParentClassBinding().getBoundClass().getName(), 074 field.getType().getName(), Map.class.getName())); 075 case LIST: 076 case SINGLETON_OR_LIST: 077 throw new IllegalStateException( 078 String.format("The field '%s' on class '%s' has data type of '%s'," + " but should have a type of '%s'.", 079 field.getName(), instance.getParentClassBinding().getBoundClass().getName(), 080 field.getType().getName(), List.class.getName())); 081 default: 082 // this should not occur 083 throw new IllegalStateException(jsonGroupAs.name()); 084 } 085 } 086 087 Class<?> rawType = (Class<?>) ((ParameterizedType) type).getRawType(); 088 if (JsonGroupAsBehavior.KEYED.equals(jsonGroupAs)) { 089 if (!Map.class.isAssignableFrom(rawType)) { 090 throw new IllegalArgumentException(String.format( 091 "The field '%s' on class '%s' has data type '%s', which is not the expected '%s' derived data type.", 092 field.getName(), 093 instance.getParentClassBinding().getBoundClass().getName(), 094 field.getType().getName(), 095 Map.class.getName())); 096 } 097 retval = new MapPropertyInfo(instance); 098 } else { 099 if (!List.class.isAssignableFrom(rawType)) { 100 throw new IllegalArgumentException(String.format( 101 "The field '%s' on class '%s' has data type '%s', which is not the expected '%s' derived data type.", 102 field.getName(), 103 instance.getParentClassBinding().getBoundClass().getName(), 104 field.getType().getName(), 105 List.class.getName())); 106 } 107 retval = new ListPropertyInfo(instance); 108 } 109 } else { 110 // single value case 111 if (type instanceof ParameterizedType) { 112 throw new IllegalStateException(String.format( 113 "The field '%s' on class '%s' has a data parmeterized type of '%s'," 114 + " but the occurance is not multi-valued.", 115 field.getName(), 116 instance.getParentClassBinding().getBoundClass().getName(), 117 field.getType().getName())); 118 } 119 retval = new SingletonPropertyInfo(instance); 120 } 121 return retval; 122 } 123 124 /** 125 * Get the associated property for which this info is for. 126 * 127 * @return the property 128 */ 129 @NonNull 130 IBoundNamedModelInstance getProperty(); 131 132 int getItemCount(@Nullable Object value); 133 134 /** 135 * Get the type of the bound object. 136 * 137 * @return the raw type of the bound object 138 */ 139 @NonNull 140 Class<?> getItemType(); 141 142 @NonNull 143 IPropertyCollector newPropertyCollector(); 144 145 default boolean isJsonKeyRequired() { 146 return false; 147 } 148 149 // TODO is the following needed? 150 /** 151 * Read the value data for the property. At the point that this is called, the 152 * parser must be located just after the property/field name has been parsed. 153 * This method will return a value based on the property's value type as 154 * reported by {@link #getProperty()}. 155 * 156 * @param collector 157 * used to hold parsed values 158 * @param context 159 * the JSON parsing context 160 * @param parentInstance 161 * the instance the property is on 162 * @throws IOException 163 * if there was an error when reading JSON data 164 */ 165 void readValues( 166 @NonNull IPropertyCollector collector, 167 @NonNull Object parentInstance, 168 @NonNull IJsonParsingContext context) 169 throws IOException; 170 171 boolean readValues( 172 @NonNull IPropertyCollector collector, 173 @NonNull Object parentInstance, 174 @NonNull StartElement start, 175 @NonNull IXmlParsingContext context) 176 throws IOException, XMLStreamException; 177 178 /** 179 * Write a {@code value} that is not {@code null}. 180 * 181 * @param value 182 * the value to write 183 * @param parentName 184 * the XML qualified name of the parent element to write 185 * @param context 186 * the XML serialization context 187 * @throws XMLStreamException 188 * if an error occurred while generating XML events 189 * @throws IOException 190 * if an error occurred while writing to the output stream 191 */ 192 void writeValues(@NonNull Object value, @NonNull QName parentName, @NonNull IXmlWritingContext context) 193 throws XMLStreamException, IOException; 194 195 void writeValues(@NonNull Object parentInstance, @NonNull IJsonWritingContext context) throws IOException; 196 197 boolean isValueSet(@NonNull Object parentInstance) throws IOException; 198 199 @NonNull 200 default Collection<? extends Object> getItemsFromParentInstance(@NonNull Object parentInstance) { 201 Object value = getProperty().getValue(parentInstance); 202 return getItemsFromValue(value); 203 } 204 205 @NonNull 206 Collection<? extends Object> getItemsFromValue(Object value); 207 208 void copy(@NonNull Object fromInstance, @NonNull Object toInstance, @NonNull IPropertyCollector collector) 209 throws BindingException; 210}