IBoundLoader.java

/*
 * Portions of this software was developed by employees of the National Institute
 * of Standards and Technology (NIST), an agency of the Federal Government and is
 * being made available as a public service. Pursuant to title 17 United States
 * Code Section 105, works of NIST employees are not subject to copyright
 * protection in the United States. This software may be subject to foreign
 * copyright. Permission in the United States and in foreign countries, to the
 * extent that NIST may hold copyright, to use, copy, modify, create derivative
 * works, and distribute this software and its documentation without fee is hereby
 * granted on a non-exclusive basis, provided that this notice and disclaimer
 * of warranty appears in all copies.
 *
 * THE SOFTWARE IS PROVIDED 'AS IS' WITHOUT ANY WARRANTY OF ANY KIND, EITHER
 * EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT LIMITED TO, ANY WARRANTY
 * THAT THE SOFTWARE WILL CONFORM TO SPECIFICATIONS, ANY IMPLIED WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND FREEDOM FROM
 * INFRINGEMENT, AND ANY WARRANTY THAT THE DOCUMENTATION WILL CONFORM TO THE
 * SOFTWARE, OR ANY WARRANTY THAT THE SOFTWARE WILL BE ERROR FREE.  IN NO EVENT
 * SHALL NIST BE LIABLE FOR ANY DAMAGES, INCLUDING, BUT NOT LIMITED TO, DIRECT,
 * INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES, ARISING OUT OF, RESULTING FROM,
 * OR IN ANY WAY CONNECTED WITH THIS SOFTWARE, WHETHER OR NOT BASED UPON WARRANTY,
 * CONTRACT, TORT, OR OTHERWISE, WHETHER OR NOT INJURY WAS SUSTAINED BY PERSONS OR
 * PROPERTY OR OTHERWISE, AND WHETHER OR NOT LOSS WAS SUSTAINED FROM, OR AROSE OUT
 * OF THE RESULTS OF, OR USE OF, THE SOFTWARE OR SERVICES PROVIDED HEREUNDER.
 */

package gov.nist.secauto.metaschema.databind.io;

import gov.nist.secauto.metaschema.core.configuration.IConfiguration;
import gov.nist.secauto.metaschema.core.configuration.IMutableConfiguration;
import gov.nist.secauto.metaschema.core.metapath.IDocumentLoader;
import gov.nist.secauto.metaschema.core.metapath.item.node.IDocumentNodeItem;
import gov.nist.secauto.metaschema.core.util.ObjectUtils;
import gov.nist.secauto.metaschema.databind.DefaultBindingContext;
import gov.nist.secauto.metaschema.databind.IBindingContext;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.file.Path;

import edu.umd.cs.findbugs.annotations.NonNull;

/**
 * A common interface for loading Module based instance resources.
 */
public interface IBoundLoader extends IDocumentLoader, IMutableConfiguration<DeserializationFeature<?>> {

  @Override
  default IBoundLoader enableFeature(DeserializationFeature<?> feature) {
    return set(feature, true);
  }

  @Override
  default IBoundLoader disableFeature(DeserializationFeature<?> feature) {
    return set(feature, false);
  }

  @Override
  IBoundLoader applyConfiguration(IConfiguration<DeserializationFeature<?>> other);

  @Override
  IBoundLoader set(DeserializationFeature<?> feature, Object value);

  /**
   * Determine the format of the provided resource.
   *
   * @param file
   *          the resource
   * @return the format information for the provided resource
   * @throws IOException
   *           if an error occurred while reading the resource
   */
  @NonNull
  default FormatDetector.Result detectFormat(@NonNull File file) throws IOException {
    return detectFormat(ObjectUtils.notNull(file.toPath()));
  }

  /**
   * Determine the format of the provided resource.
   *
   * @param path
   *          the resource
   * @return the format information for the provided resource
   * @throws IOException
   *           if an error occurred while reading the resource
   */
  @NonNull
  default FormatDetector.Result detectFormat(@NonNull Path path) throws IOException {
    return detectFormat(ObjectUtils.notNull(path.toUri()));
  }

