ListFunctionsSubcommand.java

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

package gov.nist.secauto.metaschema.cli.commands.metapath;

import gov.nist.secauto.metaschema.cli.processor.CLIProcessor.CallingContext;
import gov.nist.secauto.metaschema.cli.processor.ExitCode;
import gov.nist.secauto.metaschema.cli.processor.ExitStatus;
import gov.nist.secauto.metaschema.cli.processor.command.AbstractTerminalCommand;
import gov.nist.secauto.metaschema.cli.processor.command.ICommandExecutor;
import gov.nist.secauto.metaschema.core.metapath.StaticContext;
import gov.nist.secauto.metaschema.core.metapath.function.FunctionService;
import gov.nist.secauto.metaschema.core.metapath.function.IArgument;
import gov.nist.secauto.metaschema.core.metapath.function.IFunction;

import org.apache.commons.cli.CommandLine;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

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

/**
 * This command list the Metapath functions currently provided by the Metaschema
 * runtime.
 */
class ListFunctionsSubcommand
    extends AbstractTerminalCommand {
  private static final Logger LOGGER = LogManager.getLogger(ListFunctionsSubcommand.class);

  @NonNull
  private static final String COMMAND = "list-functions";

  @Override
  public String getName() {
    return COMMAND;
  }

  @Override
  public String getDescription() {
    return "Get a listing of supported Metapath functions";
  }

  @Override
  public ICommandExecutor newExecutor(CallingContext callingContext, CommandLine cmdLine) {
    return ICommandExecutor.using(callingContext, cmdLine, this::executeCommand);
  }

  /**
   * Execute the list functions command.
   *
   * @param callingContext
   *          the context of the command execution
   * @param cmdLine
   *          the parsed command line details
   * @return the execution result
   */
  @SuppressWarnings({
      "PMD.OnlyOneReturn", // readability
      "PMD.AvoidInstantiatingObjectsInLoops",
      "PMD.CognitiveComplexity"
  })
  protected ExitStatus executeCommand(
      @NonNull CallingContext callingContext,
      @NonNull CommandLine cmdLine) {

    Map<String, Map<String, List<IFunction>>> namespaceToNameToFunctionMap = FunctionService.getInstance().stream()
        .collect(Collectors.groupingBy(
            function -> function.getQName().getNamespaceURI(),
            Collectors.groupingBy(
                IFunction::getName,
                Collectors.toList())));

    Map<String, String> namespaceToPrefixMap = StaticContext.getWellKnownNamespacesMap().entrySet().stream()
        .collect(Collectors.toMap(entry -> entry.getValue().toASCIIString(), Map.Entry::getKey));

    List<String> namespaces = new ArrayList<>(namespaceToNameToFunctionMap.keySet());

    Collections.sort(namespaces);

    for (String namespace : namespaces) {
      String prefix = namespaceToPrefixMap.get(namespace);

      if (prefix == null) {
        LOGGER.atInfo().log("In namespace '{}':", namespace);
      } else {
        LOGGER.atInfo().log("In namespace '{}' as '{}':", namespace, prefix);
      }

      Map<String, List<IFunction>> namespacedFunctions = namespaceToNameToFunctionMap.get(namespace);

      List<String> names = new ArrayList<>(namespacedFunctions.keySet());
      Collections.sort(names);

      for (String name : names) {
        List<IFunction> functions = namespacedFunctions.get(name);
        Collections.sort(functions, Comparator.comparing(IFunction::arity));

        for (IFunction function : functions) {
          String functionRef = prefix == null
              ? String.format("Q{%s}%s", function.getQName().getNamespaceURI(), function.getName())
              : String.format("%s:%s", prefix, function.getName());

          LOGGER.atInfo().log(String.format("%s(%s) as %s",
              functionRef,
              function.getArguments().isEmpty()
                  ? ""
                  : function.getArguments().stream().map(IArgument::toSignature)
                      .collect(Collectors.joining(","))
                      + (function.isArityUnbounded() ? ", ..." : ""),
              function.getResult().toSignature()));
        }
      }
    }
    return ExitCode.OK.exit();
  }
}