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.core.datatype;
028
029import com.fasterxml.jackson.core.JsonGenerator;
030import com.fasterxml.jackson.core.JsonParser;
031import com.fasterxml.jackson.databind.jsonFormatVisitors.JsonFormatTypes;
032
033import gov.nist.secauto.metaschema.core.metapath.function.InvalidValueForCastFunctionException;
034import gov.nist.secauto.metaschema.core.metapath.item.atomic.IAnyAtomicItem;
035import gov.nist.secauto.metaschema.core.util.ObjectUtils;
036
037import org.codehaus.stax2.XMLEventReader2;
038import org.codehaus.stax2.XMLStreamWriter2;
039import org.codehaus.stax2.evt.XMLEventFactory2;
040
041import java.io.IOException;
042import java.lang.reflect.Field;
043import java.util.List;
044import java.util.function.Supplier;
045
046import javax.xml.namespace.QName;
047import javax.xml.stream.XMLEventWriter;
048import javax.xml.stream.XMLStreamException;
049import javax.xml.stream.events.StartElement;
050import javax.xml.stream.events.XMLEvent;
051
052import edu.umd.cs.findbugs.annotations.NonNull;
053
054public interface IDataTypeAdapter<TYPE> {
055
056  /**
057   * Get the metaschema type names associated with this adapter. This name must be
058   * unique with respect to all other metaschema types.
059   * <p>
060   * At least one name must be provided, with the first name being the most
061   * preferred name.
062   *
063   * @return the name
064   */
065  @NonNull
066  List<String> getNames();
067
068  /**
069   * The JSON primative type of the data type.
070   *
071   * @return the JSON data type
072   */
073  JsonFormatTypes getJsonRawType();
074
075  /**
076   * Get the most preferred name for this data type.
077   *
078   * @return the name
079   */
080  @NonNull
081  default String getPreferredName() {
082    return ObjectUtils.notNull(getNames().iterator().next());
083  }
084
085  /**
086   * Get the Java class supported by this adapter.
087   *
088   * @return the Java class
089   */
090  @NonNull
091  Class<TYPE> getJavaClass();
092
093  /**
094   * Casts the provided value to the type associated with this adapter.
095   *
096   * @param value
097   *          a value of the provided type
098   * @return the typed value
099   */
100  @NonNull
101  TYPE toValue(@NonNull Object value);
102
103  /**
104   * Gets the value as a string suitable for writing as text. This is intended for
105   * data types that have a simple string-based structure in XML and JSON, such as
106   * for XML attributes or JSON keys. An adapter for a complex data structures
107   * that consist of XML elements will throw an
108   * {@link UnsupportedOperationException} when this is called.
109   *
110   * @param value
111   *          the data to formatted as a string
112   * @return a string
113   * @throws UnsupportedOperationException
114   *           if the data type cannot be represented as a string
115   */
116  @NonNull
117  String asString(@NonNull Object value);
118
119  /**
120   * Create a copy of the provided value.
121   *
122   * @param obj
123   *          the value to copy
124   * @return the copy
125   */
126  @NonNull
127  TYPE copy(@NonNull Object obj);
128
129  /**
130   * Determines if the data type is an atomic, scalar value. Complex structures
131   * such as Markup are not considered atomic.
132   *
133   * @return {@code true} if the data type is an atomic scalar value, or
134   *         {@code false} otherwise
135   */
136  default boolean isAtomic() {
137    return true;
138  }
139
140  /**
141   * Get the java type of the associated item.
142   *
143   * @return the java associated item type
144   */
145  @NonNull
146  Class<? extends IAnyAtomicItem> getItemClass();
147
148  /**
149   * Construct a new item of this type using the provided value.
150   *
151   * @param value
152   *          the item's value
153   * @return a new item
154   */
155  // TODO: markup types are not atomic values. Figure out a better base type
156  // (i.e., IValuedItem)
157  @NonNull
158  IAnyAtomicItem newItem(@NonNull Object value);
159
160  /**
161   * Cast the provided item to an item of this type, if possible.
162   *
163   * @param item
164   *          the atomic item to cast
165   * @return an atomic item of this type
166   * @throws InvalidValueForCastFunctionException
167   *           if the provided item type cannot be cast to this item type
168   */
169  @NonNull
170  IAnyAtomicItem cast(IAnyAtomicItem item);
171
172  /**
173   * Indicates if the adapter will parse the {@link XMLEvent#START_ELEMENT} before
174   * parsing the value data.
175   *
176   * @return {@code true} if the adapter requires the start element for parsing,
177   *         or {@code false} otherwise
178   */
179  // TODO; implement or remove this
180  boolean isParsingStartElement();
181
182  /**
183   * Determines if adapter can parse the next element. The next element's
184   * {@link QName} is provided for this purpose.
185   * <p>
186   * This will be called when the parser encounter's an element it does not
187   * recognize. This gives the adapter a chance to request parsing of the data.
188   *
189   * @param nextElementQName
190   *          the next element's namespace-qualified name
191   * @return {@code true} if the adapter will parse the element, or {@code false}
192   *         otherwise
193   */
194  // TODO: implement this
195  boolean canHandleQName(@NonNull QName nextElementQName);
196
197  /**
198   * Parses a provided string. Used to parse XML attributes, simple XML character
199   * data, and JSON/YAML property values.
200   *
201   * @param value
202   *          the string value to parse
203   * @return the parsed data as the adapter's type
204   * @throws IllegalArgumentException
205   *           if the data is not valid to the data type
206   */
207  @NonNull
208  TYPE parse(@NonNull String value);
209
210  /**
211   * This method is expected to parse content starting at the next event. Parsing
212   * will continue until the next event represents content that is not handled by
213   * this adapter. This means the event stream should be positioned after any
214   * {@link XMLEvent#END_ELEMENT} that corresponds to an
215   * {@link XMLEvent#START_ELEMENT} parsed by this adapter.
216   * <p>
217   * If {@link #isParsingStartElement()} returns {@code true}, then the first
218   * event to parse will be the {@link XMLEvent#START_ELEMENT} for the element
219   * that contains the value data, then the value data. If this is the case, this
220   * method must also parse the corresponding {@link XMLEvent#END_ELEMENT}.
221   * Otherwise, the first event to parse will be the value data.
222   * <p>
223   * The value data is expected to be parsed completely, leaving the event stream
224   * on a peeked event corresponding to content that is not handled by this
225   * method.
226   *
227   * @param eventReader
228   *          the XML parser used to read the parsed value
229   * @return the parsed value
230   * @throws IOException
231   *           if a parsing error occurs
232   */
233  @NonNull
234  TYPE parse(@NonNull XMLEventReader2 eventReader) throws IOException;
235
236  /**
237   * Parses a JSON property value.
238   *
239   * @param parser
240   *          the JSON parser used to read the parsed value
241   * @return the parsed value
242   * @throws IOException
243   *           if a parsing error occurs
244   */
245  @NonNull
246  TYPE parse(@NonNull JsonParser parser) throws IOException;
247
248  /**
249   * Parses a provided string using {@link #parse(String)}.
250   * <p>
251   * This method may pre-parse the data and then return copies, since the data can
252   * only be parsed once, but the supplier might be called multiple times.
253   *
254   * @param value
255   *          the string value to parse
256   * @return a supplier that will provide new instances of the parsed data
257   * @throws IOException
258   *           if an error occurs while parsing
259   * @see #parse(String)
260   */
261  @NonNull
262  default Supplier<TYPE> parseAndSupply(@NonNull String value) throws IOException {
263    TYPE retval = parse(value);
264    return () -> copy(retval);
265  }
266
267  /**
268   * Parses a provided string using
269   * {@link IDataTypeAdapter#parse(XMLEventReader2)}.
270   * <p>
271   * This method may pre-parse the data and then return copies, since the data can
272   * only be parsed once, but the supplier might be called multiple times.
273   *
274   * @param eventReader
275   *          the XML parser used to read the parsed value
276   * @return a supplier that will provide new instances of the parsed data
277   * @throws IOException
278   *           if an error occurs while parsing
279   * @see #parse(String)
280   * @see #parse(XMLEventReader2)
281   */
282  @NonNull
283  default Supplier<TYPE> parseAndSupply(@NonNull XMLEventReader2 eventReader) throws IOException {
284    TYPE retval = parse(eventReader);
285    return () -> copy(retval);
286  }
287
288  /**
289   * Parses a provided string using {@link #parse(JsonParser)}.
290   * <p>
291   * This method may pre-parse the data and then return copies, since the data can
292   * only be parsed once, but the supplier might be called multiple times.
293   *
294   * @param parser
295   *          the JSON parser used to read the parsed value
296   * @return a supplier that will provide new instances of the parsed data
297   * @throws IOException
298   *           if an error occurs while parsing
299   * @see #parse(String)
300   * @see #parse(JsonParser)
301   */
302  @NonNull
303  default Supplier<TYPE> parseAndSupply(@NonNull JsonParser parser) throws IOException {
304    TYPE retval = parse(parser);
305    return () -> copy(retval);
306  }
307
308  /**
309   * Writes the provided Java class instance data as XML. The parent element
310   * information is provided as a {@link StartElement} event, which allows
311   * namespace information to be obtained from the parent element using the
312   * {@link StartElement#getName()} and {@link StartElement#getNamespaceContext()}
313   * methods, which can be used when writing the provided instance value.
314   *
315   * @param instance
316   *          the {@link Field} instance value to write
317   * @param parent
318   *          the {@link StartElement} XML event that is the parent of the data to
319   *          write
320   * @param eventFactory
321   *          the XML event factory used to generate XML writing events
322   * @param eventWriter
323   *          the XML writer used to output XML as events
324   * @throws XMLStreamException
325   *           if an unexpected error occurred while processing the XML output
326   * @throws IOException
327   *           if an unexpected error occurred while writing to the output stream
328   */
329  void writeXmlValue(@NonNull Object instance, @NonNull StartElement parent, @NonNull XMLEventFactory2 eventFactory,
330      @NonNull XMLEventWriter eventWriter)
331      throws IOException, XMLStreamException;
332
333  /**
334   * Writes the provided Java class instance data as XML. The parent element
335   * information is provided as an XML {@link QName}, which allows namespace
336   * information to be obtained from the parent element. Additional namespace
337   * information can be gathered using the
338   * {@link XMLStreamWriter2#getNamespaceContext()} method, which can be used when
339   * writing the provided instance value.
340   *
341   * @param instance
342   *          the {@link Field} instance value to write
343   * @param parentName
344   *          the qualified name of the XML data's parent element
345   * @param writer
346   *          the XML writer used to output the XML data
347   * @throws XMLStreamException
348   *           if an unexpected error occurred while processing the XML output
349   */
350  void writeXmlValue(@NonNull Object instance, @NonNull QName parentName, @NonNull XMLStreamWriter2 writer)
351      throws XMLStreamException;
352
353  /**
354   * Writes the provided Java class instance as a JSON/YAML field value.
355   *
356   * @param instance
357   *          the {@link Field} instance value to write
358   * @param writer
359   *          the JSON/YAML writer used to output the JSON/YAML data
360   * @throws IOException
361   *           if an unexpected error occurred while writing the JSON/YAML output
362   */
363  void writeJsonValue(@NonNull Object instance, @NonNull JsonGenerator writer) throws IOException;
364
365  /**
366   * Gets the default value to use as the JSON/YAML field name for a Metaschema
367   * field value if no JSON value key flag or name is configured.
368   *
369   * @return the default field name to use
370   */
371  @NonNull
372  String getDefaultJsonValueKey();
373
374  /**
375   * Determines if the data type's value is allowed to be unwrapped in XML when
376   * the value is a field value.
377   *
378   * @return {@code true} if allowed, or {@code false} otherwise.
379   */
380  boolean isUnrappedValueAllowedInXml();
381
382  /**
383   * Determines if the datatype uses mixed text and element content in XML.
384   *
385   * @return {@code true} if the datatype uses mixed text and element content in
386   *         XML, or {@code false} otherwise
387   */
388  boolean isXmlMixed();
389}