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    private final Map<IDefinition, DefinitionEntry> index = new LinkedHashMap<>();// new ConcurrentHashMap<>();
34  
35    @NonNull
36    public static ModuleIndex indexDefinitions(@NonNull IModule module, @NonNull IInlineStrategy inlineStrategy) {
37      Collection<? extends IAssemblyDefinition> definitions = module.getExportedRootAssemblyDefinitions();
38      ModuleIndex index = new ModuleIndex();
39      if (!definitions.isEmpty()) {
40        IndexVisitor visitor = new IndexVisitor(index, inlineStrategy);
41        for (IAssemblyDefinition definition : definitions) {
42          assert definition != null;
43  
44          // // add the root definition to the index
45          // index.getEntry(definition).incrementReferenceCount();
46  
47          // walk the definition
48          visitor.walk(ObjectUtils.requireNonNull(definition));
49        }
50      }
51      return index;
52    }
53  
54    public boolean hasEntry(@NonNull IDefinition definition) {
55      return index.containsKey(definition);
56    }
57  
58    @NonNull
59    public DefinitionEntry getEntry(@NonNull IDefinition definition) {
60      return ObjectUtils.notNull(index.computeIfAbsent(
61          definition,
62          k -> new ModuleIndex.DefinitionEntry(ObjectUtils.notNull(k))));
63    }
64  
65    @NonNull
66    public Collection<DefinitionEntry> getDefinitions() {
67      return ObjectUtils.notNull(index.values());
68    }
69  
70    private static class IndexVisitor
71        extends ModelWalker<ModuleIndex> {
72      @NonNull
73      private final IInlineStrategy inlineStrategy;
74      @NonNull
75      private final ModuleIndex index;
76  
77      public IndexVisitor(@NonNull ModuleIndex index, @NonNull IInlineStrategy inlineStrategy) {
78        this.index = index;
79        this.inlineStrategy = inlineStrategy;
80      }
81  
82      @Override
83      protected ModuleIndex getDefaultData() {
84        return index;
85      }
86  
87      @Override
88      protected boolean visit(IFlagInstance instance, ModuleIndex index) {
89        handleInstance(instance);
90        return true;
91      }
92  
93      @Override
94      protected boolean visit(IFieldInstance instance, ModuleIndex index) {
95        handleInstance(instance);
96        return true;
97      }
98  
99      @Override
100     protected boolean visit(IAssemblyInstance instance, ModuleIndex index) {
101       handleInstance(instance);
102       return true;
103     }
104 
105     @Override
106     protected void visit(IFlagDefinition def, ModuleIndex data) {
107       handleDefinition(def);
108     }
109 
110     // @Override
111     // protected boolean visit(IAssemblyDefinition def, ModuleIndex data) {
112     // // only walk if the definition hasn't already been visited
113     // return !index.hasEntry(def);
114     // }
115 
116     @Override
117     protected boolean visit(IFieldDefinition def, ModuleIndex data) {
118       return handleDefinition(def);
119     }
120 
121     @Override
122     protected boolean visit(IAssemblyDefinition def, ModuleIndex data) {
123       return handleDefinition(def);
124     }
125 
126     private boolean handleDefinition(@NonNull IDefinition definition) {
127       DefinitionEntry entry = getDefaultData().getEntry(definition);
128       boolean visited = entry.isVisited();
129       if (!visited) {
130         entry.markVisited();
131 
132         if (inlineStrategy.isInline(definition, index)) {
133           entry.markInline();
134         }
135       }
136       return !visited;
137     }
138 
139     /**
140      * Updates the index entry for the definition associated with the reference.
141      *
142      * @param instance
143      *          the instance to process
144      */
145     @NonNull
146     private DefinitionEntry handleInstance(INamedInstance instance) {
147       IDefinition definition = instance.getDefinition();
148       // check if this will be a new entry, which needs to be called before getEntry,
149       // which will create it
150       DefinitionEntry entry = getDefaultData().getEntry(definition);
151       entry.addReference(instance);
152 
153       if (isChoice(instance)) {
154         entry.markUsedAsChoice();
155       }
156 
157       if (isChoiceSibling(instance)) {
158         entry.markAsChoiceSibling();
159       }
160       return entry;
161     }
162 
163     private static boolean isChoice(@NonNull INamedInstance instance) {
164       return instance.getParentContainer() instanceof IChoiceInstance;
165     }
166 
167     private static boolean isChoiceSibling(@NonNull INamedInstance instance) {
168       IDefinition containingDefinition = instance.getContainingDefinition();
169       return containingDefinition instanceof IAssemblyDefinition
170           && !((IAssemblyDefinition) containingDefinition).getChoiceInstances().isEmpty();
171     }
172   }
173 
174   public static class DefinitionEntry {
175     @NonNull
176     private final IDefinition definition;
177     private final Set<INamedInstance> references = new HashSet<>();
178     private final AtomicBoolean inline = new AtomicBoolean(); // false
179     private final AtomicBoolean visited = new AtomicBoolean(); // false
180     private final AtomicBoolean usedAsChoice = new AtomicBoolean(); // false
181     private final AtomicBoolean choiceSibling = new AtomicBoolean(); // false
182 
183     public DefinitionEntry(@NonNull IDefinition definition) {
184       this.definition = definition;
185     }
186 
187     @NonNull
188     public IDefinition getDefinition() {
189       return definition;
190     }
191 
192     public boolean isRoot() {
193       return definition instanceof IAssemblyDefinition
194           && ((IAssemblyDefinition) definition).isRoot();
195     }
196 
197     public boolean isReferenced() {
198       return !references.isEmpty()
199           || isRoot();
200     }
201 
202     public Set<INamedInstance> getReferences() {
203       return references;
204     }
205 
206     public boolean addReference(@NonNull INamedInstance reference) {
207       return references.add(reference);
208     }
209 
210     public void markVisited() {
211       visited.compareAndSet(false, true);
212     }
213 
214     public boolean isVisited() {
215       return visited.get();
216     }
217 
218     public void markInline() {
219       inline.compareAndSet(false, true);
220     }
221 
222     public boolean isInline() {
223       return inline.get();
224     }
225 
226     public void markUsedAsChoice() {
227       usedAsChoice.compareAndSet(false, true);
228     }
229 
230     public boolean isUsedAsChoice() {
231       return usedAsChoice.get();
232     }
233 
234     public void markAsChoiceSibling() {
235       choiceSibling.compareAndSet(false, true);
236     }
237 
238     public boolean isChoiceSibling() {
239       return choiceSibling.get();
240     }
241 
242     public boolean isUsedAsJsonKey() {
243       return references.stream()
244           .anyMatch(ref -> ref instanceof INamedModelInstance
245               && ((INamedModelInstance) ref).hasJsonKey());
246     }
247 
248     public boolean isUsedWithoutJsonKey() {
249       return definition instanceof IFlagDefinition
250           || references.isEmpty()
251           || references.stream()
252               .anyMatch(ref -> ref instanceof INamedModelInstance
253                   && !((INamedModelInstance) ref).hasJsonKey());
254     }
255 
256     public boolean isChoiceGroupMember() {
257       return references.stream()
258           .anyMatch(INamedModelInstanceGrouped.class::isInstance);
259     }
260   }
261 }