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}