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