1   /*
2    * SPDX-FileCopyrightText: none
3    * SPDX-License-Identifier: CC0-1.0
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          // get the parent control if the parent is a control
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          // get control selection status
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 }