JaxbContextBuilder.java

/*
 * Copyright 2020-2022  the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.bremersee.xml;

import jakarta.xml.bind.Marshaller;
import jakarta.xml.bind.Unmarshaller;
import jakarta.xml.bind.ValidationEventHandler;
import jakarta.xml.bind.annotation.XmlRootElement;
import jakarta.xml.bind.annotation.XmlType;
import jakarta.xml.bind.annotation.adapters.XmlAdapter;
import jakarta.xml.bind.attachment.AttachmentMarshaller;
import jakarta.xml.bind.attachment.AttachmentUnmarshaller;
import java.util.Collection;
import java.util.Iterator;
import java.util.Optional;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.stream.StreamSupport;
import javax.xml.validation.Schema;

/**
 * The jaxb context builder.
 *
 * @author Christian Bremer
 */
public interface JaxbContextBuilder {

  /**
   * The default dependencies resolver implementation.
   */
  JaxbDependenciesResolver DEFAULT_DEPENDENCIES_RESOLVER = new JaxbDependenciesResolverImpl();

  /**
   * Creates a new jaxb context builder.
   *
   * @return the jaxb context builder
   */
  static JaxbContextBuilder newInstance() {
    return new JaxbContextBuilderImpl();
  }

  /**
   * Copy jaxb context builder.
   *
   * @return the jaxb context builder
   */
  JaxbContextBuilder copy();

  /**
   * Specifies whether to add a schema to the marshaller or unmarshaller. The default is to add
   * never a schema to the marshaller or unmarshaller.
   *
   * @param schemaMode the schema mode
   * @return the jaxb context builder
   * @see SchemaMode#NEVER
   * @see SchemaMode#ALWAYS
   * @see SchemaMode#MARSHAL
   * @see SchemaMode#UNMARSHAL
   * @see SchemaMode#EXTERNAL_XSD
   */
  JaxbContextBuilder withSchemaMode(SchemaMode schemaMode);

  /**
   * Specifies the schema builder to generate the schema. The default is the default schema builder
   * implementation (see {@link SchemaBuilder#newInstance()}).
   *
   * @param schemaBuilder the schema builder
   * @return the jaxb context builder
   */
  JaxbContextBuilder withSchemaBuilder(SchemaBuilder schemaBuilder);

  /**
   * Specifies the dependencies-resolver to use. The default jaxb context builder will use a default
   * implementation.
   *
   * <p>To turn off dependency resolving set {@code null} here.
   *
   * @param resolver the resolver
   * @return the jaxb context builder
   */
  JaxbContextBuilder withDependenciesResolver(JaxbDependenciesResolver resolver);

  /**
   * Specifies the class loader to use.
   *
   * @param classLoader the class loader
   * @return the jaxb context builder
   */
  JaxbContextBuilder withContextClassLoader(ClassLoader classLoader);

  /**
   * Specify whether the xml output should be formatted or not.
   *
   * @param formattedOutput the formatted output
   * @return the jaxb context builder
   */
  JaxbContextBuilder withFormattedOutput(boolean formattedOutput);

  /**
   * Sets xml adapters of marshaller and unmarshaller.
   *
   * @param xmlAdapters the xml adapters
   * @return the jaxb context builder
   */
  JaxbContextBuilder withXmlAdapters(Collection<? extends XmlAdapter<?, ?>> xmlAdapters);

  /**
   * Set attachment marshaller.
   *
   * @param attachmentMarshaller the attachment marshaller
   * @return the jaxb context builder
   */
  JaxbContextBuilder withAttachmentMarshaller(AttachmentMarshaller attachmentMarshaller);

  /**
   * Set attachment unmarshaller.
   *
   * @param attachmentUnmarshaller the attachment unmarshaller
   * @return the jaxb context builder
   */
  JaxbContextBuilder withAttachmentUnmarshaller(AttachmentUnmarshaller attachmentUnmarshaller);

  /**
   * Set validation event handler of marshaller and unmarshaller.
   *
   * @param validationEventHandler the validation event handler
   * @return the jaxb context builder
   */
  JaxbContextBuilder withValidationEventHandler(ValidationEventHandler validationEventHandler);


  /**
   * Add jaxb context meta-data to the jaxb context builder.
   *
   * @param data the data
   * @return the jaxb context builder
   */
  JaxbContextBuilder add(JaxbContextMember data);

  /**
   * Add all jaxb context meta-data to the jaxb context builder.
   *
   * @param data the data
   * @return the jaxb context builder
   */
  default JaxbContextBuilder addAll(Iterable<? extends JaxbContextMember> data) {
    return Optional.ofNullable(data)
        .map(d -> addAll(d.iterator()))
        .orElse(this);
  }

  /**
   * Add all jaxb context meta-data to the jaxb context builder.
   *
   * @param data the data
   * @return the jaxb context builder
   */
  default JaxbContextBuilder addAll(Iterator<? extends JaxbContextMember> data) {
    return Optional.ofNullable(data)
        .map(iter -> Spliterators.spliteratorUnknownSize(iter, Spliterator.ORDERED))
        .stream()
        .flatMap(split -> StreamSupport.stream(split, false))
        .map(this::add)
        .reduce((first, second) -> second)
        .orElse(this);
  }

