001/*
002 * SPDX-FileCopyrightText: none
003 * SPDX-License-Identifier: CC0-1.0
004 */
005
006package gov.nist.secauto.metaschema.cli.commands.metapath;
007
008import gov.nist.secauto.metaschema.cli.processor.CLIProcessor.CallingContext;
009import gov.nist.secauto.metaschema.cli.processor.ExitCode;
010import gov.nist.secauto.metaschema.cli.processor.ExitStatus;
011import gov.nist.secauto.metaschema.cli.processor.command.AbstractTerminalCommand;
012import gov.nist.secauto.metaschema.cli.processor.command.ICommandExecutor;
013import gov.nist.secauto.metaschema.core.metapath.StaticContext;
014import gov.nist.secauto.metaschema.core.metapath.function.FunctionService;
015import gov.nist.secauto.metaschema.core.metapath.function.IArgument;
016import gov.nist.secauto.metaschema.core.metapath.function.IFunction;
017
018import org.apache.commons.cli.CommandLine;
019import org.apache.logging.log4j.LogManager;
020import org.apache.logging.log4j.Logger;
021
022import java.util.ArrayList;
023import java.util.Collections;
024import java.util.Comparator;
025import java.util.List;
026import java.util.Map;
027import java.util.stream.Collectors;
028
029import edu.umd.cs.findbugs.annotations.NonNull;
030
031public class ListFunctionsSubcommand
032    extends AbstractTerminalCommand {
033  private static final Logger LOGGER = LogManager.getLogger(ListFunctionsSubcommand.class);
034
035  @NonNull
036  private static final String COMMAND = "list-functions";
037
038  @Override
039  public String getName() {
040    return COMMAND;
041  }
042
043  @Override
044  public String getDescription() {
045    return "Get a listing of supported Metapath functions";
046  }
047
048  @Override
049  public ICommandExecutor newExecutor(CallingContext callingContext, CommandLine cmdLine) {
050    return ICommandExecutor.using(callingContext, cmdLine, this::executeCommand);
051  }
052
053  /**
054   * Execute the list functions command.
055   *
056   * @param callingContext
057   *          the context of the command execution
058   * @param cmdLine
059   *          the parsed command line details
060   * @return the execution result
061   */
062  @SuppressWarnings({
063      "PMD.OnlyOneReturn" // readability
064  })
065  protected ExitStatus executeCommand(
066      @NonNull CallingContext callingContext,
067      @NonNull CommandLine cmdLine) {
068
069    Map<String, Map<String, List<IFunction>>> namespaceToNameToFunctionMap = FunctionService.getInstance().stream()
070        .collect(Collectors.groupingBy(
071            function -> function.getQName().getNamespaceURI(),
072            Collectors.groupingBy(
073                IFunction::getName,
074                Collectors.toList())));
075
076    Map<String, String> namespaceToPrefixMap = StaticContext.getWellKnownNamespacesMap().entrySet().stream()
077        .collect(Collectors.toMap(entry -> entry.getValue().toASCIIString(), Map.Entry::getKey));
078
079    List<String> namespaces = new ArrayList<>(namespaceToNameToFunctionMap.keySet());
080
081    Collections.sort(namespaces);
082
083    for (String namespace : namespaces) {
084      String prefix = namespaceToPrefixMap.get(namespace);
085
086      if (prefix == null) {
087        LOGGER.atInfo().log("In namespace '{}':", namespace);
088      } else {
089        LOGGER.atInfo().log("In namespace '{}' as '{}':", namespace, prefix);
090      }
091
092      Map<String, List<IFunction>> namespacedFunctions = namespaceToNameToFunctionMap.get(namespace);
093
094      List<String> names = new ArrayList<>(namespacedFunctions.keySet());
095      Collections.sort(names);
096
097      for (String name : names) {
098        List<IFunction> functions = namespacedFunctions.get(name);
099        Collections.sort(functions, Comparator.comparing(IFunction::arity));
100
101        for (IFunction function : functions) {
102          String functionRef = prefix == null
103              ? String.format("Q{%s}%s", function.getQName().getNamespaceURI(), function.getName())
104              : String.format("%s:%s", prefix, function.getName());
105
106          LOGGER.atInfo().log(String.format("%s(%s) as %s",
107              functionRef,
108              function.getArguments().isEmpty()
109                  ? ""
110                  : function.getArguments().stream().map(IArgument::toSignature)
111                      .collect(Collectors.joining(","))
112                      + (function.isArityUnbounded() ? ", ..." : ""),
113              function.getResult().toSignature()));
114        }
115      }
116    }
117    return ExitCode.OK.exit();
118  }
119}