001/* 002 * SPDX-FileCopyrightText: none 003 * SPDX-License-Identifier: CC0-1.0 004 */ 005 006package dev.metaschema.core.model; 007 008import org.apache.logging.log4j.LogManager; 009import org.apache.logging.log4j.Logger; 010 011import java.util.Collection; 012import java.util.List; 013import java.util.Locale; 014import java.util.Map; 015import java.util.function.Function; 016import java.util.function.Predicate; 017import java.util.stream.Collectors; 018import java.util.stream.Stream; 019 020import dev.metaschema.core.qname.IEnhancedQName; 021import dev.metaschema.core.util.CollectionUtil; 022import dev.metaschema.core.util.CustomCollectors; 023import dev.metaschema.core.util.ObjectUtils; 024import edu.umd.cs.findbugs.annotations.NonNull; 025import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 026import nl.talsmasoftware.lazy4j.Lazy; 027 028/** 029 * Provides a common, abstract implementation of a {@link IModule}. 030 * 031 * @param <M> 032 * the imported module Java type 033 * @param <D> 034 * the definition Java type 035 * @param <FL> 036 * the flag definition Java type 037 * @param <FI> 038 * the field definition Java type 039 * @param <A> 040 * the assembly definition Java type 041 */ 042@SuppressWarnings("PMD.CouplingBetweenObjects") 043public abstract class AbstractModule< 044 M extends IModuleExtended<M, D, FL, FI, A>, 045 D extends IModelDefinition, 046 FL extends IFlagDefinition, 047 FI extends IFieldDefinition, 048 A extends IAssemblyDefinition> 049 implements IModuleExtended<M, D, FL, FI, A> { 050 private static final Logger LOGGER = LogManager.getLogger(AbstractModule.class); 051 052 @NonNull 053 private final List<? extends M> importedModules; 054 @NonNull 055 private final Lazy<Exports> exports; 056 @NonNull 057 private final Lazy<IEnhancedQName> qname; 058 059 /** 060 * Construct a new Metaschema module object. 061 * 062 * @param importedModules 063 * the collection of Metaschema module objects this Metaschema module 064 * imports 065 */ 066 public AbstractModule(@NonNull List<? extends M> importedModules) { 067 this.importedModules 068 = CollectionUtil.unmodifiableList(ObjectUtils.requireNonNull(importedModules, "importedModules")); 069 this.exports = ObjectUtils.notNull(Lazy.of(() -> new Exports(importedModules))); 070 this.qname = ObjectUtils.notNull(Lazy.of(() -> IEnhancedQName.of(getXmlNamespace(), getShortName()))); 071 } 072 073 @Override 074 public IEnhancedQName getQName() { 075 return ObjectUtils.notNull(qname.get()); 076 } 077 078 @Override 079 @SuppressFBWarnings(value = "EI_EXPOSE_REP", justification = "interface doesn't allow modification") 080 public List<? extends M> getImportedModules() { 081 return importedModules; 082 } 083 084 @SuppressWarnings("null") 085 @NonNull 086 private Exports getExports() { 087 return exports.get(); 088 } 089 090 private Map<String, ? extends M> getImportedModulesByShortName() { 091 return importedModules.stream().collect(Collectors.toMap(IModule::getShortName, Function.identity())); 092 } 093 094 @Override 095 public M getImportedModuleByShortName(String name) { 096 return getImportedModulesByShortName().get(name); 097 } 098 099 @SuppressWarnings("null") 100 @Override 101 public Collection<FL> getExportedFlagDefinitions() { 102 return getExports().getExportedFlagDefinitionMap().values(); 103 } 104 105 @Override 106 public FL getExportedFlagDefinitionByName(IEnhancedQName name) { 107 return getExports().getExportedFlagDefinitionMap().get(name); 108 } 109 110 @SuppressWarnings("null") 111 @Override 112 public Collection<FI> getExportedFieldDefinitions() { 113 return getExports().getExportedFieldDefinitionMap().values(); 114 } 115 116 @Override 117 public FI getExportedFieldDefinitionByName(Integer name) { 118 return getExports().getExportedFieldDefinitionMap().get(name); 119 } 120 121 @SuppressWarnings("null") 122 @Override 123 public Collection<A> getExportedAssemblyDefinitions() { 124 return getExports().getExportedAssemblyDefinitionMap().values(); 125 } 126 127 @Override 128 public A getExportedAssemblyDefinitionByName(Integer name) { 129 return getExports().getExportedAssemblyDefinitionMap().get(name); 130 } 131 132 @Override 133 public A getExportedRootAssemblyDefinitionByName(Integer name) { 134 return getExports().getExportedRootAssemblyDefinitionMap().get(name); 135 } 136 137 @SuppressWarnings("unused") // used by lambda 138 private static <DEF extends IDefinition> DEF handleShadowedDefinitions( 139 @NonNull IEnhancedQName key, 140 @NonNull DEF oldDef, 141 @NonNull DEF newDef) { 142 if (!oldDef.equals(newDef) && LOGGER.isWarnEnabled()) { 143 LOGGER.warn("The {} '{}' from metaschema '{}' is shadowing '{}' from metaschema '{}'", 144 newDef.getModelType().name().toLowerCase(Locale.ROOT), 145 newDef.getName(), 146 newDef.getContainingModule().getShortName(), 147 oldDef.getName(), 148 oldDef.getContainingModule().getShortName()); 149 } 150 return newDef; 151 } 152 153 @SuppressWarnings("unused") // used by lambda 154 private static <DEF extends IDefinition> DEF handleShadowedDefinitions( 155 @NonNull Integer key, 156 @NonNull DEF oldDef, 157 @NonNull DEF newDef) { 158 if (!oldDef.equals(newDef) && LOGGER.isWarnEnabled()) { 159 LOGGER.warn("The {} '{}' from metaschema '{}' is shadowing '{}' from metaschema '{}'", 160 newDef.getModelType().name().toLowerCase(Locale.ROOT), 161 newDef.getName(), 162 newDef.getContainingModule().getShortName(), 163 oldDef.getName(), 164 oldDef.getContainingModule().getShortName()); 165 } 166 return newDef; 167 } 168 169 private class Exports { 170 @NonNull 171 private final Map<IEnhancedQName, FL> exportedFlagDefinitions; 172 @NonNull 173 private final Map<Integer, FI> exportedFieldDefinitions; 174 @NonNull 175 private final Map<Integer, A> exportedAssemblyDefinitions; 176 @NonNull 177 private final Map<Integer, A> exportedRootAssemblyDefinitions; 178 179 @SuppressWarnings({ "PMD.ConstructorCallsOverridableMethod", "synthetic-access" }) 180 public Exports(@NonNull List<? extends M> importedModules) { 181 // Populate the stream with the definitions from this module 182 Predicate<IDefinition> filter = IModuleExtended.allNonLocalDefinitions(); 183 Stream<FL> flags = getFlagDefinitions().stream() 184 .filter(filter); 185 Stream<FI> fields = getFieldDefinitions().stream() 186 .filter(filter); 187 Stream<A> assemblies = getAssemblyDefinitions().stream() 188 .filter(filter); 189 190 // handle definitions from any included module 191 if (!importedModules.isEmpty()) { 192 Stream<FL> importedFlags = Stream.empty(); 193 Stream<FI> importedFields = Stream.empty(); 194 Stream<A> importedAssemblies = Stream.empty(); 195 196 for (M module : importedModules) { 197 importedFlags = Stream.concat(importedFlags, module.getExportedFlagDefinitions().stream()); 198 importedFields = Stream.concat(importedFields, module.getExportedFieldDefinitions().stream()); 199 importedAssemblies 200 = Stream.concat(importedAssemblies, module.getExportedAssemblyDefinitions().stream()); 201 } 202 203 flags = Stream.concat(importedFlags, flags); 204 fields = Stream.concat(importedFields, fields); 205 assemblies = Stream.concat(importedAssemblies, assemblies); 206 } 207 208 // Build the maps. Definitions from this module will take priority, with 209 // shadowing being reported when a definition from this module has the same name 210 // as an imported one 211 Map<IEnhancedQName, FL> exportedFlagDefinitions = flags.collect( 212 CustomCollectors.toMap( 213 IFlagDefinition::getDefinitionQName, 214 CustomCollectors.identity(), 215 AbstractModule::handleShadowedDefinitions)); 216 Map<Integer, FI> exportedFieldDefinitions = fields.collect( 217 CustomCollectors.toMap( 218 def -> def.getDefinitionQName().getIndexPosition(), 219 CustomCollectors.identity(), 220 AbstractModule::handleShadowedDefinitions)); 221 Map<Integer, A> exportedAssemblyDefinitions = assemblies.collect( 222 CustomCollectors.toMap( 223 def -> def.getDefinitionQName().getIndexPosition(), 224 CustomCollectors.identity(), 225 AbstractModule::handleShadowedDefinitions)); 226 227 this.exportedFlagDefinitions = exportedFlagDefinitions.isEmpty() 228 ? CollectionUtil.emptyMap() 229 : CollectionUtil.unmodifiableMap(exportedFlagDefinitions); 230 this.exportedFieldDefinitions = exportedFieldDefinitions.isEmpty() 231 ? CollectionUtil.emptyMap() 232 : CollectionUtil.unmodifiableMap(exportedFieldDefinitions); 233 this.exportedAssemblyDefinitions = exportedAssemblyDefinitions.isEmpty() 234 ? CollectionUtil.emptyMap() 235 : CollectionUtil.unmodifiableMap(exportedAssemblyDefinitions); 236 this.exportedRootAssemblyDefinitions = exportedAssemblyDefinitions.isEmpty() 237 ? CollectionUtil.emptyMap() 238 : CollectionUtil.unmodifiableMap(ObjectUtils.notNull(exportedAssemblyDefinitions.values().stream() 239 .filter(IAssemblyDefinition::isRoot) 240 .collect(CustomCollectors.toMap( 241 def -> def.getRootQName().getIndexPosition(), 242 CustomCollectors.identity(), 243 AbstractModule::handleShadowedDefinitions)))); 244 } 245 246 @NonNull 247 public Map<IEnhancedQName, FL> getExportedFlagDefinitionMap() { 248 return this.exportedFlagDefinitions; 249 } 250 251 @NonNull 252 public Map<Integer, FI> getExportedFieldDefinitionMap() { 253 return this.exportedFieldDefinitions; 254 } 255 256 @NonNull 257 public Map<Integer, A> getExportedAssemblyDefinitionMap() { 258 return this.exportedAssemblyDefinitions; 259 } 260 261 @NonNull 262 public Map<Integer, A> getExportedRootAssemblyDefinitionMap() { 263 return this.exportedRootAssemblyDefinitions; 264 } 265 } 266}