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   * @param resource
125   *          the URI of the resource
126   * @return the format information for the provided resource
127   * @throws IOException
128   *           if an error occurred while reading the resource
129   */
130  @NonNull
131  FormatDetector.Result detectFormat(@NonNull InputStream is, @NonNull URI resource) throws IOException;
132
133  /**
134   * Determine the model of the provided resource.
135   * <p>
136   * This method will consume data from any {@link InputStream} provided by the
137   * {@link InputSource}. If the caller of this method intends to read data from
138   * the stream after determining the format, the caller should pass in a stream
139   * that can be reset.
140   * <p>
141   * This method will not close any {@link InputStream} provided by the
142   * {@link InputSource}, since it does not own the stream.
143   *
144   * @param is
145   *          an input stream for the resource
146   * @param resource
147   *          the URI of the resource
148   * @param format
149   *          the format of the provided resource
150   * @return the model of the provided resource
151   * @throws IOException
152   *           if an error occurred while reading the resource
153   */
154  @NonNull
155  @Owning
156  ModelDetector.Result detectModel(@NonNull InputStream is, @NonNull URI resource, @NonNull Format format)
157      throws IOException;
158
159  /**
160   * Load data from the provided resource into a bound object.
161   * <p>
162   * This method will auto-detect the format of the provided resource.
163   *
164   * @param <CLASS>
165   *          the type of the bound object to return
166   * @param file
167   *          the resource
168   * @return a bound object containing the loaded data
169   * @throws IOException
170   *           if an error occurred while reading the resource
171   * @see #detectFormat(File)
172   */
173  @NonNull
174  default <CLASS extends IBoundObject> CLASS load(@NonNull File file) throws IOException {
175    return load(ObjectUtils.notNull(file.toPath()));
176  }
177
178  /**
179   * Load data from the provided resource into a bound object.
180   * <p>
181   * This method will auto-detect the format of the provided resource.
182   *
183   * @param <CLASS>
184   *          the type of the bound object to return
185   * @param path
186   *          the resource
187   * @return a bound object containing the loaded data
188   * @throws IOException
189   *           if an error occurred while reading the resource
190   * @see #detectFormat(File)
191   */
192  @NonNull
193  default <CLASS extends IBoundObject> CLASS load(@NonNull Path path) throws IOException {
194    return load(ObjectUtils.notNull(path.toUri()));
195  }
196
197  /**
198   * Load data from the provided resource into a bound object.
199   * <p>
200   * This method will auto-detect the format of the provided resource.
201   *
202   * @param <CLASS>
203   *          the type of the bound object to return
204   * @param url
205   *          the resource
206   * @return a bound object containing the loaded data
207   * @throws IOException
208   *           if an error occurred while reading the resource
209   * @throws URISyntaxException
210   *           if the provided {@code url} is malformed
211   * @see #detectFormat(URL)
212   */
213  @NonNull
214  default <CLASS extends IBoundObject> CLASS load(@NonNull URL url) throws IOException, URISyntaxException {
215    return load(ObjectUtils.notNull(url.toURI()));
216  }
217
218  /**
219   * Load data from the resource identified by the provided {@code uri} into a
220   * bound object.
221   * <p>
222   * This method will auto-detect the format of the provided resource.
223   *
224   * @param <CLASS>
225   *          the type of the bound object to return
226   * @param uri
227   *          the resource
228   * @return a bound object containing the loaded data
229   * @throws IOException
230   *           if an error occurred while reading the resource
231   * @see #detectFormat(URL)
232   */
233  @NonNull
234  <CLASS extends IBoundObject> CLASS load(@NonNull URI uri) throws IOException;
235
236  /**
237   * Load data from the provided resource into a bound object.
238   * <p>
239   * This method should auto-detect the format of the provided resource.
240   * <p>
241   * This method will not close the provided {@link InputStream}, since it does
242   * not own the stream.
243   *
244   * @param <CLASS>
245   *          the type of the bound object to return
246   * @param is
247   *          the resource stream
248   * @param resource
249   *          the URI of the resource
250   * @return a bound object containing the loaded data
251   * @throws IOException
252   *           if an error occurred while reading the resource
253   * @see #detectFormat(InputStream, URI)
254   */
255  @NonNull
256  <CLASS extends IBoundObject> CLASS load(@NonNull InputStream is, @NonNull URI resource) throws IOException;
257
258  /**
259   * Load data from the specified resource into a bound object with the type of
260   * the specified Java class.
261   *
262   * @param <CLASS>
263   *          the Java type to load data into
264   * @param clazz
265   *          the class for the java type
266   * @param file
267   *          the resource to load
268   * @return the loaded instance data
269   * @throws IOException
270   *           if an error occurred while loading the data in the specified file
271   */
272  @NonNull
273  default <CLASS extends IBoundObject> CLASS load(
274      @NonNull Class<CLASS> clazz,
275      @NonNull File file) throws IOException {
276    return load(clazz, ObjectUtils.notNull(file.toPath()));
277  }
278
279  /**
280   * Load data from the specified resource into a bound object with the type of
281   * the specified Java class.
282   *
283   * @param <CLASS>
284   *          the Java type to load data into
285   * @param clazz
286   *          the class for the java type
287   * @param path
288   *          the resource to load
289   * @return the loaded instance data
290   * @throws IOException
291   *           if an error occurred while loading the data in the specified file
292   */
293  @NonNull
294  default <CLASS extends IBoundObject> CLASS load(
295      @NonNull Class<CLASS> clazz,
296      @NonNull Path path) throws IOException {
297    return load(clazz, ObjectUtils.notNull(path.toUri()));
298  }
299
300  /**
301   * Load data from the specified resource into a bound object with the type of
302   * the specified Java class.
303   *
304   * @param <CLASS>
305   *          the Java type to load data into
306   * @param clazz
307   *          the class for the java type
308   * @param url
309   *          the resource to load
310   * @return the loaded instance data
311   * @throws IOException
312   *           if an error occurred while loading the data in the specified file
313   * @throws URISyntaxException
314   *           if the provided {@code url} is malformed
315   */
316  @NonNull
317  default <CLASS extends IBoundObject> CLASS load(
318      @NonNull Class<CLASS> clazz,
319      @NonNull URL url) throws IOException, URISyntaxException {
320    return load(clazz, ObjectUtils.notNull(url.toURI()));
321  }
322
323  /**
324   * Load data from the specified resource into a bound object with the type of
325   * the specified Java class.
326   *
327   * @param <CLASS>
328   *          the Java type to load data into
329   * @param clazz
330   *          the class for the java type
331   * @param uri
332   *          the resource to load
333   * @return the loaded instance data
334   * @throws IOException
335   *           if an error occurred while loading the data in the specified file
336   */
337  @NonNull
338  <CLASS extends IBoundObject> CLASS load(
339      @NonNull Class<CLASS> clazz,
340      @NonNull URI uri) throws IOException;
341
342  /**
343   * Load data from the specified resource into a bound object with the type of
344   * the specified Java class.
345   * <p>
346   * This method will not close the provided {@link InputStream}, since it does
347   * not own the stream.
348   * <p>
349   * Implementations of this method will do format detection. This process might
350   * leave the provided {@link InputStream} at a position beyond the last parsed
351   * location. If you want to avoid this possibility, use and implementation of
352   * {@link IDeserializer#deserialize(InputStream, URI)} instead, such as what is
353   * provided by {@link DefaultBindingContext#newDeserializer(Format, Class)}.
354   *
355   * @param <CLASS>
356   *          the Java type to load data into
357   * @param clazz
358   *          the class for the java type
359   * @param is
360   *          the resource stream
361   * @param resource
362   *          the URI of the resource
363   * @return the loaded data
364   * @throws IOException
365   *           if an error occurred while loading the data from the specified
366   *           resource
367   */
368  @NonNull
369  <CLASS extends IBoundObject> CLASS load(
370      @NonNull Class<CLASS> clazz,
371      @NonNull InputStream is,
372      @NonNull URI resource) throws IOException;
373
374  /**
375   * Load data from the specified resource into a bound object with the type of
376   * the specified Java class.
377   * <p>
378   * This method will not close the provided {@link InputStream}, since it does
379   * not own the stream.
380   *
381   * @param <CLASS>
382   *          the Java type to load data into
383   * @param format
384   *          the format to parse
385   * @param clazz
386   *          the class for the java type
387   * @param is
388   *          the resource stream
389   * @param resource
390   *          the URI of the resource
391   * @return the loaded data
392   * @throws IOException
393   *           if an error occurred while loading the data from the specified
394   *           resource
395   */
396  @NonNull
397  <CLASS extends IBoundObject> CLASS load(
398      @NonNull Class<CLASS> clazz,
399      @NonNull Format format,
400      @NonNull InputStream is,
401      @NonNull URI resource) throws IOException;
402
403  /**
404   * Load data expressed using the provided {@code format} and return that data as
405   * a Metapath node item.
406   * <p>
407   * The specific Module model is auto-detected by analyzing the source. The class
408   * reported is implementation specific.
409   *
410   * @param format
411   *          the expected format of the data to parse
412   * @param path
413   *          the resource
414   * @return the Metapath node item for the parsed data
415   * @throws IOException
416   *           if an error occurred while loading the data from the specified
417   *           resource
418   */
419  @NonNull
420  default IDocumentNodeItem loadAsNodeItem(
421      @NonNull Format format,
422      @NonNull Path path) throws IOException {
423    return loadAsNodeItem(format, ObjectUtils.notNull(path.toUri()));
424  }
425
426  /**
427   * Load data expressed using the provided {@code format} and return that data as
428   * a Metapath node item.
429   * <p>
430   * The specific Module model is auto-detected by analyzing the source. The class
431   * reported is implementation specific.
432   *
433   * @param format
434   *          the expected format of the data to parse
435   * @param uri
436   *          the resource
437   * @return the Metapath node item for the parsed data
438   * @throws IOException
439   *           if an error occurred while loading the data from the specified
440   *           resource
441   */
442  @NonNull
443  IDocumentNodeItem loadAsNodeItem(
444      @NonNull Format format,
445      @NonNull URI uri) throws IOException;
446
447  /**
448   * Load data expressed using the provided {@code format} and return that data as
449   * a Metapath node item.
450   * <p>
451   * The specific Module model is auto-detected by analyzing the source. The class
452   * reported is implementation specific.
453   *
454   * @param format
455   *          the expected format of the data to parse
456   * @param is
457   *          the resource stream
458   * @param resource
459   *          the URI of the resource
460   * @return the Metapath node item for the parsed data
461   * @throws IOException
462   *           if an error occurred while loading the data from the specified
463   *           resource
464   */
465  @NonNull
466  IDocumentNodeItem loadAsNodeItem(
467      @NonNull Format format,
468      @NonNull InputStream is,
469      @NonNull URI resource) throws IOException;
470
471  /**
472   * Get the configured Module binding context to use to load Java types.
473   *
474   * @return the binding context
475   */
476  @NonNull
477  IBindingContext getBindingContext();
478
479  /**
480   * Auto convert the provided {@code source} to the provided {@code toFormat}.
481   * Write the converted content to the provided {@code destination}.
482   * <p>
483   * The format of the source is expected to be auto detected using
484   * {@link #detectFormat(Path)}.
485   *
486   * @param <CLASS>
487   *          the Java type to load data into
488   * @param source
489   *          the resource to convert
490   * @param destination
491   *          the resource to write converted content to
492   * @param toFormat
493   *          the format to convert to
494   * @param rootClass
495   *          the class for the Java type to load data into
496   * @throws FileNotFoundException
497   *           the the provided source file was not found
498   * @throws IOException
499   *           if an error occurred while loading the data from the specified
500   *           resource or writing the converted data to the specified destination
501   */
502  default <CLASS extends IBoundObject> void convert(
503      @NonNull Path source,
504      @NonNull Path destination,
505      @NonNull Format toFormat,
506      @NonNull Class<CLASS> rootClass) throws FileNotFoundException, IOException {
507    CLASS object = load(rootClass, source);
508
509    ISerializer<CLASS> serializer = getBindingContext().newSerializer(toFormat, rootClass);
510    serializer.serialize(object, destination);
511  }
512
513  /**
514   * Auto convert the provided {@code source} to the provided {@code toFormat}.
515   * Write the converted content to the provided {@code destination}.
516   * <p>
517   * The format of the source is expected to be auto detected using
518   * {@link #detectFormat(Path)}.
519   *
520   * @param <CLASS>
521   *          the Java type to load data into
522   * @param source
523   *          the resource to convert
524   * @param os
525   *          the output stream to write converted content to
526   * @param toFormat
527   *          the format to convert to
528   * @param rootClass
529   *          the class for the Java type to load data into
530   * @throws FileNotFoundException
531   *           the the provided source file was not found
532   * @throws IOException
533   *           if an error occurred while loading the data from the specified
534   *           resource or writing the converted data to the specified destination
535   */
536  default <CLASS extends IBoundObject> void convert(
537      @NonNull Path source,
538      @NonNull OutputStream os,
539      @NonNull Format toFormat,
540      @NonNull Class<CLASS> rootClass) throws FileNotFoundException, IOException {
541    CLASS object = load(rootClass, source);
542
543    ISerializer<CLASS> serializer = getBindingContext().newSerializer(toFormat, rootClass);
544    serializer.serialize(object, os);
545  }
546
547  /**
548   * Auto convert the provided {@code source} to the provided {@code toFormat}.
549   * Write the converted content to the provided {@code destination}.
550   * <p>
551   * The format of the source is expected to be auto detected using
552   * {@link #detectFormat(Path)}.
553   *
554   * @param <CLASS>
555   *          the Java type to load data into
556   * @param source
557   *          the resource to convert
558   * @param destination
559   *          the resource to write converted content to
560   * @param toFormat
561   *          the format to convert to
562   * @param rootClass
563   *          the class for the Java type to load data into
564   * @throws FileNotFoundException
565   *           the the provided source file was not found
566   * @throws IOException
567   *           if an error occurred while loading the data from the specified
568   *           resource or writing the converted data to the specified destination
569   */
570  default <CLASS extends IBoundObject> void convert(
571      @NonNull URI source,
572      @NonNull Path destination,
573      @NonNull Format toFormat,
574      @NonNull Class<CLASS> rootClass) throws FileNotFoundException, IOException {
575    CLASS object = load(rootClass, source);
576
577    ISerializer<CLASS> serializer = getBindingContext().newSerializer(toFormat, rootClass);
578    serializer.serialize(object, destination);
579  }
580
581  /**
582   * Auto convert the provided {@code source} to the provided {@code toFormat}.
583   * Write the converted content to the provided {@code destination}.
584   * <p>
585   * The format of the source is expected to be auto detected using
586   * {@link #detectFormat(Path)}.
587   *
588   * @param <CLASS>
589   *          the Java type to load data into
590   * @param source
591   *          the resource to convert
592   * @param os
593   *          the output stream to write converted content to
594   * @param toFormat
595   *          the format to convert to
596   * @param rootClass
597   *          the class for the Java type to load data into
598   * @throws FileNotFoundException
599   *           the the provided source file was not found
600   * @throws IOException
601   *           if an error occurred while loading the data from the specified
602   *           resource or writing the converted data to the specified destination
603   */
604  default <CLASS extends IBoundObject> void convert(
605      @NonNull URI source,
606      @NonNull OutputStream os,
607      @NonNull Format toFormat,
608      @NonNull Class<CLASS> rootClass) throws FileNotFoundException, IOException {
609    CLASS object = load(rootClass, source);
610
611    ISerializer<CLASS> serializer = getBindingContext().newSerializer(toFormat, rootClass);
612    serializer.serialize(object, os);
613  }
614
615  /**
616   * Auto convert the provided {@code source} to the provided {@code toFormat}.
617   * Write the converted content to the provided {@code destination}.
618   * <p>
619   * The format of the source is expected to be auto detected using
620   * {@link #detectFormat(Path)}.
621   *
622   * @param <CLASS>
623   *          the Java type to load data into
624   * @param source
625   *          the resource to convert
626   * @param writer
627   *          the writer to write converted content to
628   * @param toFormat
629   *          the format to convert to
630   * @param rootClass
631   *          the class for the Java type to load data into
632   * @throws FileNotFoundException
633   *           the the provided source file was not found
634   * @throws IOException
635   *           if an error occurred while loading the data from the specified
636   *           resource or writing the converted data to the specified destination
637   */
638  default <CLASS extends IBoundObject> void convert(
639      @NonNull URI source,
640      @NonNull Writer writer,
641      @NonNull Format toFormat,
642      @NonNull Class<CLASS> rootClass) throws FileNotFoundException, IOException {
643    CLASS object = load(rootClass, source);
644
645    ISerializer<CLASS> serializer = getBindingContext().newSerializer(toFormat, rootClass);
646    serializer.serialize(object, writer);
647  }
648}