001/*
002 * SPDX-FileCopyrightText: none
003 * SPDX-License-Identifier: CC0-1.0
004 */
005
006package gov.nist.secauto.metaschema.databind.io;
007
008import gov.nist.secauto.metaschema.core.configuration.IConfiguration;
009import gov.nist.secauto.metaschema.core.configuration.IMutableConfiguration;
010import gov.nist.secauto.metaschema.core.metapath.IDocumentLoader;
011import gov.nist.secauto.metaschema.core.metapath.item.node.IDocumentNodeItem;
012import gov.nist.secauto.metaschema.core.model.IBoundObject;
013import gov.nist.secauto.metaschema.core.util.ObjectUtils;
014import gov.nist.secauto.metaschema.databind.DefaultBindingContext;
015import gov.nist.secauto.metaschema.databind.IBindingContext;
016
017import org.eclipse.jdt.annotation.Owning;
018import org.xml.sax.InputSource;
019
020import java.io.File;
021import java.io.FileNotFoundException;
022import java.io.IOException;
023import java.io.InputStream;
024import java.io.OutputStream;
025import java.io.Writer;
026import java.net.URI;
027import java.net.URISyntaxException;
028import java.net.URL;
029import java.nio.file.Path;
030
031import edu.umd.cs.findbugs.annotations.NonNull;
032
033/**
034 * A common interface for loading Module based instance resources.
035 */
036public interface IBoundLoader extends IDocumentLoader, IMutableConfiguration<DeserializationFeature<?>> {
037
038  @Override
039  default IBoundLoader enableFeature(DeserializationFeature<?> feature) {
040    return set(feature, true);
041  }
042
043  @Override
044  default IBoundLoader disableFeature(DeserializationFeature<?> feature) {
045    return set(feature, false);
046  }
047
048  @Override
049  IBoundLoader applyConfiguration(IConfiguration<DeserializationFeature<?>> other);
050
051  @Override
052  IBoundLoader set(DeserializationFeature<?> feature, Object value);
053
054  /**
055   * Determine the format of the provided resource.
056   *
057   * @param file
058   *          the resource
059   * @return the format information for the provided resource
060   * @throws IOException
061   *           if an error occurred while reading the resource
062   */
063  @NonNull
064  default Format detectFormat(@NonNull File file) throws IOException {
065    return detectFormat(ObjectUtils.notNull(file.toPath()));
066  }
067
068  /**
069   * Determine the format of the provided resource.
070   *
071   * @param path
072   *          the resource
073   * @return the format information for the provided resource
074   * @throws IOException
075   *           if an error occurred while reading the resource
076   */
077  @NonNull
078  default Format detectFormat(@NonNull Path path) throws IOException {
079    return detectFormat(ObjectUtils.notNull(path.toUri()));
080  }
081
082  /**
083   * Determine the format of the provided resource.
084   *
085   * @param url
086   *          the resource
087   * @return the format information for the provided resource
088   * @throws IOException
089   *           if an error occurred while reading the resource
090   */
091  @NonNull
092  default Format detectFormat(@NonNull URL url) throws IOException {
093    try {
094      return detectFormat(ObjectUtils.notNull(url.toURI()));
095    } catch (URISyntaxException ex) {
096      throw new IOException(ex);
097    }
098  }
099
100  /**
101   * Determine the format of the resource identified by the provided {@code uri}.
102   *
103   * @param uri
104   *          the resource
105   * @return the format information for the provided resource
106   * @throws IOException
107   *           if an error occurred while reading the resource
108   */
109  @NonNull
110  Format detectFormat(@NonNull URI uri) throws IOException;
111
112  /**
113   * Determine the format of the provided resource.
114   * <p>
115   * This method will consume data from the provided {@link InputStream}. If the
116   * caller of this method intends to read data from the stream after determining
117   * the format, the caller should pass in a stream that can be reset.
118   * <p>
119   * This method will not close the provided {@link InputStream}, since it does
120   * not own the stream.
121   *
122   * @param is
123   *          an input stream for the resource
124   * @return the format information for the provided resource
125   * @throws IOException
126   *           if an error occurred while reading the resource
127   */
128  @NonNull
129  FormatDetector.Result detectFormat(@NonNull InputStream is) throws IOException;
130
131  /**
132   * Determine the model of the provided resource.
133   * <p>
134   * This method will consume data from any {@link InputStream} provided by the
135   * {@link InputSource}. If the caller of this method intends to read data from
136   * the stream after determining the format, the caller should pass in a stream
137   * that can be reset.
138   * <p>
139   * This method will not close any {@link InputStream} provided by the
140   * {@link InputSource}, since it does not own the stream.
141   *
142   * @param is
143   *          an input stream for the resource
144   * @param format
145   *          the format of the provided resource
146   * @return the model of the provided resource
147   * @throws IOException
148   *           if an error occurred while reading the resource
149   */
150  @NonNull
151  @Owning
152  ModelDetector.Result detectModel(@NonNull InputStream is, @NonNull Format format) throws IOException;
153
154  /**
155   * Load data from the provided resource into a bound object.
156   * <p>
157   * This method will auto-detect the format of the provided resource.
158   *
159   * @param <CLASS>
160   *          the type of the bound object to return
161   * @param file
162   *          the resource
163   * @return a bound object containing the loaded data
164   * @throws IOException
165   *           if an error occurred while reading the resource
166   * @see #detectFormat(File)
167   */
168  @NonNull
169  default <CLASS extends IBoundObject> CLASS load(@NonNull File file) throws IOException {
170    return load(ObjectUtils.notNull(file.toPath()));
171  }
172
173  /**
174   * Load data from the provided resource into a bound object.
175   * <p>
176   * This method will auto-detect the format of the provided resource.
177   *
178   * @param <CLASS>
179   *          the type of the bound object to return
180   * @param path
181   *          the resource
182   * @return a bound object containing the loaded data
183   * @throws IOException
184   *           if an error occurred while reading the resource
185   * @see #detectFormat(File)
186   */
187  @NonNull
188  default <CLASS extends IBoundObject> CLASS load(@NonNull Path path) throws IOException {
189    return load(ObjectUtils.notNull(path.toUri()));
190  }
191
192  /**
193   * Load data from the provided resource into a bound object.
194   * <p>
195   * This method will auto-detect the format of the provided resource.
196   *
197   * @param <CLASS>
198   *          the type of the bound object to return
199   * @param url
200   *          the resource
201   * @return a bound object containing the loaded data
202   * @throws IOException
203   *           if an error occurred while reading the resource
204   * @throws URISyntaxException
205   *           if the provided {@code url} is malformed
206   * @see #detectFormat(URL)
207   */
208  @NonNull
209  default <CLASS extends IBoundObject> CLASS load(@NonNull URL url) throws IOException, URISyntaxException {
210    return load(ObjectUtils.notNull(url.toURI()));
211  }
212
213  /**
214   * Load data from the resource identified by the provided {@code uri} into a
215   * bound object.
216   * <p>
217   * This method will auto-detect the format of the provided resource.
218   *
219   * @param <CLASS>
220   *          the type of the bound object to return
221   * @param uri
222   *          the resource
223   * @return a bound object containing the loaded data
224   * @throws IOException
225   *           if an error occurred while reading the resource
226   * @see #detectFormat(URL)
227   */
228  @NonNull
229  <CLASS extends IBoundObject> CLASS load(@NonNull URI uri) throws IOException;
230
231  /**
232   * Load data from the provided resource into a bound object.
233   * <p>
234   * This method should auto-detect the format of the provided resource.
235   * <p>
236   * This method will not close the provided {@link InputStream}, since it does
237   * not own the stream.
238   *
239   * @param <CLASS>
240   *          the type of the bound object to return
241   * @param is
242   *          the resource stream
243   * @param documentUri
244   *          the URI of the resource
245   * @return a bound object containing the loaded data
246   * @throws IOException
247   *           if an error occurred while reading the resource
248   * @see #detectFormat(InputStream)
249   */
250  @NonNull
251  <CLASS extends IBoundObject> CLASS load(@NonNull InputStream is, @NonNull URI documentUri) throws IOException;
252
253  /**
254   * Load data from the specified resource into a bound object with the type of
255   * the specified Java class.
256   *
257   * @param <CLASS>
258   *          the Java type to load data into
259   * @param clazz
260   *          the class for the java type
261   * @param file
262   *          the resource to load
263   * @return the loaded instance data
264   * @throws IOException
265   *           if an error occurred while loading the data in the specified file
266   */
267  @NonNull
268  default <CLASS extends IBoundObject> CLASS load(
269      @NonNull Class<CLASS> clazz,
270      @NonNull File file) throws IOException {
271    return load(clazz, ObjectUtils.notNull(file.toPath()));
272  }
273
274  /**
275   * Load data from the specified resource into a bound object with the type of
276   * the specified Java class.
277   *
278   * @param <CLASS>
279   *          the Java type to load data into
280   * @param clazz
281   *          the class for the java type
282   * @param path
283   *          the resource to load
284   * @return the loaded instance data
285   * @throws IOException
286   *           if an error occurred while loading the data in the specified file
287   */
288  @NonNull
289  default <CLASS extends IBoundObject> CLASS load(
290      @NonNull Class<CLASS> clazz,
291      @NonNull Path path) throws IOException {
292    return load(clazz, ObjectUtils.notNull(path.toUri()));
293  }
294
295  /**
296   * Load data from the specified resource into a bound object with the type of
297   * the specified Java class.
298   *
299   * @param <CLASS>
300   *          the Java type to load data into
301   * @param clazz
302   *          the class for the java type
303   * @param url
304   *          the resource to load
305   * @return the loaded instance data
306   * @throws IOException
307   *           if an error occurred while loading the data in the specified file
308   * @throws URISyntaxException
309   *           if the provided {@code url} is malformed
310   */
311  @NonNull
312  default <CLASS extends IBoundObject> CLASS load(
313      @NonNull Class<CLASS> clazz,
314      @NonNull URL url) throws IOException, URISyntaxException {
315    return load(clazz, ObjectUtils.notNull(url.toURI()));
316  }
317
318  /**
319   * Load data from the specified resource into a bound object with the type of
320   * the specified Java class.
321   *
322   * @param <CLASS>
323   *          the Java type to load data into
324   * @param clazz
325   *          the class for the java type
326   * @param uri
327   *          the resource to load
328   * @return the loaded instance data
329   * @throws IOException
330   *           if an error occurred while loading the data in the specified file
331   */
332  @NonNull
333  <CLASS extends IBoundObject> CLASS load(
334      @NonNull Class<CLASS> clazz,
335      @NonNull URI uri) throws IOException;
336
337  /**
338   * Load data from the specified resource into a bound object with the type of
339   * the specified Java class.
340   * <p>
341   * This method will not close the provided {@link InputStream}, since it does
342   * not own the stream.
343   * <p>
344   * Implementations of this method will do format detection. This process might
345   * leave the provided {@link InputStream} at a position beyond the last parsed
346   * location. If you want to avoid this possibility, use and implementation of
347   * {@link IDeserializer#deserialize(InputStream, URI)} instead, such as what is
348   * provided by {@link DefaultBindingContext#newDeserializer(Format, Class)}.
349   *
350   * @param <CLASS>
351   *          the Java type to load data into
352   * @param clazz
353   *          the class for the java type
354   * @param is
355   *          the resource stream
356   * @param documentUri
357   *          the URI of the resource
358   * @return the loaded data
359   * @throws IOException
360   *           if an error occurred while loading the data from the specified
361   *           resource
362   */
363  @NonNull
364  <CLASS extends IBoundObject> CLASS load(
365      @NonNull Class<CLASS> clazz,
366      @NonNull InputStream is,
367      @NonNull URI documentUri) throws IOException;
368
369  /**
370   * Load data from the specified resource into a bound object with the type of
371   * the specified Java class.
372   * <p>
373   * This method will not close the provided {@link InputStream}, since it does
374   * not own the stream.
375   *
376   * @param <CLASS>
377   *          the Java type to load data into
378   * @param format
379   *          the format to parse
380   * @param clazz
381   *          the class for the java type
382   * @param is
383   *          the resource stream
384   * @param documentUri
385   *          the URI of the resource
386   * @return the loaded data
387   * @throws IOException
388   *           if an error occurred while loading the data from the specified
389   *           resource
390   */
391  @NonNull
392  <CLASS extends IBoundObject> CLASS load(
393      @NonNull Class<CLASS> clazz,
394      @NonNull Format format,
395      @NonNull InputStream is,
396      @NonNull URI documentUri) throws IOException;
397
398  /**
399   * Load data expressed using the provided {@code format} and return that data as
400   * a Metapath node item.
401   * <p>
402   * The specific Module model is auto-detected by analyzing the source. The class
403   * reported is implementation specific.
404   *
405   * @param format
406   *          the expected format of the data to parse
407   * @param path
408   *          the resource
409   * @return the Metapath node item for the parsed data
410   * @throws IOException
411   *           if an error occurred while loading the data from the specified
412   *           resource
413   */
414  @NonNull
415  default IDocumentNodeItem loadAsNodeItem(
416      @NonNull Format format,
417      @NonNull Path path) throws IOException {
418    return loadAsNodeItem(format, ObjectUtils.notNull(path.toUri()));
419  }
420
421  /**
422   * Load data expressed using the provided {@code format} and return that data as
423   * a Metapath node item.
424   * <p>
425   * The specific Module model is auto-detected by analyzing the source. The class
426   * reported is implementation specific.
427   *
428   * @param format
429   *          the expected format of the data to parse
430   * @param uri
431   *          the resource
432   * @return the Metapath node item for the parsed data
433   * @throws IOException
434   *           if an error occurred while loading the data from the specified
435   *           resource
436   */
437  @NonNull
438  IDocumentNodeItem loadAsNodeItem(
439      @NonNull Format format,
440      @NonNull URI uri) throws IOException;
441
442  /**
443   * Load data expressed using the provided {@code format} and return that data as
444   * a Metapath node item.
445   * <p>
446   * The specific Module model is auto-detected by analyzing the source. The class
447   * reported is implementation specific.
448   *
449   * @param format
450   *          the expected format of the data to parse
451   * @param is
452   *          the resource stream
453   * @param documentUri
454   *          the URI of the resource
455   * @return the Metapath node item for the parsed data
456   * @throws IOException
457   *           if an error occurred while loading the data from the specified
458   *           resource
459   */
460  @NonNull
461  IDocumentNodeItem loadAsNodeItem(
462      @NonNull Format format,
463      @NonNull InputStream is,
464      @NonNull URI documentUri) throws IOException;
465
466  /**
467   * Get the configured Module binding context to use to load Java types.
468   *
469   * @return the binding context
470   */
471  @NonNull
472  IBindingContext getBindingContext();
473
474  /**
475   * Auto convert the provided {@code source} to the provided {@code toFormat}.
476   * Write the converted content to the provided {@code destination}.
477   * <p>
478   * The format of the source is expected to be auto detected using
479   * {@link #detectFormat(Path)}.
480   *
481   * @param <CLASS>
482   *          the Java type to load data into
483   * @param source
484   *          the resource to convert
485   * @param destination
486   *          the resource to write converted content to
487   * @param toFormat
488   *          the format to convert to
489   * @param rootClass
490   *          the class for the Java type to load data into
491   * @throws FileNotFoundException
492   *           the the provided source file was not found
493   * @throws IOException
494   *           if an error occurred while loading the data from the specified
495   *           resource or writing the converted data to the specified destination
496   */
497  default <CLASS extends IBoundObject> void convert(
498      @NonNull Path source,
499      @NonNull Path destination,
500      @NonNull Format toFormat,
501      @NonNull Class<CLASS> rootClass) throws FileNotFoundException, IOException {
502    CLASS object = load(rootClass, source);
503
504    ISerializer<CLASS> serializer = getBindingContext().newSerializer(toFormat, rootClass);
505    serializer.serialize(object, destination);
506  }
507
508  /**
509   * Auto convert the provided {@code source} to the provided {@code toFormat}.
510   * Write the converted content to the provided {@code destination}.
511   * <p>
512   * The format of the source is expected to be auto detected using
513   * {@link #detectFormat(Path)}.
514   *
515   * @param <CLASS>
516   *          the Java type to load data into
517   * @param source
518   *          the resource to convert
519   * @param os
520   *          the output stream to write converted content to
521   * @param toFormat
522   *          the format to convert to
523   * @param rootClass
524   *          the class for the Java type to load data into
525   * @throws FileNotFoundException
526   *           the the provided source file was not found
527   * @throws IOException
528   *           if an error occurred while loading the data from the specified
529   *           resource or writing the converted data to the specified destination
530   */
531  default <CLASS extends IBoundObject> void convert(
532      @NonNull Path source,
533      @NonNull OutputStream os,
534      @NonNull Format toFormat,
535      @NonNull Class<CLASS> rootClass) throws FileNotFoundException, IOException {
536    CLASS object = load(rootClass, source);
537
538    ISerializer<CLASS> serializer = getBindingContext().newSerializer(toFormat, rootClass);
539    serializer.serialize(object, os);
540  }
541
542  /**
543   * Auto convert the provided {@code source} to the provided {@code toFormat}.
544   * Write the converted content to the provided {@code destination}.
545   * <p>
546   * The format of the source is expected to be auto detected using
547   * {@link #detectFormat(Path)}.
548   *
549   * @param <CLASS>
550   *          the Java type to load data into
551   * @param source
552   *          the resource to convert
553   * @param destination
554   *          the resource to write converted content to
555   * @param toFormat
556   *          the format to convert to
557   * @param rootClass
558   *          the class for the Java type to load data into
559   * @throws FileNotFoundException
560   *           the the provided source file was not found
561   * @throws IOException
562   *           if an error occurred while loading the data from the specified
563   *           resource or writing the converted data to the specified destination
564   */
565  default <CLASS extends IBoundObject> void convert(
566      @NonNull URI source,
567      @NonNull Path destination,
568      @NonNull Format toFormat,
569      @NonNull Class<CLASS> rootClass) throws FileNotFoundException, IOException {
570    CLASS object = load(rootClass, source);
571
572    ISerializer<CLASS> serializer = getBindingContext().newSerializer(toFormat, rootClass);
573    serializer.serialize(object, destination);
574  }
575
576  /**
577   * Auto convert the provided {@code source} to the provided {@code toFormat}.
578   * Write the converted content to the provided {@code destination}.
579   * <p>
580   * The format of the source is expected to be auto detected using
581   * {@link #detectFormat(Path)}.
582   *
583   * @param <CLASS>
584   *          the Java type to load data into
585   * @param source
586   *          the resource to convert
587   * @param os
588   *          the output stream to write converted content to
589   * @param toFormat
590   *          the format to convert to
591   * @param rootClass
592   *          the class for the Java type to load data into
593   * @throws FileNotFoundException
594   *           the the provided source file was not found
595   * @throws IOException
596   *           if an error occurred while loading the data from the specified
597   *           resource or writing the converted data to the specified destination
598   */
599  default <CLASS extends IBoundObject> void convert(
600      @NonNull URI source,
601      @NonNull OutputStream os,
602      @NonNull Format toFormat,
603      @NonNull Class<CLASS> rootClass) throws FileNotFoundException, IOException {
604    CLASS object = load(rootClass, source);
605
606    ISerializer<CLASS> serializer = getBindingContext().newSerializer(toFormat, rootClass);
607    serializer.serialize(object, os);
608  }
609
610  /**
611   * Auto convert the provided {@code source} to the provided {@code toFormat}.
612   * Write the converted content to the provided {@code destination}.
613   * <p>
614   * The format of the source is expected to be auto detected using
615   * {@link #detectFormat(Path)}.
616   *
617   * @param <CLASS>
618   *          the Java type to load data into
619   * @param source
620   *          the resource to convert
621   * @param writer
622   *          the writer to write converted content to
623   * @param toFormat
624   *          the format to convert to
625   * @param rootClass
626   *          the class for the Java type to load data into
627   * @throws FileNotFoundException
628   *           the the provided source file was not found
629   * @throws IOException
630   *           if an error occurred while loading the data from the specified
631   *           resource or writing the converted data to the specified destination
632   */
633  default <CLASS extends IBoundObject> void convert(
634      @NonNull URI source,
635      @NonNull Writer writer,
636      @NonNull Format toFormat,
637      @NonNull Class<CLASS> rootClass) throws FileNotFoundException, IOException {
638    CLASS object = load(rootClass, source);
639
640    ISerializer<CLASS> serializer = getBindingContext().newSerializer(toFormat, rootClass);
641    serializer.serialize(object, writer);
642  }
643}