1   /*
2    * SPDX-FileCopyrightText: none
3    * SPDX-License-Identifier: CC0-1.0
4    */
5   
6   package gov.nist.secauto.metaschema.schemagen;
7   
8   import gov.nist.secauto.metaschema.core.model.IAssemblyDefinition;
9   import gov.nist.secauto.metaschema.core.model.IAssemblyInstance;
10  import gov.nist.secauto.metaschema.core.model.IChoiceInstance;
11  import gov.nist.secauto.metaschema.core.model.IDefinition;
12  import gov.nist.secauto.metaschema.core.model.IFieldDefinition;
13  import gov.nist.secauto.metaschema.core.model.IFieldInstance;
14  import gov.nist.secauto.metaschema.core.model.IFlagDefinition;
15  import gov.nist.secauto.metaschema.core.model.IFlagInstance;
16  import gov.nist.secauto.metaschema.core.model.IModule;
17  import gov.nist.secauto.metaschema.core.model.INamedInstance;
18  import gov.nist.secauto.metaschema.core.model.INamedModelInstance;
19  import gov.nist.secauto.metaschema.core.model.INamedModelInstanceGrouped;
20  import gov.nist.secauto.metaschema.core.model.ModelWalker;
21  import gov.nist.secauto.metaschema.core.util.ObjectUtils;
22  
23  import java.util.Collection;
24  import java.util.HashSet;
25  import java.util.LinkedHashMap;
26  import java.util.Map;
27  import java.util.Set;
28  import java.util.concurrent.atomic.AtomicBoolean;
29  
30  import edu.umd.cs.findbugs.annotations.NonNull;
31  
32  public class ModuleIndex {
33    // needs to be ordered
34    @SuppressWarnings("PMD.UseConcurrentHashMap")
35    private final Map<IDefinition, DefinitionEntry> index = new LinkedHashMap<>();
36  
37    @NonNull
38    public static ModuleIndex indexDefinitions(@NonNull IModule module, @NonNull IInlineStrategy inlineStrategy) {
39      Collection<? extends IAssemblyDefinition> definitions = module.getExportedRootAssemblyDefinitions();
40      ModuleIndex index = new ModuleIndex();
41      if (!definitions.isEmpty()) {
42        IndexVisitor visitor = new IndexVisitor(index, inlineStrategy);
43        for (IAssemblyDefinition definition : definitions) {
44          assert definition != null;
45  
46          // // add the root definition to the index
47          // index.getEntry(definition).incrementReferenceCount();
48  
49          // walk the definition
50          visitor.walk(ObjectUtils.requireNonNull(definition));
51        }
52      }
53      return index;
54    }
55  
56    public boolean hasEntry(@NonNull IDefinition definition) {
57      return index.containsKey(definition);
58    }
59  
60    @NonNull
61    public DefinitionEntry getEntry(@NonNull IDefinition definition) {
62      return ObjectUtils.notNull(index.computeIfAbsent(
63          definition,
64          k -> new ModuleIndex.DefinitionEntry(ObjectUtils.notNull(k))));
65    }
66  
67    @NonNull
68    public Collection<DefinitionEntry> getDefinitions() {
69      return ObjectUtils.notNull(index.values());
70    }
71  
72    private static class IndexVisitor
73        extends ModelWalker<ModuleIndex> {
74      @NonNull
75      private final IInlineStrategy inlineStrategy;
76      @NonNull
77      private final ModuleIndex index;
78  
79      public IndexVisitor(@NonNull ModuleIndex index, @NonNull IInlineStrategy inlineStrategy) {
80        this.index = index;
81        this.inlineStrategy = inlineStrategy;
82      }
83  
84      @Override
85      protected ModuleIndex getDefaultData() {
86        return index;
87      }
88  
89      @Override
90      protected boolean visit(IFlagInstance instance, ModuleIndex index) {
91        handleInstance(instance);
92        return true;
93      }
94  
95      @Override
96      protected boolean visit(IFieldInstance instance, ModuleIndex index) {
97        handleInstance(instance);
98        return true;
99      }
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 }