1   /*
2    * SPDX-FileCopyrightText: none
3    * SPDX-License-Identifier: CC0-1.0
4    */
5   
6   package gov.nist.secauto.metaschema.core.model;
7   
8   import gov.nist.secauto.metaschema.core.qname.IEnhancedQName;
9   import gov.nist.secauto.metaschema.core.util.CollectionUtil;
10  import gov.nist.secauto.metaschema.core.util.CustomCollectors;
11  import gov.nist.secauto.metaschema.core.util.ObjectUtils;
12  
13  import org.apache.logging.log4j.LogManager;
14  import org.apache.logging.log4j.Logger;
15  
16  import java.util.Collection;
17  import java.util.List;
18  import java.util.Locale;
19  import java.util.Map;
20  import java.util.function.Function;
21  import java.util.function.Predicate;
22  import java.util.stream.Collectors;
23  import java.util.stream.Stream;
24  
25  import edu.umd.cs.findbugs.annotations.NonNull;
26  import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
27  import nl.talsmasoftware.lazy4j.Lazy;
28  
29  /**
30   * Provides a common, abstract implementation of a {@link IModule}.
31   *
32   * @param <M>
33   *          the imported module Java type
34   * @param <D>
35   *          the definition Java type
36   * @param <FL>
37   *          the flag definition Java type
38   * @param <FI>
39   *          the field definition Java type
40   * @param <A>
41   *          the assembly definition Java type
42   */
43  @SuppressWarnings("PMD.CouplingBetweenObjects")
44  public abstract class AbstractModule<
45      M extends IModuleExtended<M, D, FL, FI, A>,
46      D extends IModelDefinition,
47      FL extends IFlagDefinition,
48      FI extends IFieldDefinition,
49      A extends IAssemblyDefinition>
50      implements IModuleExtended<M, D, FL, FI, A> {
51    private static final Logger LOGGER = LogManager.getLogger(AbstractModule.class);
52  
53    @NonNull
54    private final List<? extends M> importedModules;
55    @NonNull
56    private final Lazy<Exports> exports;
57    @NonNull
58    private final Lazy<IEnhancedQName> qname;
59  
60    /**
61     * Construct a new Metaschema module object.
62     *
63     * @param importedModules
64     *          the collection of Metaschema module objects this Metaschema module
65     *          imports
66     */
67    public AbstractModule(@NonNull List<? extends M> importedModules) {
68      this.importedModules
69          = CollectionUtil.unmodifiableList(ObjectUtils.requireNonNull(importedModules, "importedModules"));
70      this.exports = ObjectUtils.notNull(Lazy.of(() -> new Exports(importedModules)));
71      this.qname = ObjectUtils.notNull(Lazy.of(() -> IEnhancedQName.of(getXmlNamespace(), getShortName())));
72    }
73  
74    @Override
75    public IEnhancedQName getQName() {
76      return ObjectUtils.notNull(qname.get());
77    }
78  
79    @Override
80    @SuppressFBWarnings(value = "EI_EXPOSE_REP", justification = "interface doesn't allow modification")
81    public List<? extends M> getImportedModules() {
82      return importedModules;
83    }
84  
85    @SuppressWarnings("null")
86    @NonNull
87    private Exports getExports() {
88      return exports.get();
89    }
90  
91    private Map<String, ? extends M> getImportedModulesByShortName() {
92      return importedModules.stream().collect(Collectors.toMap(IModule::getShortName, Function.identity()));
93    }
94  
95    @Override
96    public M getImportedModuleByShortName(String name) {
97      return getImportedModulesByShortName().get(name);
98    }
99  
100   @SuppressWarnings("null")
101   @Override
102   public Collection<FL> getExportedFlagDefinitions() {
103     return getExports().getExportedFlagDefinitionMap().values();
104   }
105 
106   @Override
107   public FL getExportedFlagDefinitionByName(IEnhancedQName name) {
108     return getExports().getExportedFlagDefinitionMap().get(name);
109   }
110 
111   @SuppressWarnings("null")
112   @Override
113   public Collection<FI> getExportedFieldDefinitions() {
114     return getExports().getExportedFieldDefinitionMap().values();
115   }
116 
117   @Override
118   public FI getExportedFieldDefinitionByName(Integer name) {
119     return getExports().getExportedFieldDefinitionMap().get(name);
120   }
121 
122   @SuppressWarnings("null")
123   @Override
124   public Collection<A> getExportedAssemblyDefinitions() {
125     return getExports().getExportedAssemblyDefinitionMap().values();
126   }
127 
128   @Override
129   public A getExportedAssemblyDefinitionByName(Integer name) {
130     return getExports().getExportedAssemblyDefinitionMap().get(name);
131   }
132 
133   @Override
134   public A getExportedRootAssemblyDefinitionByName(Integer name) {
135     return getExports().getExportedRootAssemblyDefinitionMap().get(name);
136   }
137 
138   @SuppressWarnings({ "unused", "PMD.UnusedPrivateMethod" }) // used by lambda
139   private static <DEF extends IDefinition> DEF handleShadowedDefinitions(
140       @NonNull IEnhancedQName key,
141       @NonNull DEF oldDef,
142       @NonNull DEF newDef) {
143     if (!oldDef.equals(newDef) && LOGGER.isWarnEnabled()) {
144       LOGGER.warn("The {} '{}' from metaschema '{}' is shadowing '{}' from metaschema '{}'",
145           newDef.getModelType().name().toLowerCase(Locale.ROOT),
146           newDef.getName(),
147           newDef.getContainingModule().getShortName(),
148           oldDef.getName(),
149           oldDef.getContainingModule().getShortName());
150     }
151     return newDef;
152   }
153 
154   @SuppressWarnings({ "unused", "PMD.UnusedPrivateMethod" }) // used by lambda
155   private static <DEF extends IDefinition> DEF handleShadowedDefinitions(
156       @NonNull Integer key,
157       @NonNull DEF oldDef,
158       @NonNull DEF newDef) {
159     if (!oldDef.equals(newDef) && LOGGER.isWarnEnabled()) {
160       LOGGER.warn("The {} '{}' from metaschema '{}' is shadowing '{}' from metaschema '{}'",
161           newDef.getModelType().name().toLowerCase(Locale.ROOT),
162           newDef.getName(),
163           newDef.getContainingModule().getShortName(),
164           oldDef.getName(),
165           oldDef.getContainingModule().getShortName());
166     }
167     return newDef;
168   }
169 
170   private class Exports {
171     @NonNull
172     private final Map<IEnhancedQName, FL> exportedFlagDefinitions;
173     @NonNull
174     private final Map<Integer, FI> exportedFieldDefinitions;
175     @NonNull
176     private final Map<Integer, A> exportedAssemblyDefinitions;
177     @NonNull
178     private final Map<Integer, A> exportedRootAssemblyDefinitions;
179 
180     @SuppressWarnings({ "PMD.ConstructorCallsOverridableMethod", "synthetic-access" })
181     public Exports(@NonNull List<? extends M> importedModules) {
182       // Populate the stream with the definitions from this module
183       Predicate<IDefinition> filter = IModuleExtended.allNonLocalDefinitions();
184       Stream<FL> flags = getFlagDefinitions().stream()
185           .filter(filter);
186       Stream<FI> fields = getFieldDefinitions().stream()
187           .filter(filter);
188       Stream<A> assemblies = getAssemblyDefinitions().stream()
189           .filter(filter);
190 
191       // handle definitions from any included module
192       if (!importedModules.isEmpty()) {
193         Stream<FL> importedFlags = Stream.empty();
194         Stream<FI> importedFields = Stream.empty();
195         Stream<A> importedAssemblies = Stream.empty();
196 
197         for (M module : importedModules) {
198           importedFlags = Stream.concat(importedFlags, module.getExportedFlagDefinitions().stream());
199           importedFields = Stream.concat(importedFields, module.getExportedFieldDefinitions().stream());
200           importedAssemblies
201               = Stream.concat(importedAssemblies, module.getExportedAssemblyDefinitions().stream());
202         }
203 
204         flags = Stream.concat(importedFlags, flags);
205         fields = Stream.concat(importedFields, fields);
206         assemblies = Stream.concat(importedAssemblies, assemblies);
207       }
208 
209       // Build the maps. Definitions from this module will take priority, with
210       // shadowing being reported when a definition from this module has the same name
211       // as an imported one
212       Map<IEnhancedQName, FL> exportedFlagDefinitions = flags.collect(
213           CustomCollectors.toMap(
214               IFlagDefinition::getDefinitionQName,
215               CustomCollectors.identity(),
216               AbstractModule::handleShadowedDefinitions));
217       Map<Integer, FI> exportedFieldDefinitions = fields.collect(
218           CustomCollectors.toMap(
219               def -> def.getDefinitionQName().getIndexPosition(),
220               CustomCollectors.identity(),
221               AbstractModule::handleShadowedDefinitions));
222       Map<Integer, A> exportedAssemblyDefinitions = assemblies.collect(
223           CustomCollectors.toMap(
224               def -> def.getDefinitionQName().getIndexPosition(),
225               CustomCollectors.identity(),
226               AbstractModule::handleShadowedDefinitions));
227 
228       this.exportedFlagDefinitions = exportedFlagDefinitions.isEmpty()
229           ? CollectionUtil.emptyMap()
230           : CollectionUtil.unmodifiableMap(exportedFlagDefinitions);
231       this.exportedFieldDefinitions = exportedFieldDefinitions.isEmpty()
232           ? CollectionUtil.emptyMap()
233           : CollectionUtil.unmodifiableMap(exportedFieldDefinitions);
234       this.exportedAssemblyDefinitions = exportedAssemblyDefinitions.isEmpty()
235           ? CollectionUtil.emptyMap()
236           : CollectionUtil.unmodifiableMap(exportedAssemblyDefinitions);
237       this.exportedRootAssemblyDefinitions = exportedAssemblyDefinitions.isEmpty()
238           ? CollectionUtil.emptyMap()
239           : CollectionUtil.unmodifiableMap(ObjectUtils.notNull(exportedAssemblyDefinitions.values().stream()
240               .filter(IAssemblyDefinition::isRoot)
241               .collect(CustomCollectors.toMap(
242                   def -> def.getRootQName().getIndexPosition(),
243                   CustomCollectors.identity(),
244                   AbstractModule::handleShadowedDefinitions))));
245     }
246 
247     @NonNull
248     public Map<IEnhancedQName, FL> getExportedFlagDefinitionMap() {
249       return this.exportedFlagDefinitions;
250     }
251 
252     @NonNull
253     public Map<Integer, FI> getExportedFieldDefinitionMap() {
254       return this.exportedFieldDefinitions;
255     }
256 
257     @NonNull
258     public Map<Integer, A> getExportedAssemblyDefinitionMap() {
259       return this.exportedAssemblyDefinitions;
260     }
261 
262     @NonNull
263     public Map<Integer, A> getExportedRootAssemblyDefinitionMap() {
264       return this.exportedRootAssemblyDefinitions;
265     }
266   }
267 }