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.core.model;
028
029import gov.nist.secauto.metaschema.core.util.CollectionUtil;
030import gov.nist.secauto.metaschema.core.util.CustomCollectors;
031import gov.nist.secauto.metaschema.core.util.ObjectUtils;
032
033import org.apache.logging.log4j.LogManager;
034import org.apache.logging.log4j.Logger;
035
036import java.util.Collection;
037import java.util.List;
038import java.util.Locale;
039import java.util.Map;
040import java.util.function.Function;
041import java.util.function.Predicate;
042import java.util.stream.Collectors;
043import java.util.stream.Stream;
044
045import edu.umd.cs.findbugs.annotations.NonNull;
046import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
047
048/**
049 * Provides a common, abstract implementation of a {@link IModule}.
050 */
051@SuppressWarnings("PMD.CouplingBetweenObjects")
052public abstract class AbstractModule
053    implements IModule {
054  private static final Logger LOGGER = LogManager.getLogger(AbstractModule.class);
055
056  @NonNull
057  private final List<? extends IModule> importedModules;
058  private Map<String, IFlagDefinition> exportedFlagDefinitions;
059  private Map<String, IFieldDefinition> exportedFieldDefinitions;
060  private Map<String, IAssemblyDefinition> exportedAssemblyDefinitions;
061
062  /**
063   * Construct a new Metaschema module object.
064   *
065   * @param importedModules
066   *          the collection of Metaschema module objects this Metaschema module
067   *          imports
068   */
069  public AbstractModule(@NonNull List<? extends IModule> importedModules) {
070    this.importedModules
071        = CollectionUtil.unmodifiableList(ObjectUtils.requireNonNull(importedModules, "importedModules"));
072  }
073
074  @Override
075  @SuppressFBWarnings(value = "EI_EXPOSE_REP", justification = "interface doesn't allow modification")
076  public List<? extends IModule> getImportedModules() {
077    return importedModules;
078  }
079
080  private Map<String, ? extends IModule> getImportedModulesByShortName() {
081    return importedModules.stream().collect(Collectors.toMap(IModule::getShortName, Function.identity()));
082  }
083
084  @Override
085  public IModule getImportedModuleByShortName(String name) {
086    return getImportedModulesByShortName().get(name);
087  }
088
089  @SuppressWarnings("null")
090  @Override
091  public Collection<? extends IFlagDefinition> getExportedFlagDefinitions() {
092    return getExportedFlagDefinitionMap().values();
093  }
094
095  @Override
096  public IFlagDefinition getExportedFlagDefinitionByName(String name) {
097    return getExportedFlagDefinitionMap().get(name);
098  }
099
100  private Map<String, ? extends IFlagDefinition> getExportedFlagDefinitionMap() {
101    initExports();
102    return exportedFlagDefinitions;
103  }
104
105  @SuppressWarnings("null")
106  @Override
107  public Collection<? extends IFieldDefinition> getExportedFieldDefinitions() {
108    return getExportedFieldDefinitionMap().values();
109  }
110
111  @Override
112  public IFieldDefinition getExportedFieldDefinitionByName(String name) {
113    return getExportedFieldDefinitionMap().get(name);
114  }
115
116  private Map<String, ? extends IFieldDefinition> getExportedFieldDefinitionMap() {
117    initExports();
118    return exportedFieldDefinitions;
119  }
120
121  @SuppressWarnings("null")
122  @Override
123  public Collection<? extends IAssemblyDefinition> getExportedAssemblyDefinitions() {
124    return getExportedAssemblyDefinitionMap().values();
125  }
126
127  @Override
128  public IAssemblyDefinition getExportedAssemblyDefinitionByName(String name) {
129    return getExportedAssemblyDefinitionMap().get(name);
130  }
131
132  private Map<String, ? extends IAssemblyDefinition> getExportedAssemblyDefinitionMap() {
133    initExports();
134    return exportedAssemblyDefinitions;
135  }
136
137  /**
138   * Processes the definitions exported by the Metaschema, saving a list of all
139   * exported by specific model types.
140   */
141  protected void initExports() {
142    synchronized (this) {
143      if (exportedFlagDefinitions == null) {
144        // Populate the stream with the definitions from this module
145        Predicate<IDefinition> filter = IModule.allNonLocalDefinitions();
146        Stream<? extends IFlagDefinition> flags = getFlagDefinitions().stream()
147            .filter(filter);
148        Stream<? extends IFieldDefinition> fields = getFieldDefinitions().stream()
149            .filter(filter);
150        Stream<? extends IAssemblyDefinition> assemblies = getAssemblyDefinitions().stream()
151            .filter(filter);
152
153        // handle definitions from any included module
154        if (!getImportedModules().isEmpty()) {
155          Stream<? extends IFlagDefinition> importedFlags = Stream.empty();
156          Stream<? extends IFieldDefinition> importedFields = Stream.empty();
157          Stream<? extends IAssemblyDefinition> importedAssemblies = Stream.empty();
158
159          for (IModule module : getImportedModules()) {
160            importedFlags = Stream.concat(importedFlags, module.getExportedFlagDefinitions().stream());
161            importedFields = Stream.concat(importedFields, module.getExportedFieldDefinitions().stream());
162            importedAssemblies
163                = Stream.concat(importedAssemblies, module.getExportedAssemblyDefinitions().stream());
164          }
165
166          flags = Stream.concat(importedFlags, flags);
167          fields = Stream.concat(importedFields, fields);
168          assemblies = Stream.concat(importedAssemblies, assemblies);
169        }
170
171        // Build the maps. Definitions from this module will take priority, with
172        // shadowing being reported when a definition from this module has the same name
173        // as an imported one
174        Map<String, IFlagDefinition> exportedFlagDefinitions = flags.collect(
175            CustomCollectors.toMap(
176                IFlagDefinition::getName,
177                CustomCollectors.identity(),
178                AbstractModule::handleShadowedDefinitions));
179        Map<String, IFieldDefinition> exportedFieldDefinitions = fields.collect(
180            CustomCollectors.toMap(
181                IFieldDefinition::getName,
182                CustomCollectors.identity(),
183                AbstractModule::handleShadowedDefinitions));
184        Map<String, IAssemblyDefinition> exportedAssemblyDefinitions = assemblies.collect(
185            CustomCollectors.toMap(
186                IAssemblyDefinition::getName,
187                CustomCollectors.identity(),
188                AbstractModule::handleShadowedDefinitions));
189
190        this.exportedFlagDefinitions = exportedFlagDefinitions.isEmpty()
191            ? CollectionUtil.emptyMap()
192            : CollectionUtil.unmodifiableMap(exportedFlagDefinitions);
193        this.exportedFieldDefinitions = exportedFieldDefinitions.isEmpty()
194            ? CollectionUtil.emptyMap()
195            : CollectionUtil.unmodifiableMap(exportedFieldDefinitions);
196        this.exportedAssemblyDefinitions = exportedAssemblyDefinitions.isEmpty()
197            ? CollectionUtil.emptyMap()
198            : CollectionUtil.unmodifiableMap(exportedAssemblyDefinitions);
199      }
200    }
201  }
202
203  @SuppressWarnings({ "unused", "PMD.UnusedPrivateMethod" }) // used by lambda
204  private static <DEF extends IDefinition> DEF handleShadowedDefinitions(
205      @SuppressWarnings("unused") @NonNull String key, @NonNull DEF oldDef, @NonNull DEF newDef) {
206    if (!oldDef.equals(newDef) && LOGGER.isWarnEnabled()) {
207      LOGGER.warn("The {} '{}' from metaschema '{}' is shadowing '{}' from metaschema '{}'",
208          newDef.getModelType().name().toLowerCase(Locale.ROOT),
209          newDef.getName(),
210          newDef.getContainingModule().getShortName(),
211          oldDef.getName(),
212          oldDef.getContainingModule().getShortName());
213    }
214    return newDef;
215  }
216}