001/*
002 * SPDX-FileCopyrightText: none
003 * SPDX-License-Identifier: CC0-1.0
004 */
005
006package dev.metaschema.oscal.lib.profile.resolver.support;
007
008import java.util.Collections;
009import java.util.EnumSet;
010import java.util.Set;
011
012import dev.metaschema.core.metapath.IMetapathExpression;
013import dev.metaschema.core.metapath.item.node.IAssemblyNodeItem;
014import dev.metaschema.core.metapath.item.node.IDocumentNodeItem;
015import dev.metaschema.core.metapath.item.node.IRootAssemblyNodeItem;
016import dev.metaschema.core.util.CollectionUtil;
017import dev.metaschema.core.util.ObjectUtils;
018import dev.metaschema.oscal.lib.OscalBindingContext;
019import dev.metaschema.oscal.lib.OscalModelConstants;
020import edu.umd.cs.findbugs.annotations.NonNull;
021
022/**
023 * Visits a catalog document and its children as designated.
024 * <p>
025 * This implementation is stateless. The {@code T} parameter can be used to
026 * convey state as needed.
027 *
028 * @param <T>
029 *          the state type
030 * @param <R>
031 *          the result type
032 */
033public abstract class AbstractCatalogEntityVisitor<T, R>
034    extends AbstractCatalogVisitor<T, R> {
035  @NonNull
036  public static final IMetapathExpression CHILD_PART_METAPATH
037      = IMetapathExpression.compile("part|part//part",
038          OscalBindingContext.OSCAL_STATIC_METAPATH_CONTEXT);
039  @NonNull
040  private static final IMetapathExpression BACK_MATTER_RESOURCES_METAPATH
041      = IMetapathExpression.compile("back-matter/resource",
042          OscalBindingContext.OSCAL_STATIC_METAPATH_CONTEXT);
043  @NonNull
044  private static final Set<IEntityItem.ItemType> GROUP_CONTAINER_TYPES
045      = ObjectUtils.notNull(EnumSet.of(
046          IEntityItem.ItemType.GROUP,
047          IEntityItem.ItemType.CONTROL,
048          IEntityItem.ItemType.PARAMETER,
049          IEntityItem.ItemType.PART));
050  @NonNull
051  private static final Set<IEntityItem.ItemType> CONTROL_CONTAINER_TYPES
052      = ObjectUtils.notNull(EnumSet.of(
053          IEntityItem.ItemType.CONTROL,
054          IEntityItem.ItemType.PARAMETER,
055          IEntityItem.ItemType.PART));
056  @NonNull
057  private final Set<IEntityItem.ItemType> itemTypesToVisit;
058
059  /**
060   * Create a new visitor that will visit the item types identified by
061   * {@code itemTypesToVisit}.
062   *
063   * @param itemTypesToVisit
064   *          the item type the visitor will visit
065   */
066  public AbstractCatalogEntityVisitor(@NonNull Set<IEntityItem.ItemType> itemTypesToVisit) {
067    this.itemTypesToVisit = CollectionUtil.unmodifiableSet(itemTypesToVisit);
068  }
069
070  public Set<IEntityItem.ItemType> getItemTypesToVisit() {
071    return CollectionUtil.unmodifiableSet(itemTypesToVisit);
072  }
073
074  protected boolean isVisitedItemType(@NonNull IEntityItem.ItemType type) {
075    return itemTypesToVisit.contains(type);
076  }
077
078  @Override
079  public R visitCatalog(IDocumentNodeItem catalogDocument, T state) {
080    R result = super.visitCatalog(catalogDocument, state);
081
082    catalogDocument.modelItems().forEachOrdered(item -> {
083      IRootAssemblyNodeItem root = ObjectUtils.requireNonNull((IRootAssemblyNodeItem) item);
084      visitMetadata(root, state);
085      visitBackMatter(root, state);
086    });
087    return result;
088  }
089
090  @Override
091  protected R visitGroupContainer(IAssemblyNodeItem catalogOrGroup, R initialResult, T state) {
092    R retval;
093    if (Collections.disjoint(getItemTypesToVisit(), GROUP_CONTAINER_TYPES)) {
094      retval = initialResult;
095    } else {
096      retval = super.visitGroupContainer(catalogOrGroup, initialResult, state);
097    }
098    return retval;
099  }
100
101  @Override
102  protected R visitControlContainer(IAssemblyNodeItem catalogOrGroupOrControl, R initialResult, T state) {
103    R retval;
104    if (Collections.disjoint(getItemTypesToVisit(), CONTROL_CONTAINER_TYPES)) {
105      retval = initialResult;
106    } else {
107      // first descend to all control container children
108      retval = super.visitControlContainer(catalogOrGroupOrControl, initialResult, state);
109
110      // handle parameters
111      if (isVisitedItemType(IEntityItem.ItemType.PARAMETER)) {
112        retval = catalogOrGroupOrControl.getModelItemsByName(OscalModelConstants.QNAME_PARAM).stream()
113            .map(paramItem -> visitParameter(
114                ObjectUtils.requireNonNull((IAssemblyNodeItem) paramItem),
115                catalogOrGroupOrControl,
116                state))
117            .reduce(retval, (first, second) -> aggregateResults(first, second, state));
118      }
119    }
120    return retval;
121  }
122
123  protected void visitParts(@NonNull IAssemblyNodeItem groupOrControlItem, T state) {
124    // handle parts
125    if (isVisitedItemType(IEntityItem.ItemType.PART)) {
126      CHILD_PART_METAPATH.evaluate(groupOrControlItem).stream()
127          .map(item -> (IAssemblyNodeItem) item)
128          .forEachOrdered(partItem -> {
129            visitPart(ObjectUtils.requireNonNull(partItem), groupOrControlItem, state);
130          });
131    }
132  }
133
134  @Override
135  protected R visitGroupInternal(@NonNull IAssemblyNodeItem item, R childResult, T state) {
136    if (isVisitedItemType(IEntityItem.ItemType.PART)) {
137      visitParts(item, state);
138    }
139
140    R retval = childResult;
141    if (isVisitedItemType(IEntityItem.ItemType.GROUP)) {
142      retval = visitGroup(item, retval, state);
143    }
144    return retval;
145  }
146
147  @Override
148  protected R visitControlInternal(IAssemblyNodeItem item, R childResult, T state) {
149    if (isVisitedItemType(IEntityItem.ItemType.PART)) {
150      visitParts(item, state);
151    }
152
153    R retval = childResult;
154    if (isVisitedItemType(IEntityItem.ItemType.CONTROL)) {
155      retval = visitControl(item, retval, state);
156    }
157    return retval;
158  }
159
160  /**
161   * Called when visiting a parameter.
162   * <p>
163   * Can be overridden by classes extending this interface to support processing
164   * of the visited object.
165   *
166   * @param item
167   *          the Metapath item for the parameter
168   * @param catalogOrGroupOrControl
169   *          the parameter's parent Metapath item
170   * @param state
171   *          the calling context information
172   * @return a meaningful result of the given type
173   */
174  protected R visitParameter(
175      @NonNull IAssemblyNodeItem item,
176      @NonNull IAssemblyNodeItem catalogOrGroupOrControl,
177      T state) {
178    // do nothing
179    return newDefaultResult(state);
180  }
181
182  /**
183   * Called when visiting a part.
184   * <p>
185   * Can be overridden by classes extending this interface to support processing
186   * of the visited object.
187   *
188   * @param item
189   *          the Metapath item for the part
190   * @param groupOrControl
191   *          the part's parent Metapath item
192   * @param state
193   *          the calling context information
194   */
195  protected void visitPart( // NOPMD noop default
196      @NonNull IAssemblyNodeItem item,
197      @NonNull IAssemblyNodeItem groupOrControl,
198      T state) {
199    // do nothing
200  }
201
202  /**
203   * Called when visiting the "metadata" section of an OSCAL document.
204   * <p>
205   * Visits each contained role, location, and party.
206   *
207   * @param rootItem
208   *          the root Module node item containing the "metadata" node
209   * @param state
210   *          the calling context information
211   */
212  protected void visitMetadata(@NonNull IRootAssemblyNodeItem rootItem, T state) {
213    rootItem.getModelItemsByName(OscalModelConstants.QNAME_METADATA).stream()
214        .map(metadataItem -> (IAssemblyNodeItem) metadataItem)
215        .forEach(metadataItem -> {
216          if (isVisitedItemType(IEntityItem.ItemType.ROLE)) {
217            metadataItem.getModelItemsByName(OscalModelConstants.QNAME_ROLE).stream()
218                .map(roleItem -> (IAssemblyNodeItem) roleItem)
219                .forEachOrdered(roleItem -> {
220                  visitRole(ObjectUtils.requireNonNull(roleItem), metadataItem, state);
221                });
222          }
223
224          if (isVisitedItemType(IEntityItem.ItemType.LOCATION)) {
225            metadataItem.getModelItemsByName(OscalModelConstants.QNAME_LOCATION).stream()
226                .map(locationItem -> (IAssemblyNodeItem) locationItem)
227                .forEachOrdered(locationItem -> {
228                  visitLocation(ObjectUtils.requireNonNull(locationItem), metadataItem, state);
229                });
230          }
231
232          if (isVisitedItemType(IEntityItem.ItemType.PARTY)) {
233            metadataItem.getModelItemsByName(OscalModelConstants.QNAME_PARTY).stream()
234                .map(partyItem -> (IAssemblyNodeItem) partyItem)
235                .forEachOrdered(partyItem -> {
236                  visitParty(ObjectUtils.requireNonNull(partyItem), metadataItem, state);
237                });
238          }
239        });
240  }
241
242  /**
243   * Called when visiting a role in the "metadata" section of an OSCAL document.
244   * <p>
245   * Can be overridden by classes extending this interface to support processing
246   * of the visited object.
247   *
248   * @param item
249   *          the role Module node item which is a child of the "metadata" node
250   * @param metadataItem
251   *          the "metadata" Module node item containing the role
252   * @param state
253   *          the calling context information
254   */
255  protected void visitRole( // NOPMD noop default
256      @NonNull IAssemblyNodeItem item,
257      @NonNull IAssemblyNodeItem metadataItem,
258      T state) {
259    // do nothing
260  }
261
262  /**
263   * Called when visiting a location in the "metadata" section of an OSCAL
264   * document.
265   * <p>
266   * Can be overridden by classes extending this interface to support processing
267   * of the visited object.
268   *
269   * @param item
270   *          the location Module node item which is a child of the "metadata"
271   *          node
272   * @param metadataItem
273   *          the "metadata" Module node item containing the location
274   * @param state
275   *          the calling context information
276   */
277  protected void visitLocation( // NOPMD noop default
278      @NonNull IAssemblyNodeItem item,
279      @NonNull IAssemblyNodeItem metadataItem,
280      T state) {
281    // do nothing
282  }
283
284  /**
285   * Called when visiting a party in the "metadata" section of an OSCAL document.
286   * <p>
287   * Can be overridden by classes extending this interface to support processing
288   * of the visited object.
289   *
290   * @param item
291   *          the party Module node item which is a child of the "metadata" node
292   * @param metadataItem
293   *          the "metadata" Module node item containing the party
294   * @param state
295   *          the calling context information
296   */
297  protected void visitParty( // NOPMD noop default
298      @NonNull IAssemblyNodeItem item,
299      @NonNull IAssemblyNodeItem metadataItem,
300      T state) {
301    // do nothing
302  }
303
304  /**
305   * Called when visiting the "back-matter" section of an OSCAL document.
306   * <p>
307   * Visits each contained resource.
308   *
309   * @param rootItem
310   *          the root Module node item containing the "back-matter" node
311   * @param state
312   *          the calling context information
313   */
314  protected void visitBackMatter(@NonNull IRootAssemblyNodeItem rootItem, T state) {
315    if (isVisitedItemType(IEntityItem.ItemType.RESOURCE)) {
316      BACK_MATTER_RESOURCES_METAPATH.evaluate(rootItem).stream()
317          .map(item -> (IAssemblyNodeItem) item)
318          .forEachOrdered(resourceItem -> {
319            visitResource(ObjectUtils.requireNonNull(resourceItem), rootItem, state);
320          });
321    }
322  }
323
324  /**
325   * Called when visiting a resource in the "back-matter" section of an OSCAL
326   * document.
327   * <p>
328   * Can be overridden by classes extending this interface to support processing
329   * of the visited object.
330   *
331   * @param resource
332   *          the resource Module node item which is a child of the "metadata"
333   *          node
334   * @param backMatter
335   *          the resource Module node item containing the party
336   * @param state
337   *          the calling context information
338   */
339  protected void visitResource( // NOPMD noop default
340      @NonNull IAssemblyNodeItem resource,
341      @NonNull IRootAssemblyNodeItem backMatter,
342      T state) {
343    // do nothing
344  }
345}