001/*
002 * SPDX-FileCopyrightText: none
003 * SPDX-License-Identifier: CC0-1.0
004 */
005
006package dev.metaschema.oscal.lib.model.util;
007
008import java.util.Collection;
009import java.util.LinkedHashMap;
010import java.util.LinkedList;
011import java.util.List;
012import java.util.Map;
013
014import dev.metaschema.core.metapath.DynamicContext;
015import dev.metaschema.core.metapath.StaticContext;
016import dev.metaschema.core.metapath.item.ISequence;
017import dev.metaschema.core.metapath.item.node.AbstractRecursionPreventingNodeItemVisitor;
018import dev.metaschema.core.metapath.item.node.IAssemblyInstanceGroupedNodeItem;
019import dev.metaschema.core.metapath.item.node.IAssemblyNodeItem;
020import dev.metaschema.core.metapath.item.node.IDefinitionNodeItem;
021import dev.metaschema.core.metapath.item.node.IFieldNodeItem;
022import dev.metaschema.core.metapath.item.node.IFlagNodeItem;
023import dev.metaschema.core.metapath.item.node.IModuleNodeItem;
024import dev.metaschema.core.metapath.item.node.INodeItemFactory;
025import dev.metaschema.core.model.IModule;
026import dev.metaschema.core.model.constraint.IAllowedValuesConstraint;
027import dev.metaschema.core.model.constraint.ILet;
028import edu.umd.cs.findbugs.annotations.NonNull;
029
030public class AllowedValueCollectingNodeItemVisitor
031    extends AbstractRecursionPreventingNodeItemVisitor<DynamicContext, Void> {
032
033  private final Map<IDefinitionNodeItem<?, ?>, NodeItemRecord> nodeItemAnalysis = new LinkedHashMap<>();
034
035  public Collection<NodeItemRecord> getAllowedValueLocations() {
036    return nodeItemAnalysis.values();
037  }
038
039  public void visit(@NonNull IModule module) {
040    DynamicContext context = new DynamicContext(
041        StaticContext.builder()
042            .defaultModelNamespace(module.getXmlNamespace())
043            .build());
044    context.disablePredicateEvaluation();
045
046    visit(INodeItemFactory.instance().newModuleNodeItem(module), context);
047  }
048
049  public void visit(@NonNull IModuleNodeItem module, @NonNull DynamicContext context) {
050
051    visitMetaschema(module, context);
052  }
053
054  private void handleAllowedValuesAtLocation(
055      @NonNull IDefinitionNodeItem<?, ?> itemLocation,
056      @NonNull DynamicContext context) {
057    itemLocation.getDefinition().getAllowedValuesConstraints().stream()
058        .forEachOrdered(allowedValues -> {
059          ISequence<?> result = allowedValues.getTarget().evaluate(itemLocation, context);
060          result.stream().forEachOrdered(target -> {
061            assert target != null;
062            handleAllowedValues(allowedValues, itemLocation, (IDefinitionNodeItem<?, ?>) target);
063          });
064        });
065  }
066
067  private void handleAllowedValues(
068      @NonNull IAllowedValuesConstraint allowedValues,
069      @NonNull IDefinitionNodeItem<?, ?> location,
070      @NonNull IDefinitionNodeItem<?, ?> target) {
071    NodeItemRecord itemRecord = nodeItemAnalysis.get(target);
072    if (itemRecord == null) {
073      itemRecord = new NodeItemRecord(target);
074      nodeItemAnalysis.put(target, itemRecord);
075    }
076
077    AllowedValuesRecord allowedValuesRecord = new AllowedValuesRecord(allowedValues, location, target);
078    itemRecord.addAllowedValues(allowedValuesRecord);
079  }
080
081  @Override
082  public Void visitFlag(IFlagNodeItem item, DynamicContext context) {
083    assert context != null;
084    DynamicContext subContext = handleLetStatements(item, context);
085    handleAllowedValuesAtLocation(item, subContext);
086    return super.visitFlag(item, subContext);
087  }
088
089  @Override
090  public Void visitField(IFieldNodeItem item, DynamicContext context) {
091    assert context != null;
092    DynamicContext subContext = handleLetStatements(item, context);
093    handleAllowedValuesAtLocation(item, subContext);
094    return super.visitField(item, subContext);
095  }
096
097  @Override
098  public Void visitAssembly(IAssemblyNodeItem item, DynamicContext context) {
099    assert context != null;
100    DynamicContext subContext = handleLetStatements(item, context);
101    handleAllowedValuesAtLocation(item, subContext);
102    return super.visitAssembly(item, subContext);
103  }
104
105  private DynamicContext handleLetStatements(IDefinitionNodeItem<?, ?> item, DynamicContext context) {
106    assert context != null;
107    DynamicContext subContext = context;
108    for (ILet let : item.getDefinition().getLetExpressions().values()) {
109      ISequence<?> result = let.getValueExpression().evaluate(item,
110          subContext).reusable();
111      subContext = subContext.bindVariableValue(let.getName(), result);
112    }
113    return subContext;
114  }
115
116  @Override
117  public Void visitAssembly(IAssemblyInstanceGroupedNodeItem item, DynamicContext context) {
118    return visitAssembly((IAssemblyNodeItem) item, context);
119  }
120
121  @Override
122  protected Void defaultResult() {
123    return null;
124  }
125
126  public static final class NodeItemRecord {
127    @NonNull
128    private final IDefinitionNodeItem<?, ?> item;
129    @NonNull
130    private final List<AllowedValuesRecord> allowedValues = new LinkedList<>();
131
132    private NodeItemRecord(@NonNull IDefinitionNodeItem<?, ?> item) {
133      this.item = item;
134    }
135
136    @NonNull
137    public IDefinitionNodeItem<?, ?> getItem() {
138      return item;
139    }
140
141    @NonNull
142    public List<AllowedValuesRecord> getAllowedValues() {
143      return allowedValues;
144    }
145
146    public void addAllowedValues(@NonNull AllowedValuesRecord record) {
147      this.allowedValues.add(record);
148    }
149  }
150
151  public static final class AllowedValuesRecord {
152    @NonNull
153    private final IAllowedValuesConstraint allowedValues;
154    @NonNull
155    private final IDefinitionNodeItem<?, ?> location;
156    @NonNull
157    private final IDefinitionNodeItem<?, ?> target;
158
159    public AllowedValuesRecord(
160        @NonNull IAllowedValuesConstraint allowedValues,
161        @NonNull IDefinitionNodeItem<?, ?> location,
162        @NonNull IDefinitionNodeItem<?, ?> target) {
163      this.allowedValues = allowedValues;
164      this.location = location;
165      this.target = target;
166    }
167
168    @NonNull
169    public IAllowedValuesConstraint getAllowedValues() {
170      return allowedValues;
171    }
172
173    @NonNull
174    public IDefinitionNodeItem<?, ?> getLocation() {
175      return location;
176    }
177
178    @NonNull
179    public IDefinitionNodeItem<?, ?> getTarget() {
180      return target;
181    }
182  }
183}