001/* 002 * SPDX-FileCopyrightText: none 003 * SPDX-License-Identifier: CC0-1.0 004 */ 005 006package gov.nist.secauto.metaschema.schemagen; 007 008import gov.nist.secauto.metaschema.core.model.IAssemblyDefinition; 009import gov.nist.secauto.metaschema.core.model.IAssemblyInstance; 010import gov.nist.secauto.metaschema.core.model.IChoiceInstance; 011import gov.nist.secauto.metaschema.core.model.IDefinition; 012import gov.nist.secauto.metaschema.core.model.IFieldDefinition; 013import gov.nist.secauto.metaschema.core.model.IFieldInstance; 014import gov.nist.secauto.metaschema.core.model.IFlagDefinition; 015import gov.nist.secauto.metaschema.core.model.IFlagInstance; 016import gov.nist.secauto.metaschema.core.model.IModule; 017import gov.nist.secauto.metaschema.core.model.INamedInstance; 018import gov.nist.secauto.metaschema.core.model.INamedModelInstance; 019import gov.nist.secauto.metaschema.core.model.INamedModelInstanceGrouped; 020import gov.nist.secauto.metaschema.core.model.ModelWalker; 021import gov.nist.secauto.metaschema.core.util.ObjectUtils; 022 023import java.util.Collection; 024import java.util.HashSet; 025import java.util.LinkedHashMap; 026import java.util.Map; 027import java.util.Set; 028import java.util.concurrent.atomic.AtomicBoolean; 029 030import edu.umd.cs.findbugs.annotations.NonNull; 031 032public class ModuleIndex { 033 // needs to be ordered 034 @SuppressWarnings("PMD.UseConcurrentHashMap") 035 private final Map<IDefinition, DefinitionEntry> index = new LinkedHashMap<>(); 036 037 @NonNull 038 public static ModuleIndex indexDefinitions(@NonNull IModule module, @NonNull IInlineStrategy inlineStrategy) { 039 Collection<? extends IAssemblyDefinition> definitions = module.getExportedRootAssemblyDefinitions(); 040 ModuleIndex index = new ModuleIndex(); 041 if (!definitions.isEmpty()) { 042 IndexVisitor visitor = new IndexVisitor(index, inlineStrategy); 043 for (IAssemblyDefinition definition : definitions) { 044 assert definition != null; 045 046 // // add the root definition to the index 047 // index.getEntry(definition).incrementReferenceCount(); 048 049 // walk the definition 050 visitor.walk(ObjectUtils.requireNonNull(definition)); 051 } 052 } 053 return index; 054 } 055 056 public boolean hasEntry(@NonNull IDefinition definition) { 057 return index.containsKey(definition); 058 } 059 060 @NonNull 061 public DefinitionEntry getEntry(@NonNull IDefinition definition) { 062 return ObjectUtils.notNull(index.computeIfAbsent( 063 definition, 064 k -> new ModuleIndex.DefinitionEntry(ObjectUtils.notNull(k)))); 065 } 066 067 @NonNull 068 public Collection<DefinitionEntry> getDefinitions() { 069 return ObjectUtils.notNull(index.values()); 070 } 071 072 private static class IndexVisitor 073 extends ModelWalker<ModuleIndex> { 074 @NonNull 075 private final IInlineStrategy inlineStrategy; 076 @NonNull 077 private final ModuleIndex index; 078 079 public IndexVisitor(@NonNull ModuleIndex index, @NonNull IInlineStrategy inlineStrategy) { 080 this.index = index; 081 this.inlineStrategy = inlineStrategy; 082 } 083 084 @Override 085 protected ModuleIndex getDefaultData() { 086 return index; 087 } 088 089 @Override 090 protected boolean visit(IFlagInstance instance, ModuleIndex index) { 091 handleInstance(instance); 092 return true; 093 } 094 095 @Override 096 protected boolean visit(IFieldInstance instance, ModuleIndex index) { 097 handleInstance(instance); 098 return true; 099 } 100 101 @Override 102 protected boolean visit(IAssemblyInstance instance, ModuleIndex index) { 103 handleInstance(instance); 104 return true; 105 } 106 107 @Override 108 protected void visit(IFlagDefinition def, ModuleIndex data) { 109 handleDefinition(def); 110 } 111 112 // @Override 113 // protected boolean visit(IAssemblyDefinition def, ModuleIndex data) { 114 // // only walk if the definition hasn't already been visited 115 // return !index.hasEntry(def); 116 // } 117 118 @Override 119 protected boolean visit(IFieldDefinition def, ModuleIndex data) { 120 return handleDefinition(def); 121 } 122 123 @Override 124 protected boolean visit(IAssemblyDefinition def, ModuleIndex data) { 125 return handleDefinition(def); 126 } 127 128 private boolean handleDefinition(@NonNull IDefinition definition) { 129 DefinitionEntry entry = getDefaultData().getEntry(definition); 130 boolean visited = entry.isVisited(); 131 if (!visited) { 132 entry.markVisited(); 133 134 if (inlineStrategy.isInline(definition, index)) { 135 entry.markInline(); 136 } 137 } 138 return !visited; 139 } 140 141 /** 142 * Updates the index entry for the definition associated with the reference. 143 * 144 * @param instance 145 * the instance to process 146 */ 147 @NonNull 148 private DefinitionEntry handleInstance(INamedInstance instance) { 149 IDefinition definition = instance.getDefinition(); 150 // check if this will be a new entry, which needs to be called before getEntry, 151 // which will create it 152 DefinitionEntry entry = getDefaultData().getEntry(definition); 153 entry.addReference(instance); 154 155 if (isChoice(instance)) { 156 entry.markUsedAsChoice(); 157 } 158 159 if (isChoiceSibling(instance)) { 160 entry.markAsChoiceSibling(); 161 } 162 return entry; 163 } 164 165 private static boolean isChoice(@NonNull INamedInstance instance) { 166 return instance.getParentContainer() instanceof IChoiceInstance; 167 } 168 169 private static boolean isChoiceSibling(@NonNull INamedInstance instance) { 170 IDefinition containingDefinition = instance.getContainingDefinition(); 171 return containingDefinition instanceof IAssemblyDefinition 172 && !((IAssemblyDefinition) containingDefinition).getChoiceInstances().isEmpty(); 173 } 174 } 175 176 public static class DefinitionEntry { 177 @NonNull 178 private final IDefinition definition; 179 private final Set<INamedInstance> references = new HashSet<>(); 180 private final AtomicBoolean inline = new AtomicBoolean(); // false 181 private final AtomicBoolean visited = new AtomicBoolean(); // false 182 private final AtomicBoolean usedAsChoice = new AtomicBoolean(); // false 183 private final AtomicBoolean choiceSibling = new AtomicBoolean(); // false 184 185 public DefinitionEntry(@NonNull IDefinition definition) { 186 this.definition = definition; 187 } 188 189 @NonNull 190 public IDefinition getDefinition() { 191 return definition; 192 } 193 194 public boolean isRoot() { 195 return definition instanceof IAssemblyDefinition 196 && ((IAssemblyDefinition) definition).isRoot(); 197 } 198 199 public boolean isReferenced() { 200 return !references.isEmpty() 201 || isRoot(); 202 } 203 204 public Set<INamedInstance> getReferences() { 205 return references; 206 } 207 208 public boolean addReference(@NonNull INamedInstance reference) { 209 return references.add(reference); 210 } 211 212 public void markVisited() { 213 visited.compareAndSet(false, true); 214 } 215 216 public boolean isVisited() { 217 return visited.get(); 218 } 219 220 public void markInline() { 221 inline.compareAndSet(false, true); 222 } 223 224 public boolean isInline() { 225 return inline.get(); 226 } 227 228 public void markUsedAsChoice() { 229 usedAsChoice.compareAndSet(false, true); 230 } 231 232 public boolean isUsedAsChoice() { 233 return usedAsChoice.get(); 234 } 235 236 public void markAsChoiceSibling() { 237 choiceSibling.compareAndSet(false, true); 238 } 239 240 public boolean isChoiceSibling() { 241 return choiceSibling.get(); 242 } 243 244 public boolean isUsedAsJsonKey() { 245 return references.stream() 246 .anyMatch(ref -> ref instanceof INamedModelInstance 247 && ((INamedModelInstance) ref).hasJsonKey()); 248 } 249 250 public boolean isUsedWithoutJsonKey() { 251 return definition instanceof IFlagDefinition 252 || references.isEmpty() 253 || references.stream() 254 .anyMatch(ref -> ref instanceof INamedModelInstance 255 && !((INamedModelInstance) ref).hasJsonKey()); 256 } 257 258 public boolean isChoiceGroupMember() { 259 return references.stream() 260 .anyMatch(INamedModelInstanceGrouped.class::isInstance); 261 } 262 } 263}