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}