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;
022import gov.nist.secauto.metaschema.databind.codegen.JavaGenerator;
023
024import org.apache.logging.log4j.LogManager;
025import org.apache.logging.log4j.Logger;
026
027import java.util.Collection;
028import java.util.HashSet;
029import java.util.LinkedHashMap;
030import java.util.Map;
031import java.util.Set;
032import java.util.concurrent.atomic.AtomicBoolean;
033
034import edu.umd.cs.findbugs.annotations.NonNull;
035
036public class ModuleIndex {
037  private static final Logger LOGGER = LogManager.getLogger(JavaGenerator.class);
038
039  private final Map<IDefinition, DefinitionEntry> index = new LinkedHashMap<>();// new ConcurrentHashMap<>();
040
041  @NonNull
042  public static ModuleIndex indexDefinitions(@NonNull IModule module, @NonNull IInlineStrategy inlineStrategy) {
043    Collection<? extends IAssemblyDefinition> definitions = module.getExportedRootAssemblyDefinitions();
044    ModuleIndex index = new ModuleIndex();
045    if (!definitions.isEmpty()) {
046      IndexVisitor visitor = new IndexVisitor(index, inlineStrategy);
047      for (IAssemblyDefinition definition : definitions) {
048        assert definition != null;
049
050        // // add the root definition to the index
051        // index.getEntry(definition).incrementReferenceCount();
052
053        // walk the definition
054        visitor.walk(ObjectUtils.requireNonNull(definition));
055      }
056    }
057    return index;
058  }
059
060  public boolean hasEntry(@NonNull IDefinition definition) {
061    return index.containsKey(definition);
062  }
063
064  @NonNull
065  public DefinitionEntry getEntry(@NonNull IDefinition definition) {
066    return ObjectUtils.notNull(index.computeIfAbsent(
067        definition,
068        k -> new ModuleIndex.DefinitionEntry(ObjectUtils.notNull(k))));
069  }
070
071  @NonNull
072  public Collection<DefinitionEntry> getDefinitions() {
073    return ObjectUtils.notNull(index.values());
074  }
075
076  private static class IndexVisitor
077      extends ModelWalker<ModuleIndex> {
078    @NonNull
079    private final IInlineStrategy inlineStrategy;
080    @NonNull
081    private final ModuleIndex index;
082
083    public IndexVisitor(@NonNull ModuleIndex index, @NonNull IInlineStrategy inlineStrategy) {
084      this.index = index;
085      this.inlineStrategy = inlineStrategy;
086    }
087
088    @Override
089    protected ModuleIndex getDefaultData() {
090      return index;
091    }
092
093    @Override
094    protected boolean visit(IFlagInstance instance, ModuleIndex index) {
095      handleInstance(instance);
096      return true;
097    }
098
099    @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}