1   /*
2    * SPDX-FileCopyrightText: none
3    * SPDX-License-Identifier: CC0-1.0
4    */
5   
6   package gov.nist.secauto.metaschema.databind.io;
7   
8   import gov.nist.secauto.metaschema.core.configuration.IConfiguration;
9   import gov.nist.secauto.metaschema.core.configuration.IMutableConfiguration;
10  import gov.nist.secauto.metaschema.core.metapath.IDocumentLoader;
11  import gov.nist.secauto.metaschema.core.metapath.item.node.IDocumentNodeItem;
12  import gov.nist.secauto.metaschema.core.model.IBoundObject;
13  import gov.nist.secauto.metaschema.core.util.ObjectUtils;
14  import gov.nist.secauto.metaschema.databind.DefaultBindingContext;
15  import gov.nist.secauto.metaschema.databind.IBindingContext;
16  
17  import org.eclipse.jdt.annotation.Owning;
18  import org.xml.sax.InputSource;
19  
20  import java.io.File;
21  import java.io.FileNotFoundException;
22  import java.io.IOException;
23  import java.io.InputStream;
24  import java.io.OutputStream;
25  import java.io.Writer;
26  import java.net.URI;
27  import java.net.URISyntaxException;
28  import java.net.URL;
29  import java.nio.file.Path;
30  
31  import edu.umd.cs.findbugs.annotations.NonNull;
32  
33  /**
34   * A common interface for loading Module based instance resources.
35   */
36  public interface IBoundLoader extends IDocumentLoader, IMutableConfiguration<DeserializationFeature<?>> {
37  
38    @Override
39    default IBoundLoader enableFeature(DeserializationFeature<?> feature) {
40      return set(feature, true);
41    }
42  
43    @Override
44    default IBoundLoader disableFeature(DeserializationFeature<?> feature) {
45      return set(feature, false);
46    }
47  
48    @Override
49    IBoundLoader applyConfiguration(IConfiguration<DeserializationFeature<?>> other);
50  
51    @Override
52    IBoundLoader set(DeserializationFeature<?> feature, Object value);
53  
54    /**
55     * Determine the format of the provided resource.
56     *
57     * @param file
58     *          the resource
59     * @return the format information for the provided resource
60     * @throws IOException
61     *           if an error occurred while reading the resource
62     */
63    @NonNull
64    default Format detectFormat(@NonNull File file) throws IOException {
65      return detectFormat(ObjectUtils.notNull(file.toPath()));
66    }
67  
68    /**
69     * Determine the format of the provided resource.
70     *
71     * @param path
72     *          the resource
73     * @return the format information for the provided resource
74     * @throws IOException
75     *           if an error occurred while reading the resource
76     */
77    @NonNull
78    default Format detectFormat(@NonNull Path path) throws IOException {
79      return detectFormat(ObjectUtils.notNull(path.toUri()));
80    }
81  
82    /**
83     * Determine the format of the provided resource.
84     *
85     * @param url
86     *          the resource
87     * @return the format information for the provided resource
88     * @throws IOException
89     *           if an error occurred while reading the resource
90     */
91    @NonNull
92    default Format detectFormat(@NonNull URL url) throws IOException {
93      try {
94        return detectFormat(ObjectUtils.notNull(url.toURI()));
95      } catch (URISyntaxException ex) {
96        throw new IOException(ex);
97      }
98    }
99  
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 }