1   /*
2    * SPDX-FileCopyrightText: none
3    * SPDX-License-Identifier: CC0-1.0
4    */
5   
6   package dev.metaschema.oscal.lib.model.util;
7   
8   import java.util.Collection;
9   import java.util.LinkedHashMap;
10  import java.util.LinkedList;
11  import java.util.List;
12  import java.util.Map;
13  
14  import dev.metaschema.core.metapath.DynamicContext;
15  import dev.metaschema.core.metapath.StaticContext;
16  import dev.metaschema.core.metapath.item.ISequence;
17  import dev.metaschema.core.metapath.item.node.AbstractRecursionPreventingNodeItemVisitor;
18  import dev.metaschema.core.metapath.item.node.IAssemblyInstanceGroupedNodeItem;
19  import dev.metaschema.core.metapath.item.node.IAssemblyNodeItem;
20  import dev.metaschema.core.metapath.item.node.IDefinitionNodeItem;
21  import dev.metaschema.core.metapath.item.node.IFieldNodeItem;
22  import dev.metaschema.core.metapath.item.node.IFlagNodeItem;
23  import dev.metaschema.core.metapath.item.node.IModuleNodeItem;
24  import dev.metaschema.core.metapath.item.node.INodeItemFactory;
25  import dev.metaschema.core.model.IModule;
26  import dev.metaschema.core.model.constraint.IAllowedValuesConstraint;
27  import dev.metaschema.core.model.constraint.ILet;
28  import edu.umd.cs.findbugs.annotations.NonNull;
29  
30  public class AllowedValueCollectingNodeItemVisitor
31      extends AbstractRecursionPreventingNodeItemVisitor<DynamicContext, Void> {
32  
33    private final Map<IDefinitionNodeItem<?, ?>, NodeItemRecord> nodeItemAnalysis = new LinkedHashMap<>();
34  
35    public Collection<NodeItemRecord> getAllowedValueLocations() {
36      return nodeItemAnalysis.values();
37    }
38  
39    public void visit(@NonNull IModule module) {
40      DynamicContext context = new DynamicContext(
41          StaticContext.builder()
42              .defaultModelNamespace(module.getXmlNamespace())
43              .build());
44      context.disablePredicateEvaluation();
45  
46      visit(INodeItemFactory.instance().newModuleNodeItem(module), context);
47    }
48  
49    public void visit(@NonNull IModuleNodeItem module, @NonNull DynamicContext context) {
50  
51      visitMetaschema(module, context);
52    }
53  
54    private void handleAllowedValuesAtLocation(
55        @NonNull IDefinitionNodeItem<?, ?> itemLocation,
56        @NonNull DynamicContext context) {
57      itemLocation.getDefinition().getAllowedValuesConstraints().stream()
58          .forEachOrdered(allowedValues -> {
59            ISequence<?> result = allowedValues.getTarget().evaluate(itemLocation, context);
60            result.stream().forEachOrdered(target -> {
61              assert target != null;
62              handleAllowedValues(allowedValues, itemLocation, (IDefinitionNodeItem<?, ?>) target);
63            });
64          });
65    }
66  
67    private void handleAllowedValues(
68        @NonNull IAllowedValuesConstraint allowedValues,
69        @NonNull IDefinitionNodeItem<?, ?> location,
70        @NonNull IDefinitionNodeItem<?, ?> target) {
71      NodeItemRecord itemRecord = nodeItemAnalysis.get(target);
72      if (itemRecord == null) {
73        itemRecord = new NodeItemRecord(target);
74        nodeItemAnalysis.put(target, itemRecord);
75      }
76  
77      AllowedValuesRecord allowedValuesRecord = new AllowedValuesRecord(allowedValues, location, target);
78      itemRecord.addAllowedValues(allowedValuesRecord);
79    }
80  
81    @Override
82    public Void visitFlag(IFlagNodeItem item, DynamicContext context) {
83      assert context != null;
84      DynamicContext subContext = handleLetStatements(item, context);
85      handleAllowedValuesAtLocation(item, subContext);
86      return super.visitFlag(item, subContext);
87    }
88  
89    @Override
90    public Void visitField(IFieldNodeItem item, DynamicContext context) {
91      assert context != null;
92      DynamicContext subContext = handleLetStatements(item, context);
93      handleAllowedValuesAtLocation(item, subContext);
94      return super.visitField(item, subContext);
95    }
96  
97    @Override
98    public Void visitAssembly(IAssemblyNodeItem item, DynamicContext context) {
99      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 }