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}