  /**
   * Determine the format of the provided resource.
   *
   * @param url
   *          the resource
   * @return the format information for the provided resource
   * @throws IOException
   *           if an error occurred while reading the resource
   */
  @NonNull
  default FormatDetector.Result detectFormat(@NonNull URL url) throws IOException {
    try {
      return detectFormat(ObjectUtils.notNull(url.toURI()));
    } catch (URISyntaxException ex) {
      throw new IOException(ex);
    }
  }

  /**
   * Determine the format of the resource identified by the provided {@code uri}.
   *
   * @param uri
   *          the resource
   * @return the format information for the provided resource
   * @throws IOException
   *           if an error occurred while reading the resource
   */
  @NonNull
  FormatDetector.Result detectFormat(@NonNull URI uri) throws IOException;

  /**
   * Determine the format of the provided resource.
   * <p>
   * This method will consume data from the provided {@link InputStream}. If the
   * caller of this method intends to read data from the stream after determining
   * the format, the caller should pass in a stream that can be reset.
   * <p>
   * This method will not close the provided {@link InputStream}, since it does
   * not own the stream.
   *
   * @param is
   *          an input stream for the resource
   * @return the format information for the provided resource
   * @throws IOException
   *           if an error occurred while reading the resource
   */
  @NonNull
  FormatDetector.Result detectFormat(@NonNull InputStream is) throws IOException;

  //
  // /**
  // * Determine the format of the provided resource.
  // * <p>
  // * This method will consume data from any {@link InputStream} provided by the
  // * {@link InputSource}. If the caller of this method intends to read data from
  // * the stream after determining the format, the caller should pass in a stream
  // * that can be reset.
  // * <p>
  // * This method will not close any {@link InputStream} provided by the
  // * {@link InputSource}, since it does not own the stream.
  // *
  // * @param source
  // * information about how to access the resource
  // * @return the format of the provided resource
  // * @throws IOException
  // * if an error occurred while reading the resource
  // */
  // @NonNull
  // Format detectFormat(@NonNull InputSource source) throws IOException;

  /**
   * Load data from the provided resource into a bound object.
   * <p>
   * This method will auto-detect the format of the provided resource.
   *
   * @param <CLASS>
   *          the type of the bound object to return
   * @param file
   *          the resource
   * @return a bound object containing the loaded data
   * @throws IOException
   *           if an error occurred while reading the resource
   * @see #detectFormat(File)
   */
  @NonNull
  default <CLASS> CLASS load(@NonNull File file) throws IOException {
    return load(ObjectUtils.notNull(file.toPath()));
  }

  /**
   * Load data from the provided resource into a bound object.
   * <p>
   * This method will auto-detect the format of the provided resource.
   *
   * @param <CLASS>
   *          the type of the bound object to return
   * @param path
   *          the resource
   * @return a bound object containing the loaded data
   * @throws IOException
   *           if an error occurred while reading the resource
   * @see #detectFormat(File)
   */
  @NonNull
  default <CLASS> CLASS load(@NonNull Path path) throws IOException {
    return load(ObjectUtils.notNull(path.toUri()));
  }

  /**
   * Load data from the provided resource into a bound object.
   * <p>
   * This method will auto-detect the format of the provided resource.
   *
   * @param <CLASS>
   *          the type of the bound object to return
   * @param url
   *          the resource
   * @return a bound object containing the loaded data
   * @throws IOException
   *           if an error occurred while reading the resource
   * @throws URISyntaxException
   *           if the provided {@code url} is malformed
   * @see #detectFormat(URL)
   */
  @NonNull
  default <CLASS> CLASS load(@NonNull URL url) throws IOException, URISyntaxException {
    return load(ObjectUtils.notNull(url.toURI()));
  }

  /**
   * Load data from the resource identified by the provided {@code uri} into a
   * bound object.
   * <p>
   * This method will auto-detect the format of the provided resource.
   *
   * @param <CLASS>
   *          the type of the bound object to return
   * @param uri
   *          the resource
   * @return a bound object containing the loaded data
   * @throws IOException
   *           if an error occurred while reading the resource
   * @see #detectFormat(URL)
   */
  @NonNull
  <CLASS> CLASS load(@NonNull URI uri) throws IOException;

