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    * @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 }