001/* 002 * SPDX-FileCopyrightText: none 003 * SPDX-License-Identifier: CC0-1.0 004 */ 005 006package gov.nist.secauto.metaschema.databind.model; 007 008import gov.nist.secauto.metaschema.core.metapath.StaticContext; 009import gov.nist.secauto.metaschema.core.model.AbstractModule; 010import gov.nist.secauto.metaschema.core.model.IBoundObject; 011import gov.nist.secauto.metaschema.core.util.CollectionUtil; 012import gov.nist.secauto.metaschema.core.util.ObjectUtils; 013import gov.nist.secauto.metaschema.databind.IBindingContext; 014import gov.nist.secauto.metaschema.databind.model.annotations.MetaschemaModule; 015import gov.nist.secauto.metaschema.databind.model.annotations.NsBinding; 016 017import java.lang.reflect.Array; 018import java.lang.reflect.Constructor; 019import java.lang.reflect.InvocationTargetException; 020import java.util.ArrayList; 021import java.util.Arrays; 022import java.util.Collection; 023import java.util.Collections; 024import java.util.LinkedHashMap; 025import java.util.List; 026import java.util.Map; 027import java.util.function.Function; 028import java.util.stream.Collectors; 029 030import javax.xml.namespace.QName; 031 032import edu.umd.cs.findbugs.annotations.NonNull; 033import nl.talsmasoftware.lazy4j.Lazy; 034 035public abstract class AbstractBoundModule 036 extends AbstractModule< 037 IBoundModule, 038 IBoundDefinitionModelComplex, 039 IBoundDefinitionFlag, 040 IBoundDefinitionModelField<?>, 041 IBoundDefinitionModelAssembly> 042 implements IBoundModule { 043 @NonNull 044 private final IBindingContext bindingContext; 045 @NonNull 046 private final Lazy<Map<QName, IBoundDefinitionModelAssembly>> assemblyDefinitions; 047 @NonNull 048 private final Lazy<Map<QName, IBoundDefinitionModelField<?>>> fieldDefinitions; 049 @NonNull 050 private final Lazy<StaticContext> staticContext; 051 052 /** 053 * Create a new Module instance for a given class annotated by the 054 * {@link MetaschemaModule} annotation. 055 * <p> 056 * Will also load any imported Metaschemas. 057 * 058 * 059 * @param clazz 060 * the Module class 061 * @param bindingContext 062 * the Module binding context 063 * @return the new Module instance 064 */ 065 @NonNull 066 public static IBoundModule createInstance( 067 @NonNull Class<? extends IBoundModule> clazz, 068 @NonNull IBindingContext bindingContext) { 069 070 if (!clazz.isAnnotationPresent(MetaschemaModule.class)) { 071 throw new IllegalStateException(String.format("The class '%s' is missing the '%s' annotation", 072 clazz.getCanonicalName(), MetaschemaModule.class.getCanonicalName())); 073 } 074 075 MetaschemaModule moduleAnnotation = clazz.getAnnotation(MetaschemaModule.class); 076 077 List<IBoundModule> importedModules; 078 if (moduleAnnotation.imports().length > 0) { 079 importedModules = new ArrayList<>(moduleAnnotation.imports().length); 080 for (Class<? extends IBoundModule> importClass : moduleAnnotation.imports()) { 081 assert importClass != null; 082 IBoundModule moduleImport = bindingContext.registerModule(importClass); 083 importedModules.add(moduleImport); 084 } 085 } else { 086 importedModules = CollectionUtil.emptyList(); 087 } 088 return createInstance(clazz, bindingContext, importedModules); 089 } 090 091 @NonNull 092 private static IBoundModule createInstance( 093 @NonNull Class<? extends IBoundModule> clazz, 094 @NonNull IBindingContext bindingContext, 095 @NonNull List<? extends IBoundModule> importedModules) { 096 097 Constructor<? extends IBoundModule> constructor; 098 try { 099 constructor = clazz.getDeclaredConstructor(List.class, IBindingContext.class); 100 } catch (NoSuchMethodException ex) { 101 throw new IllegalArgumentException(ex); 102 } 103 104 try { 105 return ObjectUtils.notNull(constructor.newInstance(importedModules, bindingContext)); 106 } catch (InstantiationException | IllegalAccessException | InvocationTargetException ex) { 107 throw new IllegalArgumentException(ex); 108 } 109 } 110 111 /** 112 * Construct a new Module instance. 113 * 114 * @param importedModules 115 * Module imports associated with the Metaschema module 116 * @param bindingContext 117 * the Module binding context 118 */ 119 protected AbstractBoundModule( 120 @NonNull List<? extends IBoundModule> importedModules, 121 @NonNull IBindingContext bindingContext) { 122 super(importedModules); 123 this.bindingContext = bindingContext; 124 this.assemblyDefinitions = ObjectUtils.notNull(Lazy.lazy(() -> Arrays.stream(getAssemblyClasses()) 125 .map(clazz -> { 126 assert clazz != null; 127 return (IBoundDefinitionModelAssembly) ObjectUtils 128 .requireNonNull(bindingContext.getBoundDefinitionForClass(clazz)); 129 }) 130 .collect(Collectors.toUnmodifiableMap( 131 IBoundDefinitionModelAssembly::getDefinitionQName, 132 Function.identity())))); 133 this.fieldDefinitions = ObjectUtils.notNull(Lazy.lazy(() -> Arrays.stream(getFieldClasses()) 134 .map(clazz -> { 135 assert clazz != null; 136 return (IBoundDefinitionModelField<?>) ObjectUtils 137 .requireNonNull(bindingContext.getBoundDefinitionForClass(clazz)); 138 }) 139 .collect(Collectors.toUnmodifiableMap( 140 IBoundDefinitionModelField::getDefinitionQName, 141 Function.identity())))); 142 this.staticContext = ObjectUtils.notNull(Lazy.lazy(() -> { 143 StaticContext.Builder builder = StaticContext.builder() 144 .defaultModelNamespace(getXmlNamespace()); 145 146 getNamespaceBindings() 147 .forEach( 148 (prefix, ns) -> builder.namespace( 149 ObjectUtils.requireNonNull(prefix), 150 ObjectUtils.requireNonNull(ns))); 151 return builder.build(); 152 })); 153 } 154 155 @Override 156 public StaticContext getModuleStaticContext() { 157 return ObjectUtils.notNull(staticContext.get()); 158 } 159 160 @Override 161 @NonNull 162 public IBindingContext getBindingContext() { 163 return bindingContext; 164 } 165 166 @Override 167 public Map<String, String> getNamespaceBindings() { 168 return ObjectUtils.notNull(Arrays.stream(getNsBindings()) 169 .collect(Collectors.toMap( 170 NsBinding::prefix, 171 NsBinding::uri, 172 (v1, v2) -> v2, 173 LinkedHashMap::new))); 174 } 175 176 @SuppressWarnings({ "null" }) 177 @NonNull 178 protected NsBinding[] getNsBindings() { 179 return getClass().isAnnotationPresent(MetaschemaModule.class) 180 ? getClass().getAnnotation(MetaschemaModule.class).nsBindings() 181 : (NsBinding[]) Array.newInstance(NsBinding.class, 0); 182 } 183 184 /** 185 * Get the assembly instance annotations associated with this bound choice 186 * group. 187 * 188 * @return the annotations 189 */ 190 @SuppressWarnings({ "null", "unchecked" }) 191 @NonNull 192 protected Class<? extends IBoundObject>[] getAssemblyClasses() { 193 return getClass().isAnnotationPresent(MetaschemaModule.class) 194 ? getClass().getAnnotation(MetaschemaModule.class).assemblies() 195 : (Class<? extends IBoundObject>[]) Array.newInstance(Class.class, 0); 196 } 197 198 /** 199 * Get the field instance annotations associated with this bound choice group. 200 * 201 * @return the annotations 202 */ 203 @SuppressWarnings({ "null", "unchecked" }) 204 @NonNull 205 protected Class<? extends IBoundObject>[] getFieldClasses() { 206 return getClass().isAnnotationPresent(MetaschemaModule.class) 207 ? getClass().getAnnotation(MetaschemaModule.class).fields() 208 : (Class<? extends IBoundObject>[]) Array.newInstance(Class.class, 0); 209 } 210 211 /** 212 * Get the mapping of assembly definition effective name to definition. 213 * 214 * @return the mapping 215 */ 216 protected Map<QName, IBoundDefinitionModelAssembly> getAssemblyDefinitionMap() { 217 return assemblyDefinitions.get(); 218 } 219 220 @SuppressWarnings("null") 221 @Override 222 public Collection<IBoundDefinitionModelAssembly> getAssemblyDefinitions() { 223 return getAssemblyDefinitionMap().values(); 224 } 225 226 @Override 227 public IBoundDefinitionModelAssembly getAssemblyDefinitionByName(@NonNull QName name) { 228 return getAssemblyDefinitionMap().get(name); 229 } 230 231 /** 232 * Get the mapping of field definition effective name to definition. 233 * 234 * @return the mapping 235 */ 236 protected Map<QName, IBoundDefinitionModelField<?>> getFieldDefinitionMap() { 237 return fieldDefinitions.get(); 238 } 239 240 @SuppressWarnings("null") 241 @Override 242 public Collection<IBoundDefinitionModelField<?>> getFieldDefinitions() { 243 return getFieldDefinitionMap().values(); 244 } 245 246 @Override 247 public IBoundDefinitionModelField<?> getFieldDefinitionByName(@NonNull QName name) { 248 return getFieldDefinitionMap().get(name); 249 } 250 251 @SuppressWarnings("null") 252 @Override 253 public Collection<IBoundDefinitionFlag> getFlagDefinitions() { 254 // Flags are always inline, so they do not have separate definitions 255 return Collections.emptyList(); 256 } 257 258 @Override 259 public IBoundDefinitionFlag getFlagDefinitionByName(@NonNull QName name) { 260 // Flags are always inline, so they do not have separate definitions 261 return null; 262 } 263}