001/*
002 * SPDX-FileCopyrightText: none
003 * SPDX-License-Identifier: CC0-1.0
004 */
005
006package dev.metaschema.core.model;
007
008import java.io.IOException;
009import java.net.URI;
010import java.util.ArrayList;
011import java.util.Collection;
012import java.util.Collections;
013import java.util.Deque;
014import java.util.LinkedHashMap;
015import java.util.List;
016import java.util.Map;
017
018import dev.metaschema.core.util.ObjectUtils;
019import edu.umd.cs.findbugs.annotations.NonNull;
020
021/**
022 * Provides methods to load a Metaschema expressed in XML.
023 * <p>
024 * Loaded Metaschema instances are cached to avoid the need to load them for
025 * every use. Any Metaschema imported is also loaded and cached automatically.
026 *
027 * @param <T>
028 *          the Java type of the module binding
029 * @param <M>
030 *          the Java type of the Metaschema module loaded by this loader
031 */
032public abstract class AbstractModuleLoader<T, M extends IModuleExtended<M, ?, ?, ?, ?>>
033    extends AbstractLoader<M>
034    implements IModuleLoader<M> {
035  /**
036   * Construct a new Metaschema module loader, which use the provided module post
037   * processors when loading a module.
038   */
039  protected AbstractModuleLoader() {
040    // only allow construction by extending classes
041  }
042
043  /**
044   * Parse the {@code resource} based on the provided {@code xmlObject}.
045   *
046   * @param resource
047   *          the URI of the resource being parsed
048   * @param binding
049   *          the XML beans object to parse
050   * @param importedModules
051   *          previously parsed Metaschema modules imported by the provided
052   *          {@code resource}
053   * @return the parsed resource as a Metaschema module
054   * @throws MetaschemaException
055   *           if an error occurred while parsing the XML beans object
056   */
057  @NonNull
058  protected abstract M newModule(
059      @NonNull URI resource,
060      @NonNull T binding,
061      @NonNull List<? extends M> importedModules) throws MetaschemaException;
062
063  /**
064   * Get the list of Metaschema module URIs associated with the provided binding.
065   *
066   * @param binding
067   *          the Metaschema module binding declaring the imports
068   * @return the list of Metaschema module URIs
069   */
070  @NonNull
071  protected abstract List<URI> getImports(@NonNull T binding);
072
073  @Override
074  protected M parseResource(@NonNull URI resource, @NonNull Deque<URI> visitedResources)
075      throws IOException {
076    // parse this Metaschema module
077    T binding = parseModule(resource);
078
079    // now check if this Metaschema imports other metaschema
080    List<URI> imports = getImports(binding);
081    @NonNull
082    Map<URI, M> importedModules;
083    if (imports.isEmpty()) {
084      importedModules = ObjectUtils.notNull(Collections.emptyMap());
085    } else {
086      try {
087        importedModules = new LinkedHashMap<>();
088        for (URI importedResource : imports) {
089          URI resolvedResource = ObjectUtils.notNull(resource.resolve(importedResource));
090          importedModules.put(resolvedResource, loadInternal(resolvedResource, visitedResources));
091        }
092      } catch (MetaschemaException ex) {
093        throw new IOException(ex);
094      }
095    }
096
097    // now create this metaschema
098    Collection<M> values = importedModules.values();
099    try {
100      return newModule(resource, binding, new ArrayList<>(values));
101    } catch (MetaschemaException ex) {
102      throw new IOException(ex);
103    }
104  }
105
106  /**
107   * Parse the provided XML resource as a Metaschema module.
108   *
109   * @param resource
110   *          the resource to parse
111   * @return the parsed Metaschema module
112   * @throws IOException
113   *           if a parsing error occurred
114   */
115  @NonNull
116  protected abstract T parseModule(@NonNull URI resource) throws IOException;
117}