001/* 002 * SPDX-FileCopyrightText: none 003 * SPDX-License-Identifier: CC0-1.0 004 */ 005 006package dev.metaschema.databind.model; 007 008import java.lang.reflect.Array; 009import java.util.Arrays; 010import java.util.Collection; 011import java.util.Collections; 012import java.util.LinkedHashMap; 013import java.util.List; 014import java.util.Map; 015import java.util.function.Function; 016import java.util.stream.Collectors; 017 018import dev.metaschema.core.metapath.StaticContext; 019import dev.metaschema.core.model.AbstractModule; 020import dev.metaschema.core.model.IBoundObject; 021import dev.metaschema.core.model.ISource; 022import dev.metaschema.core.qname.IEnhancedQName; 023import dev.metaschema.core.util.ObjectUtils; 024import dev.metaschema.databind.IBindingContext; 025import dev.metaschema.databind.model.annotations.MetaschemaModule; 026import dev.metaschema.databind.model.annotations.NsBinding; 027import edu.umd.cs.findbugs.annotations.NonNull; 028import nl.talsmasoftware.lazy4j.Lazy; 029 030/** 031 * An abstract base class for Metaschema modules bound to Java classes. 032 * <p> 033 * This class provides the common implementation for modules that are defined 034 * through Java annotations on classes, enabling data binding between Metaschema 035 * module definitions and Java objects. 036 */ 037public abstract class AbstractBoundModule 038 extends AbstractModule< 039 IBoundModule, 040 IBoundDefinitionModelComplex, 041 IBoundDefinitionFlag, 042 IBoundDefinitionModelField<?>, 043 IBoundDefinitionModelAssembly> 044 implements IBoundModule { 045 @NonNull 046 private final IBindingContext bindingContext; 047 @NonNull 048 private final Lazy<Map<Integer, IBoundDefinitionModelAssembly>> assemblyDefinitions; 049 @NonNull 050 private final Lazy<Map<Integer, IBoundDefinitionModelField<?>>> fieldDefinitions; 051 @NonNull 052 private final Lazy<StaticContext> staticContext; 053 @NonNull 054 private final ISource source; 055 056 /** 057 * Construct a new Module instance. 058 * 059 * @param importedModules 060 * Module imports associated with the Metaschema module 061 * @param bindingContext 062 * the Module binding context 063 */ 064 protected AbstractBoundModule( 065 @NonNull List<? extends IBoundModule> importedModules, 066 @NonNull IBindingContext bindingContext) { 067 super(importedModules); 068 this.bindingContext = bindingContext; 069 this.assemblyDefinitions = ObjectUtils.notNull(Lazy.of(() -> Arrays.stream(getAssemblyClasses()) 070 .map(clazz -> { 071 assert clazz != null; 072 return (IBoundDefinitionModelAssembly) ObjectUtils 073 .requireNonNull(bindingContext.getBoundDefinitionForClass(clazz)); 074 }) 075 .collect(Collectors.toUnmodifiableMap( 076 def -> def.getDefinitionQName().getIndexPosition(), 077 Function.identity())))); 078 this.fieldDefinitions = ObjectUtils.notNull(Lazy.of(() -> Arrays.stream(getFieldClasses()) 079 .map(clazz -> { 080 assert clazz != null; 081 return (IBoundDefinitionModelField<?>) ObjectUtils 082 .requireNonNull(bindingContext.getBoundDefinitionForClass(clazz)); 083 }) 084 .collect(Collectors.toUnmodifiableMap( 085 def -> def.getDefinitionQName().getIndexPosition(), 086 Function.identity())))); 087 this.staticContext = ObjectUtils.notNull(Lazy.of(() -> { 088 StaticContext.Builder builder = StaticContext.builder() 089 .defaultModelNamespace(getXmlNamespace()); 090 091 getNamespaceBindings() 092 .forEach( 093 (prefix, ns) -> builder.namespace( 094 ObjectUtils.requireNonNull(prefix), 095 ObjectUtils.requireNonNull(ns))); 096 return builder.build(); 097 })); 098 this.source = ISource.moduleSource(this); 099 } 100 101 @Override 102 public ISource getSource() { 103 return source; 104 } 105 106 @Override 107 public String getLocationHint() { 108 return ObjectUtils.notNull(getClass().getName()); 109 } 110 111 @Override 112 public StaticContext getModuleStaticContext() { 113 return ObjectUtils.notNull(staticContext.get()); 114 } 115 116 @Override 117 @NonNull 118 public IBindingContext getBindingContext() { 119 return bindingContext; 120 } 121 122 @Override 123 public Map<String, String> getNamespaceBindings() { 124 return ObjectUtils.notNull(Arrays.stream(getNsBindings()) 125 .collect(Collectors.toMap( 126 NsBinding::prefix, 127 NsBinding::uri, 128 (v1, v2) -> v2, 129 LinkedHashMap::new))); 130 } 131 132 /** 133 * Get the namespace binding annotations associated with this module. 134 * 135 * @return the namespace bindings array, or an empty array if none are defined 136 */ 137 @SuppressWarnings({ "null" }) 138 @NonNull 139 protected NsBinding[] getNsBindings() { 140 return getClass().isAnnotationPresent(MetaschemaModule.class) 141 ? getClass().getAnnotation(MetaschemaModule.class).nsBindings() 142 : (NsBinding[]) Array.newInstance(NsBinding.class, 0); 143 } 144 145 /** 146 * Get the assembly instance annotations associated with this bound choice 147 * group. 148 * 149 * @return the annotations 150 */ 151 @SuppressWarnings({ "null", "unchecked" }) 152 @NonNull 153 protected Class<? extends IBoundObject>[] getAssemblyClasses() { 154 return getClass().isAnnotationPresent(MetaschemaModule.class) 155 ? getClass().getAnnotation(MetaschemaModule.class).assemblies() 156 : (Class<? extends IBoundObject>[]) Array.newInstance(Class.class, 0); 157 } 158 159 /** 160 * Get the field instance annotations associated with this bound choice group. 161 * 162 * @return the annotations 163 */ 164 @SuppressWarnings({ "null", "unchecked" }) 165 @NonNull 166 protected Class<? extends IBoundObject>[] getFieldClasses() { 167 return getClass().isAnnotationPresent(MetaschemaModule.class) 168 ? getClass().getAnnotation(MetaschemaModule.class).fields() 169 : (Class<? extends IBoundObject>[]) Array.newInstance(Class.class, 0); 170 } 171 172 /** 173 * Get the mapping of assembly definition effective name to definition. 174 * 175 * @return the mapping 176 */ 177 protected Map<Integer, IBoundDefinitionModelAssembly> getAssemblyDefinitionMap() { 178 return assemblyDefinitions.get(); 179 } 180 181 @SuppressWarnings("null") 182 @Override 183 public Collection<IBoundDefinitionModelAssembly> getAssemblyDefinitions() { 184 return getAssemblyDefinitionMap().values(); 185 } 186 187 @Override 188 public IBoundDefinitionModelAssembly getAssemblyDefinitionByName(@NonNull Integer name) { 189 return getAssemblyDefinitionMap().get(name); 190 } 191 192 /** 193 * Get the mapping of field definition effective name to definition. 194 * 195 * @return the mapping 196 */ 197 protected Map<Integer, IBoundDefinitionModelField<?>> getFieldDefinitionMap() { 198 return fieldDefinitions.get(); 199 } 200 201 @SuppressWarnings("null") 202 @Override 203 public Collection<IBoundDefinitionModelField<?>> getFieldDefinitions() { 204 return getFieldDefinitionMap().values(); 205 } 206 207 @Override 208 public IBoundDefinitionModelField<?> getFieldDefinitionByName(@NonNull Integer name) { 209 return getFieldDefinitionMap().get(name); 210 } 211 212 @SuppressWarnings("null") 213 @Override 214 public Collection<IBoundDefinitionFlag> getFlagDefinitions() { 215 // Flags are always inline, so they do not have separate definitions 216 return Collections.emptyList(); 217 } 218 219 @Override 220 public IBoundDefinitionFlag getFlagDefinitionByName(@NonNull IEnhancedQName name) { 221 // Flags are always inline, so they do not have separate definitions 222 return null; 223 } 224}