Wildcard.java

/*
 * SPDX-FileCopyrightText: none
 * SPDX-License-Identifier: CC0-1.0
 */

package gov.nist.secauto.metaschema.core.metapath.cst.path;

import gov.nist.secauto.metaschema.core.metapath.DynamicContext;
import gov.nist.secauto.metaschema.core.metapath.ISequence;
import gov.nist.secauto.metaschema.core.metapath.cst.IExpressionVisitor;
import gov.nist.secauto.metaschema.core.metapath.item.ItemUtils;
import gov.nist.secauto.metaschema.core.metapath.item.node.IDefinitionNodeItem;
import gov.nist.secauto.metaschema.core.metapath.item.node.INodeItem;
import gov.nist.secauto.metaschema.core.util.ObjectUtils;

import java.util.function.Predicate;
import java.util.stream.Stream;

import edu.umd.cs.findbugs.annotations.NonNull;
import edu.umd.cs.findbugs.annotations.Nullable;

/**
 * The CST node for a Metapath
 * <a href="https://www.w3.org/TR/xpath-31/#doc-xpath31-Wildcard">wildcard name
 * test</a>.
 */
public class Wildcard implements INameTestExpression {
  @Nullable
  private final Predicate<IDefinitionNodeItem<?, ?>> matcher;

  /**
   * Construct a new wildcard name test expression using the provided matcher.
   *
   * @param matcher
   *          the matcher used to determine matching nodes
   */
  public Wildcard(@Nullable IWildcardMatcher matcher) {
    this.matcher = matcher;
  }

  @Override
  public <RESULT, CONTEXT> RESULT accept(IExpressionVisitor<RESULT, CONTEXT> visitor, CONTEXT context) {
    return visitor.visitWildcard(this, context);
  }

  @Override
  public ISequence<? extends INodeItem> accept(
      DynamicContext dynamicContext, ISequence<?> focus) {
    Stream<? extends INodeItem> nodes = ObjectUtils.notNull(focus.stream().map(ItemUtils::checkItemIsNodeItemForStep));
    return ISequence.of(match(nodes));
  }

  /**
   * Check the provided items to determine if each item matches the wildcard. All
   * items that match are returned.
   * <p>
   * This is an intermediate stream operation.
   *
   * @param <T>
   *          the item Java type
   * @param items
   *          the items to check if they match
   * @return the matching items
   */
  @NonNull
  public <T extends INodeItem> Stream<T> match(@SuppressWarnings("resource") @NonNull Stream<T> items) {
    Stream<T> nodes = items;
    if (matcher != null) {
      Predicate<IDefinitionNodeItem<?, ?>> test = matcher;
      nodes = ObjectUtils.notNull(nodes.filter(item -> {
        assert matcher != null;
        return !(item instanceof IDefinitionNodeItem) ||
            test.test((IDefinitionNodeItem<?, ?>) item);
      }));
    }
    return nodes;
  }

  @SuppressWarnings("null")
  @Override
  public String toASTString() {
    return String.format("%s[%s]", getClass().getName(), matcher == null ? "*:*" : matcher.toString());
  }

  public interface IWildcardMatcher extends Predicate<IDefinitionNodeItem<?, ?>> {
    @Override
    @NonNull
    String toString();
  }

  /**
   * A wildcard matcher that matches a specific local name in any namespace.
   */
  public static class MatchAnyNamespace implements IWildcardMatcher {
    @NonNull
    private final String localName;

    /**
     * Construct the matcher using the provided local name for matching.
     *
     * @param localName
     *          the name used to match nodes
     */
    public MatchAnyNamespace(@NonNull String localName) {
      this.localName = localName;
    }

    @Override
    public boolean test(IDefinitionNodeItem<?, ?> item) {
      return localName.equals(item.getQName().getLocalPart());
    }

    @Override
    public String toString() {
      return "*:" + localName;
    }
  }

  /**
   * A wildcard matcher that matches any local name in a specific namespace.
   */
  public static class MatchAnyLocalName implements IWildcardMatcher {
    @NonNull
    private final String namespace;

    /**
     * Construct the matcher using the provided namespace for matching.
     *
     * @param namespace
     *          the namespace used to match nodes
     */
    public MatchAnyLocalName(@NonNull String namespace) {
      this.namespace = namespace;
    }

    @Override
    public boolean test(IDefinitionNodeItem<?, ?> item) {
      return namespace.equals(item.getQName().getNamespaceURI());
    }

    @Override
    public String toString() {
      return namespace + ":*";
    }
  }
}