001/* 002 * SPDX-FileCopyrightText: none 003 * SPDX-License-Identifier: CC0-1.0 004 */ 005 006package gov.nist.secauto.oscal.lib.profile.resolver.selection; 007 008import gov.nist.secauto.metaschema.core.metapath.MetapathExpression; 009import gov.nist.secauto.metaschema.core.metapath.format.IPathFormatter; 010import gov.nist.secauto.metaschema.core.metapath.item.node.IAssemblyNodeItem; 011import gov.nist.secauto.metaschema.core.metapath.item.node.IModelNodeItem; 012import gov.nist.secauto.metaschema.core.util.ObjectUtils; 013import gov.nist.secauto.oscal.lib.OscalBindingContext; 014import gov.nist.secauto.oscal.lib.model.CatalogGroup; 015import gov.nist.secauto.oscal.lib.model.Control; 016import gov.nist.secauto.oscal.lib.profile.resolver.support.IIndexer; 017 018import org.apache.commons.lang3.tuple.Pair; 019 020import java.util.Map; 021import java.util.concurrent.ConcurrentHashMap; 022 023import edu.umd.cs.findbugs.annotations.NonNull; 024import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; 025 026public class ControlSelectionState implements IControlSelectionState { 027 private static final MetapathExpression GROUP_CHILDREN = MetapathExpression.compile( 028 "group|descendant::control", 029 OscalBindingContext.OSCAL_STATIC_METAPATH_CONTEXT); 030 031 @NonNull 032 private final IIndexer index; 033 @NonNull 034 private final IControlFilter filter; 035 @NonNull 036 private final Map<IModelNodeItem<?, ?>, SelectionState> itemSelectionState = new ConcurrentHashMap<>(); 037 038 @SuppressFBWarnings(value = "EI_EXPOSE_REP2", justification = "provides intentional access to index state") 039 public ControlSelectionState(@NonNull IIndexer index, @NonNull IControlFilter filter) { 040 this.index = index; 041 this.filter = filter; 042 } 043 044 @Override 045 @SuppressFBWarnings(value = "EI_EXPOSE_REP", justification = "provides intentional access to index state") 046 public IIndexer getIndex() { 047 return index; 048 } 049 050 @NonNull 051 public IControlFilter getFilter() { 052 return filter; 053 } 054 055 @Override 056 public boolean isSelected(@NonNull IModelNodeItem<?, ?> item) { 057 return getSelectionState(item).isSelected(); 058 } 059 060 @NonNull 061 protected SelectionState getSelectionState(@NonNull IModelNodeItem<?, ?> item) { 062 SelectionState retval = itemSelectionState.get(item); 063 if (retval == null) { 064 Object itemValue = ObjectUtils.requireNonNull(item.getValue()); 065 066 if (itemValue instanceof Control) { 067 Control control = (Control) itemValue; 068 069 // get the parent control if the parent is a control 070 IAssemblyNodeItem parentItem = ObjectUtils.requireNonNull(item.getParentContentNodeItem()); 071 Object parentValue = parentItem.getValue(); 072 Control parentControl = parentValue instanceof Control ? (Control) parentValue : null; 073 074 boolean defaultMatch = false; 075 if (parentControl != null) { 076 SelectionState parentSelectionState = getSelectionState(parentItem); 077 defaultMatch = parentSelectionState.isSelected() && parentSelectionState.isWithChildren(); 078 } 079 080 Pair<Boolean, Boolean> matchResult = getFilter().match(control, defaultMatch); 081 boolean selected = matchResult.getLeft(); 082 boolean withChildren = matchResult.getRight(); 083 084 retval = new SelectionState(selected, withChildren); 085 086 } else if (itemValue instanceof CatalogGroup) { 087 // get control selection status 088 boolean selected = GROUP_CHILDREN.evaluate(item).stream() 089 .map(child -> { 090 return getSelectionState((IModelNodeItem<?, ?>) ObjectUtils.requireNonNull(child)).isSelected(); 091 }) 092 .reduce(false, (first, second) -> first || second); 093 094 retval = new SelectionState(selected, false); 095 } else { 096 throw new IllegalStateException( 097 String.format("Selection not supported for type '%s' at path '%s'", 098 itemValue.getClass().getName(), 099 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}