  /**
   * Load data from the provided resource into a bound object.
   * <p>
   * This method should auto-detect the format of the provided resource.
   * <p>
   * This method will not close the provided {@link InputStream}, since it does
   * not own the stream.
   *
   * @param <CLASS>
   *          the type of the bound object to return
   * @param is
   *          the resource stream
   * @param documentUri
   *          the URI of the resource
   * @return a bound object containing the loaded data
   * @throws IOException
   *           if an error occurred while reading the resource
   * @see #detectFormat(InputStream)
   */
  @NonNull
  <CLASS> CLASS load(@NonNull InputStream is, @NonNull URI documentUri) throws IOException;

  /**
   * Load data from the specified resource into a bound object with the type of
   * the specified Java class.
   *
   * @param <CLASS>
   *          the Java type to load data into
   * @param clazz
   *          the class for the java type
   * @param file
   *          the resource to load
   * @return the loaded instance data
   * @throws IOException
   *           if an error occurred while loading the data in the specified file
   */
  @NonNull
  default <CLASS> CLASS load(
      @NonNull Class<CLASS> clazz,
      @NonNull File file) throws IOException {
    return load(clazz, ObjectUtils.notNull(file.toPath()));
  }

  /**
   * Load data from the specified resource into a bound object with the type of
   * the specified Java class.
   *
   * @param <CLASS>
   *          the Java type to load data into
   * @param clazz
   *          the class for the java type
   * @param path
   *          the resource to load
   * @return the loaded instance data
   * @throws IOException
   *           if an error occurred while loading the data in the specified file
   */
  @NonNull
  default <CLASS> CLASS load(
      @NonNull Class<CLASS> clazz,
      @NonNull Path path) throws IOException {
    return load(clazz, ObjectUtils.notNull(path.toUri()));
  }

  /**
   * Load data from the specified resource into a bound object with the type of
   * the specified Java class.
   *
   * @param <CLASS>
   *          the Java type to load data into
   * @param clazz
   *          the class for the java type
   * @param url
   *          the resource to load
   * @return the loaded instance data
   * @throws IOException
   *           if an error occurred while loading the data in the specified file
   * @throws URISyntaxException
   *           if the provided {@code url} is malformed
   */
  @NonNull
  default <CLASS> CLASS load(
      @NonNull Class<CLASS> clazz,
      @NonNull URL url) throws IOException, URISyntaxException {
    return load(clazz, ObjectUtils.notNull(url.toURI()));
  }

  /**
   * Load data from the specified resource into a bound object with the type of
   * the specified Java class.
   *
   * @param <CLASS>
   *          the Java type to load data into
   * @param clazz
   *          the class for the java type
   * @param uri
   *          the resource to load
   * @return the loaded instance data
   * @throws IOException
   *           if an error occurred while loading the data in the specified file
   */
  @NonNull
  <CLASS> CLASS load(
      @NonNull Class<CLASS> clazz,
      @NonNull URI uri) throws IOException;

  /**
   * Load data from the specified resource into a bound object with the type of
   * the specified Java class.
   * <p>
   * This method will not close the provided {@link InputStream}, since it does
   * not own the stream.
   * <p>
   * Implementations of this method will do format detection. This process might
   * leave the provided {@link InputStream} at a position beyond the last parsed
   * location. If you want to avoid this possibility, use and implementation of
   * {@link IDeserializer#deserialize(InputStream, URI)} instead, such as what is
   * provided by {@link DefaultBindingContext#newDeserializer(Format, Class)}.
   *
   * @param <CLASS>
   *          the Java type to load data into
   * @param clazz
   *          the class for the java type
   * @param is
   *          the resource stream
   * @param documentUri
   *          the URI of the resource
   * @return the loaded data
   * @throws IOException
   *           if an error occurred while loading the data from the specified
   *           resource
   */
  @NonNull
  <CLASS> CLASS load(
      @NonNull Class<CLASS> clazz,
      @NonNull InputStream is,
      @NonNull URI documentUri) throws IOException;

