1   /*
2    * SPDX-FileCopyrightText: none
3    * SPDX-License-Identifier: CC0-1.0
4    */
5   
6   package gov.nist.secauto.metaschema.databind.model;
7   
8   import gov.nist.secauto.metaschema.core.metapath.StaticContext;
9   import gov.nist.secauto.metaschema.core.model.AbstractModule;
10  import gov.nist.secauto.metaschema.core.model.IBoundObject;
11  import gov.nist.secauto.metaschema.core.util.CollectionUtil;
12  import gov.nist.secauto.metaschema.core.util.ObjectUtils;
13  import gov.nist.secauto.metaschema.databind.IBindingContext;
14  import gov.nist.secauto.metaschema.databind.model.annotations.MetaschemaModule;
15  import gov.nist.secauto.metaschema.databind.model.annotations.NsBinding;
16  
17  import java.lang.reflect.Array;
18  import java.lang.reflect.Constructor;
19  import java.lang.reflect.InvocationTargetException;
20  import java.util.ArrayList;
21  import java.util.Arrays;
22  import java.util.Collection;
23  import java.util.Collections;
24  import java.util.LinkedHashMap;
25  import java.util.List;
26  import java.util.Map;
27  import java.util.function.Function;
28  import java.util.stream.Collectors;
29  
30  import javax.xml.namespace.QName;
31  
32  import edu.umd.cs.findbugs.annotations.NonNull;
33  import nl.talsmasoftware.lazy4j.Lazy;
34  
35  public abstract class AbstractBoundModule
36      extends AbstractModule<
37          IBoundModule,
38          IBoundDefinitionModelComplex,
39          IBoundDefinitionFlag,
40          IBoundDefinitionModelField<?>,
41          IBoundDefinitionModelAssembly>
42      implements IBoundModule {
43    @NonNull
44    private final IBindingContext bindingContext;
45    @NonNull
46    private final Lazy<Map<QName, IBoundDefinitionModelAssembly>> assemblyDefinitions;
47    @NonNull
48    private final Lazy<Map<QName, IBoundDefinitionModelField<?>>> fieldDefinitions;
49    @NonNull
50    private final Lazy<StaticContext> staticContext;
51  
52    /**
53     * Create a new Module instance for a given class annotated by the
54     * {@link MetaschemaModule} annotation.
55     * <p>
56     * Will also load any imported Metaschemas.
57     *
58     *
59     * @param clazz
60     *          the Module class
61     * @param bindingContext
62     *          the Module binding context
63     * @return the new Module instance
64     */
65    @NonNull
66    public static IBoundModule createInstance(
67        @NonNull Class<? extends IBoundModule> clazz,
68        @NonNull IBindingContext bindingContext) {
69  
70      if (!clazz.isAnnotationPresent(MetaschemaModule.class)) {
71        throw new IllegalStateException(String.format("The class '%s' is missing the '%s' annotation",
72            clazz.getCanonicalName(), MetaschemaModule.class.getCanonicalName()));
73      }
74  
75      MetaschemaModule moduleAnnotation = clazz.getAnnotation(MetaschemaModule.class);
76  
77      List<IBoundModule> importedModules;
78      if (moduleAnnotation.imports().length > 0) {
79        importedModules = new ArrayList<>(moduleAnnotation.imports().length);
80        for (Class<? extends IBoundModule> importClass : moduleAnnotation.imports()) {
81          assert importClass != null;
82          IBoundModule moduleImport = bindingContext.registerModule(importClass);
83          importedModules.add(moduleImport);
84        }
85      } else {
86        importedModules = CollectionUtil.emptyList();
87      }
88      return createInstance(clazz, bindingContext, importedModules);
89    }
90  
91    @NonNull
92    private static IBoundModule createInstance(
93        @NonNull Class<? extends IBoundModule> clazz,
94        @NonNull IBindingContext bindingContext,
95        @NonNull List<? extends IBoundModule> importedModules) {
96  
97      Constructor<? extends IBoundModule> constructor;
98      try {
99        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 }