  /**
   * Process the jaxb context meta-data provider and add its data to the jaxb context builder.
   *
   * @param dataProvider the data provider
   * @return the jaxb context builder
   */
  default JaxbContextBuilder process(JaxbContextDataProvider dataProvider) {
    return Optional.ofNullable(dataProvider)
        .map(provider -> addAll(provider.getJaxbContextData()))
        .orElse(this);
  }

  /**
   * Process the jaxb context meta-data providers and add their data to the jaxb context builder.
   *
   * @param dataProviders the data providers
   * @return the jaxb context builder
   */
  default JaxbContextBuilder processAll(
      Iterable<? extends JaxbContextDataProvider> dataProviders) {
    return Optional.ofNullable(dataProviders)
        .map(providers -> processAll(providers.iterator()))
        .orElse(this);
  }

  /**
   * Process the jaxb context meta-data providers and add their data to the jaxb context builder.
   *
   * @param dataProviders the data providers
   * @return the jaxb context builder
   */
  default JaxbContextBuilder processAll(
      Iterator<? extends JaxbContextDataProvider> dataProviders) {
    return Optional.ofNullable(dataProviders)
        .map(iter -> Spliterators.spliteratorUnknownSize(iter, Spliterator.ORDERED))
        .stream()
        .flatMap(split -> StreamSupport.stream(split, false))
        .map(this::process)
        .reduce((first, second) -> second)
        .orElse(this);
  }

  /**
   * Determines whether the unmarshaller can decode xml into an object of the given class.
   *
   * @param clazz the class
   * @return {@code true} if the unmarshaller can decode xml into an object of the given class,
   *     otherwise {@code false}
   */
  default boolean canUnmarshal(Class<?> clazz) {
    return Optional.ofNullable(clazz)
        .filter(c -> c.isAnnotationPresent(XmlRootElement.class)
            || c.isAnnotationPresent(XmlType.class))
        .isPresent();
  }

  /**
   * Determines whether the marshaller can encode an object of the given class into xml.
   *
   * @param clazz the class
   * @return {@code true} if the marshaller can decode an object of the given class into xml,
   *     otherwise {@code false}
   */
  default boolean canMarshal(Class<?> clazz) {
    return Optional.ofNullable(clazz)
        .filter(c -> c.isAnnotationPresent(XmlRootElement.class))
        .isPresent();
  }

  /**
   * Build unmarshaller for the given classes with the specified dependencies-resolver. If
   * dependency resolving is turned off, an unmarshaller of the default context (defined by the
   * added meta-data) will be returned or one that is created with
   * {@link jakarta.xml.bind.JAXBContext#newInstance(Class[])}*.
   *
   * @param classes the classes that should be processed by the unmarshaller
   * @return the unmarshaller
   * @see JaxbDependenciesResolver
   */
  Unmarshaller buildUnmarshaller(Class<?>... classes);

  /**
   * Build marshaller with the context which is defined by the added meta-data.
   *
   * @return the marshaller
   */
  default Marshaller buildMarshaller() {
    return buildMarshaller(null);
  }

  /**
   * Build marshaller for the given object (POJO) or for the given class or array of classes with
   * the specified dependencies-resolver. If dependency resolving is turned off, a marshaller of the
   * default context (defined by the added meta-data) will be returned or one that is created with
   * {@link jakarta.xml.bind.JAXBContext#newInstance(Class[])}.
   *
   * @param value the value (POJO) that should be processed by the marshaller or a single class
   *     or an array of classes
   * @return the marshaller
   * @see JaxbDependenciesResolver
   */
  Marshaller buildMarshaller(Object value);

  /**
   * Inits default jaxb context. Otherwise, the jaxb context will be created at first usage.
   *
   * @return the jaxb context builder
   */
  JaxbContextBuilder initJaxbContext();

  /**
   * Build default jaxb context that is defined by the added meta-data.
   *
   * @return the jaxb context wrapper
   */
  default JaxbContextWrapper buildJaxbContext() {
    return buildJaxbContext(null);
  }

  /**
   * Build jaxb context for the given object (POJO) or for the given class or array of classes with
   * the specified dependency resolver. If dependency resolving is turned off, the default jaxb
   * context (defined by the added meta-data) will be returned or a jaxb context will be created
   * with {@link jakarta.xml.bind.JAXBContext#newInstance(Class[])}.
   *
   * @param value the value (POJO) that should be processed by the jaxb context or a single
   *     class or an array of classes
   * @return the jaxb context
   */
  JaxbContextWrapper buildJaxbContext(Object value);

  /**
   * Build schema of the default jaxb context (defined by the added meta-data).
   *
   * @return the schema
   */
  default Schema buildSchema() {
    return buildSchema(null);
  }

  /**
   * Build schema of the specified value (POJO), a single class or an array of classes.
   *
   * @param value the value (POJO), a single class or an array of classes for which the schema
   *     should be created
   * @return the schema
   */
  Schema buildSchema(Object value);

}