1
2
3
4
5
6 package dev.metaschema.oscal.lib.profile.resolver.selection;
7
8 import org.apache.commons.lang3.tuple.Pair;
9
10 import java.util.Map;
11 import java.util.concurrent.ConcurrentHashMap;
12
13 import dev.metaschema.core.metapath.IMetapathExpression;
14 import dev.metaschema.core.metapath.format.IPathFormatter;
15 import dev.metaschema.core.metapath.item.node.IAssemblyNodeItem;
16 import dev.metaschema.core.metapath.item.node.IModelNodeItem;
17 import dev.metaschema.core.util.ObjectUtils;
18 import dev.metaschema.oscal.lib.OscalBindingContext;
19 import dev.metaschema.oscal.lib.model.CatalogGroup;
20 import dev.metaschema.oscal.lib.model.Control;
21 import dev.metaschema.oscal.lib.profile.resolver.support.IIndexer;
22 import edu.umd.cs.findbugs.annotations.NonNull;
23 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
24
25 public class ControlSelectionState implements IControlSelectionState {
26 private static final IMetapathExpression GROUP_CHILDREN = IMetapathExpression.compile(
27 "group|descendant::control",
28 OscalBindingContext.OSCAL_STATIC_METAPATH_CONTEXT);
29
30 @NonNull
31 private final IIndexer index;
32 @NonNull
33 private final IControlFilter filter;
34 @NonNull
35 private final Map<IModelNodeItem<?, ?>, SelectionState> itemSelectionState = new ConcurrentHashMap<>();
36
37 @SuppressFBWarnings(value = "EI_EXPOSE_REP2", justification = "provides intentional access to index state")
38 public ControlSelectionState(@NonNull IIndexer index, @NonNull IControlFilter filter) {
39 this.index = index;
40 this.filter = filter;
41 }
42
43 @Override
44 @SuppressFBWarnings(value = "EI_EXPOSE_REP", justification = "provides intentional access to index state")
45 public IIndexer getIndex() {
46 return index;
47 }
48
49 @NonNull
50 public IControlFilter getFilter() {
51 return filter;
52 }
53
54 @Override
55 public boolean isSelected(@NonNull IModelNodeItem<?, ?> item) {
56 return getSelectionState(item).isSelected();
57 }
58
59 @NonNull
60 protected SelectionState getSelectionState(@NonNull IModelNodeItem<?, ?> item) {
61 SelectionState retval = itemSelectionState.get(item);
62 if (retval == null) {
63 Object itemValue = ObjectUtils.requireNonNull(item.getValue());
64
65 if (itemValue instanceof Control) {
66 Control control = (Control) itemValue;
67
68
69 IAssemblyNodeItem parentItem = ObjectUtils.requireNonNull(item.getParentContentNodeItem());
70 Object parentValue = parentItem.getValue();
71 Control parentControl = parentValue instanceof Control ? (Control) parentValue : null;
72
73 boolean defaultMatch = false;
74 if (parentControl != null) {
75 SelectionState parentSelectionState = getSelectionState(parentItem);
76 defaultMatch = parentSelectionState.isSelected() && parentSelectionState.isWithChildren();
77 }
78
79 Pair<Boolean, Boolean> matchResult = getFilter().match(control, defaultMatch);
80 boolean selected = matchResult.getLeft();
81 boolean withChildren = matchResult.getRight();
82
83 retval = new SelectionState(selected, withChildren);
84
85 } else if (itemValue instanceof CatalogGroup) {
86
87 boolean selected = GROUP_CHILDREN.evaluate(item).stream()
88 .map(child -> getSelectionState((IModelNodeItem<?, ?>) ObjectUtils.requireNonNull(child)).isSelected())
89 .reduce(false, (first, second) -> first || second);
90
91 retval = new SelectionState(selected, false);
92 } else {
93 throw new IllegalStateException(
94 String.format("Selection not supported for type '%s' at path '%s'",
95 itemValue.getClass().getName(),
96 item.toPath(IPathFormatter.METAPATH_PATH_FORMATER)));
97 }
98 itemSelectionState.put(item, retval);
99 }
100 return retval;
101 }
102
103 private static final class SelectionState {
104 private final boolean selected;
105 private final boolean withChildren;
106
107 private SelectionState(boolean selected, boolean withChildren) {
108 this.selected = selected;
109 this.withChildren = withChildren;
110 }
111
112 public boolean isSelected() {
113 return selected;
114 }
115
116 public boolean isWithChildren() {
117 return selected && withChildren;
118 }
119 }
120 }