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}