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