001/*
002 * Portions of this software was developed by employees of the National Institute
003 * of Standards and Technology (NIST), an agency of the Federal Government and is
004 * being made available as a public service. Pursuant to title 17 United States
005 * Code Section 105, works of NIST employees are not subject to copyright
006 * protection in the United States. This software may be subject to foreign
007 * copyright. Permission in the United States and in foreign countries, to the
008 * extent that NIST may hold copyright, to use, copy, modify, create derivative
009 * works, and distribute this software and its documentation without fee is hereby
010 * granted on a non-exclusive basis, provided that this notice and disclaimer
011 * of warranty appears in all copies.
012 *
013 * THE SOFTWARE IS PROVIDED 'AS IS' WITHOUT ANY WARRANTY OF ANY KIND, EITHER
014 * EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT LIMITED TO, ANY WARRANTY
015 * THAT THE SOFTWARE WILL CONFORM TO SPECIFICATIONS, ANY IMPLIED WARRANTIES OF
016 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND FREEDOM FROM
017 * INFRINGEMENT, AND ANY WARRANTY THAT THE DOCUMENTATION WILL CONFORM TO THE
018 * SOFTWARE, OR ANY WARRANTY THAT THE SOFTWARE WILL BE ERROR FREE.  IN NO EVENT
019 * SHALL NIST BE LIABLE FOR ANY DAMAGES, INCLUDING, BUT NOT LIMITED TO, DIRECT,
020 * INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES, ARISING OUT OF, RESULTING FROM,
021 * OR IN ANY WAY CONNECTED WITH THIS SOFTWARE, WHETHER OR NOT BASED UPON WARRANTY,
022 * CONTRACT, TORT, OR OTHERWISE, WHETHER OR NOT INJURY WAS SUSTAINED BY PERSONS OR
023 * PROPERTY OR OTHERWISE, AND WHETHER OR NOT LOSS WAS SUSTAINED FROM, OR AROSE OUT
024 * OF THE RESULTS OF, OR USE OF, THE SOFTWARE OR SERVICES PROVIDED HEREUNDER.
025 */
026
027package gov.nist.secauto.metaschema.databind.model;
028
029import gov.nist.secauto.metaschema.core.model.AbstractModule;
030import gov.nist.secauto.metaschema.core.model.IAssemblyDefinition;
031import gov.nist.secauto.metaschema.core.model.IFieldDefinition;
032import gov.nist.secauto.metaschema.core.model.IFlagDefinition;
033import gov.nist.secauto.metaschema.core.model.IModule;
034import gov.nist.secauto.metaschema.core.util.CollectionUtil;
035import gov.nist.secauto.metaschema.core.util.ObjectUtils;
036import gov.nist.secauto.metaschema.databind.IBindingContext;
037import gov.nist.secauto.metaschema.databind.model.annotations.Module;
038
039import java.lang.reflect.Constructor;
040import java.lang.reflect.InvocationTargetException;
041import java.net.URI;
042import java.util.ArrayList;
043import java.util.Arrays;
044import java.util.Collection;
045import java.util.Collections;
046import java.util.List;
047import java.util.Map;
048import java.util.function.Function;
049import java.util.stream.Collectors;
050
051import edu.umd.cs.findbugs.annotations.NonNull;
052
053public abstract class AbstractBoundModule
054    extends AbstractModule {
055  @NonNull
056  private final IBindingContext bindingContext;
057  private Map<String, IAssemblyClassBinding> assemblyDefinitions;
058  private Map<String, IFieldClassBinding> fieldDefinitions;
059
060  /**
061   * Create a new Module instance for a given class annotated by the
062   * {@link Module} annotation.
063   * <p>
064   * Will also load any imported Metaschemas.
065   *
066   *
067   * @param clazz
068   *          the Module class
069   * @param bindingContext
070   *          the Module binding context
071   * @return the new Module instance
072   */
073  @NonNull
074  public static IModule createInstance(
075      @NonNull Class<? extends IModule> clazz,
076      @NonNull IBindingContext bindingContext) {
077
078    if (!clazz.isAnnotationPresent(Module.class)) {
079      throw new IllegalStateException(String.format("The class '%s' is missing the '%s' annotation",
080          clazz.getCanonicalName(), Module.class.getCanonicalName()));
081    }
082
083    Module moduleAnnotation = clazz.getAnnotation(Module.class);
084
085    List<IModule> importedModules;
086    if (moduleAnnotation.imports().length > 0) {
087      importedModules = new ArrayList<>(moduleAnnotation.imports().length);
088      for (Class<? extends IModule> importClass : moduleAnnotation.imports()) {
089        assert importClass != null;
090        IModule moduleImport = bindingContext.getModuleByClass(importClass);
091        importedModules.add(moduleImport);
092      }
093    } else {
094      importedModules = CollectionUtil.emptyList();
095    }
096    return createInstance(clazz, bindingContext, importedModules);
097  }
098
099  @NonNull
100  private static IModule createInstance(
101      @NonNull Class<? extends IModule> clazz,
102      @NonNull IBindingContext bindingContext,
103      @NonNull List<? extends IModule> importedModules) {
104
105    Constructor<? extends IModule> constructor;
106    try {
107      constructor = clazz.getDeclaredConstructor(List.class, IBindingContext.class);
108    } catch (NoSuchMethodException ex) {
109      throw new IllegalArgumentException(ex);
110    }
111
112    try {
113      return ObjectUtils.notNull(constructor.newInstance(importedModules, bindingContext));
114    } catch (InstantiationException | IllegalAccessException | InvocationTargetException ex) {
115      throw new IllegalArgumentException(ex);
116    }
117  }
118
119  /**
120   * Construct a new Module instance.
121   *
122   * @param importedModules
123   *          Module imports associated with the Metaschema module
124   * @param bindingContext
125   *          the Module binding context
126   */
127  protected AbstractBoundModule(
128      @NonNull List<? extends IModule> importedModules,
129      @NonNull IBindingContext bindingContext) {
130    super(importedModules);
131    this.bindingContext = bindingContext;
132  }
133
134  /**
135   * Get the Module binding context.
136   *
137   * @return the context
138   */
139  @NonNull
140  protected IBindingContext getBindingContext() {
141    return bindingContext;
142  }
143
144  @Override
145  public URI getLocation() { // NOPMD - intentional
146    // not known
147    return null;
148  }
149
150  @NonNull
151  protected Class<?>[] getAssemblyClasses() {
152    Class<?>[] retval;
153    if (getClass().isAnnotationPresent(Module.class)) {
154      Module moduleAnnotation = getClass().getAnnotation(Module.class);
155      retval = moduleAnnotation.assemblies();
156    } else {
157      retval = new Class<?>[] {};
158    }
159    return retval;
160  }
161
162  @NonNull
163  protected Class<?>[] getFieldClasses() {
164    Class<?>[] retval;
165    if (getClass().isAnnotationPresent(Module.class)) {
166      Module moduleAnnotation = getClass().getAnnotation(Module.class);
167      retval = moduleAnnotation.fields();
168    } else {
169      retval = new Class<?>[] {};
170    }
171    return retval;
172  }
173
174  protected void initDefinitions() {
175    synchronized (this) {
176      if (assemblyDefinitions == null) {
177        IBindingContext bindingContext = getBindingContext();
178        this.assemblyDefinitions = Arrays.stream(getAssemblyClasses())
179            .map(clazz -> {
180              assert clazz != null;
181              return (IAssemblyClassBinding) ObjectUtils.requireNonNull(bindingContext.getClassBinding(clazz));
182            })
183            .collect(Collectors.toUnmodifiableMap(
184                IAssemblyClassBinding::getName,
185                Function.identity()));
186        this.fieldDefinitions = Arrays.stream(getFieldClasses())
187            .map(clazz -> {
188              assert clazz != null;
189              return (IFieldClassBinding) ObjectUtils.requireNonNull(bindingContext.getClassBinding(clazz));
190            })
191            .collect(Collectors.toUnmodifiableMap(
192                IFieldClassBinding::getName,
193                Function.identity()));
194      }
195    }
196
197  }
198
199  @SuppressWarnings("null")
200  protected @NonNull Map<String, ? extends IAssemblyDefinition> getAssemblyDefinitionMap() {
201    initDefinitions();
202    return assemblyDefinitions;
203  }
204
205  @SuppressWarnings("null")
206  @Override
207  public Collection<? extends IAssemblyDefinition> getAssemblyDefinitions() {
208    return getAssemblyDefinitionMap().values();
209  }
210
211  @Override
212  public IAssemblyDefinition getAssemblyDefinitionByName(@NonNull String name) {
213    return getAssemblyDefinitionMap().get(name);
214  }
215
216  protected Map<String, ? extends IFieldDefinition> getFieldDefinitionMap() {
217    initDefinitions();
218    return fieldDefinitions;
219  }
220
221  @SuppressWarnings("null")
222  @Override
223  public Collection<? extends IFieldDefinition> getFieldDefinitions() {
224    return getFieldDefinitionMap().values();
225  }
226
227  @Override
228  public IFieldDefinition getFieldDefinitionByName(@NonNull String name) {
229    return getFieldDefinitionMap().get(name);
230  }
231
232  @NonNull
233  public Map<String, ? extends IFlagDefinition> getFlagDefinitionMap() {
234    // FlagContainer are always inline
235    return CollectionUtil.emptyMap();
236  }
237
238  @SuppressWarnings("null")
239  @Override
240  public Collection<? extends IFlagDefinition> getFlagDefinitions() {
241    // FlagContainer are always inline
242    return Collections.emptyList();
243  }
244
245  @Override
246  public IFlagDefinition getFlagDefinitionByName(@NonNull String name) { // NOPMD - intentional
247    // FlagContainer are always inline
248    return null;
249  }
250}