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