  /**
   * Load data expressed using the provided {@code format} and return that data as
   * a Metapath node item.
   * <p>
   * The specific Module model is auto-detected by analyzing the source. The class
   * reported is implementation specific.
   *
   * @param format
   *          the expected format of the data to parse
   * @param path
   *          the resource
   * @return the Metapath node item for the parsed data
   * @throws IOException
   *           if an error occurred while loading the data from the specified
   *           resource
   */
  @NonNull
  default IDocumentNodeItem loadAsNodeItem(
      @NonNull Format format,
      @NonNull Path path) throws IOException {
    return loadAsNodeItem(format, ObjectUtils.notNull(path.toUri()));
  }

  /**
   * Load data expressed using the provided {@code format} and return that data as
   * a Metapath node item.
   * <p>
   * The specific Module model is auto-detected by analyzing the source. The class
   * reported is implementation specific.
   *
   * @param format
   *          the expected format of the data to parse
   * @param uri
   *          the resource
   * @return the Metapath node item for the parsed data
   * @throws IOException
   *           if an error occurred while loading the data from the specified
   *           resource
   */
  @NonNull
  IDocumentNodeItem loadAsNodeItem(
      @NonNull Format format,
      @NonNull URI uri) throws IOException;

  /**
   * Load data expressed using the provided {@code format} and return that data as
   * a Metapath node item.
   * <p>
   * The specific Module model is auto-detected by analyzing the source. The class
   * reported is implementation specific.
   *
   * @param format
   *          the expected format of the data to parse
   * @param is
   *          the resource stream
   * @param documentUri
   *          the URI of the resource
   * @return the Metapath node item for the parsed data
   * @throws IOException
   *           if an error occurred while loading the data from the specified
   *           resource
   */
  @NonNull
  IDocumentNodeItem loadAsNodeItem(
      @NonNull Format format,
      @NonNull InputStream is,
      @NonNull URI documentUri) throws IOException;

  /**
   * Get the configured Module binding context to use to load Java types.
   *
   * @return the binding context
   */
  @NonNull
  IBindingContext getBindingContext();

  /**
   * Auto convert the provided {@code source} to the provided {@code toFormat}.
   * Write the converted content to the provided {@code destination}.
   * <p>
   * The format of the source is expected to be auto detected using
   * {@link #detectFormat(Path)}.
   *
   * @param <CLASS>
   *          the Java type to load data into
   * @param source
   *          the resource to convert
   * @param destination
   *          the resource to write converted content to
   * @param toFormat
   *          the format to convert to
   * @param rootClass
   *          the class for the Java type to load data into
   * @throws FileNotFoundException
   *           the the provided source file was not found
   * @throws IOException
   *           if an error occurred while loading the data from the specified
   *           resource or writing the converted data to the specified destination
   */
  default <CLASS> void convert(
      @NonNull Path source,
      @NonNull Path destination,
      @NonNull Format toFormat,
      @NonNull Class<CLASS> rootClass) throws FileNotFoundException, IOException {
    CLASS object = load(rootClass, source);

    ISerializer<CLASS> serializer = getBindingContext().newSerializer(toFormat, rootClass);
    serializer.serialize(object, destination);
  }

  /**
   * Auto convert the provided {@code source} to the provided {@code toFormat}.
   * Write the converted content to the provided {@code destination}.
   * <p>
   * The format of the source is expected to be auto detected using
   * {@link #detectFormat(Path)}.
   *
   * @param <CLASS>
   *          the Java type to load data into
   * @param source
   *          the resource to convert
   * @param os
   *          the output stream to write converted content to
   * @param toFormat
   *          the format to convert to
   * @param rootClass
   *          the class for the Java type to load data into
   * @throws FileNotFoundException
   *           the the provided source file was not found
   * @throws IOException
   *           if an error occurred while loading the data from the specified
   *           resource or writing the converted data to the specified destination
   */
  default <CLASS> void convert(
      @NonNull Path source,
      @NonNull OutputStream os,
      @NonNull Format toFormat,
      @NonNull Class<CLASS> rootClass) throws FileNotFoundException, IOException {
    CLASS object = load(rootClass, source);

    ISerializer<CLASS> serializer = getBindingContext().newSerializer(toFormat, rootClass);
    serializer.serialize(object, os);
  }
}