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  private final Map<IDefinition, DefinitionEntry> index = new LinkedHashMap<>();// new ConcurrentHashMap<>();
034
035  @NonNull
036  public static ModuleIndex indexDefinitions(@NonNull IModule module, @NonNull IInlineStrategy inlineStrategy) {
037    Collection<? extends IAssemblyDefinition> definitions = module.getExportedRootAssemblyDefinitions();
038    ModuleIndex index = new ModuleIndex();
039    if (!definitions.isEmpty()) {
040      IndexVisitor visitor = new IndexVisitor(index, inlineStrategy);
041      for (IAssemblyDefinition definition : definitions) {
042        assert definition != null;
043
044        // // add the root definition to the index
045        // index.getEntry(definition).incrementReferenceCount();
046
047        // walk the definition
048        visitor.walk(ObjectUtils.requireNonNull(definition));
049      }
050    }
051    return index;
052  }
053
054  public boolean hasEntry(@NonNull IDefinition definition) {
055    return index.containsKey(definition);
056  }
057
058  @NonNull
059  public DefinitionEntry getEntry(@NonNull IDefinition definition) {
060    return ObjectUtils.notNull(index.computeIfAbsent(
061        definition,
062        k -> new ModuleIndex.DefinitionEntry(ObjectUtils.notNull(k))));
063  }
064
065  @NonNull
066  public Collection<DefinitionEntry> getDefinitions() {
067    return ObjectUtils.notNull(index.values());
068  }
069
070  private static class IndexVisitor
071      extends ModelWalker<ModuleIndex> {
072    @NonNull
073    private final IInlineStrategy inlineStrategy;
074    @NonNull
075    private final ModuleIndex index;
076
077    public IndexVisitor(@NonNull ModuleIndex index, @NonNull IInlineStrategy inlineStrategy) {
078      this.index = index;
079      this.inlineStrategy = inlineStrategy;
080    }
081
082    @Override
083    protected ModuleIndex getDefaultData() {
084      return index;
085    }
086
087    @Override
088    protected boolean visit(IFlagInstance instance, ModuleIndex index) {
089      handleInstance(instance);
090      return true;
091    }
092
093    @Override
094    protected boolean visit(IFieldInstance instance, ModuleIndex index) {
095      handleInstance(instance);
096      return true;
097    }
098
099    @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}