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