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