1   /*
2    * SPDX-FileCopyrightText: none
3    * SPDX-License-Identifier: CC0-1.0
4    */
5   
6   package dev.metaschema.cli.commands.metapath;
7   
8   import org.apache.commons.cli.CommandLine;
9   import org.apache.logging.log4j.LogManager;
10  import org.apache.logging.log4j.Logger;
11  
12  import java.util.ArrayList;
13  import java.util.Collections;
14  import java.util.Comparator;
15  import java.util.List;
16  import java.util.Map;
17  import java.util.stream.Collectors;
18  
19  import dev.metaschema.cli.processor.CallingContext;
20  import dev.metaschema.cli.processor.ExitCode;
21  import dev.metaschema.cli.processor.ExitStatus;
22  import dev.metaschema.cli.processor.command.AbstractTerminalCommand;
23  import dev.metaschema.cli.processor.command.ICommandExecutor;
24  import dev.metaschema.core.metapath.function.FunctionService;
25  import dev.metaschema.core.metapath.function.IArgument;
26  import dev.metaschema.core.metapath.function.IFunction;
27  import dev.metaschema.core.qname.WellKnown;
28  import edu.umd.cs.findbugs.annotations.NonNull;
29  
30  /**
31   * This command list the Metapath functions currently provided by the Metaschema
32   * runtime.
33   */
34  class ListFunctionsSubcommand
35      extends AbstractTerminalCommand {
36    private static final Logger LOGGER = LogManager.getLogger(ListFunctionsSubcommand.class);
37  
38    @NonNull
39    private static final String COMMAND = "list-functions";
40  
41    @Override
42    public String getName() {
43      return COMMAND;
44    }
45  
46    @Override
47    public String getDescription() {
48      return "Get a listing of supported Metapath functions";
49    }
50  
51    @Override
52    public ICommandExecutor newExecutor(CallingContext callingContext, CommandLine cmdLine) {
53      return ICommandExecutor.using(callingContext, cmdLine, this::executeCommand);
54    }
55  
56    /**
57     * Execute the list functions command.
58     *
59     * @param callingContext
60     *          the context of the command execution
61     * @param cmdLine
62     *          the parsed command line details
63     * @return the execution result
64     */
65    protected ExitStatus executeCommand(
66        @NonNull CallingContext callingContext,
67        @NonNull CommandLine cmdLine) {
68  
69      Map<String, Map<String, List<IFunction>>> namespaceToNameToFunctionMap = FunctionService.getInstance().stream()
70          .collect(Collectors.groupingBy(
71              function -> function.getQName().getNamespace(),
72              Collectors.groupingBy(
73                  IFunction::getName,
74                  Collectors.toList())));
75  
76      List<String> namespaces = new ArrayList<>(namespaceToNameToFunctionMap.keySet());
77  
78      Collections.sort(namespaces);
79  
80      for (String namespace : namespaces) {
81        assert namespace != null;
82        String prefix = WellKnown.getWellKnownPrefixForUri(namespace);
83  
84        if (prefix == null) {
85          LOGGER.atInfo().log("In namespace '{}':", namespace);
86        } else {
87          LOGGER.atInfo().log("In namespace '{}' as '{}':", namespace, prefix);
88        }
89  
90        Map<String, List<IFunction>> namespacedFunctions = namespaceToNameToFunctionMap.get(namespace);
91  
92        List<String> names = new ArrayList<>(namespacedFunctions.keySet());
93        Collections.sort(names);
94  
95        for (String name : names) {
96          List<IFunction> functions = namespacedFunctions.get(name);
97          Collections.sort(functions, Comparator.comparing(IFunction::arity));
98  
99          for (IFunction function : functions) {
100           String functionRef = prefix == null
101               ? String.format("Q{%s}%s", function.getQName().getNamespace(), function.getName())
102               : String.format("%s:%s", prefix, function.getName());
103 
104           LOGGER.atInfo().log(String.format("%s(%s) as %s",
105               functionRef,
106               function.getArguments().isEmpty()
107                   ? ""
108                   : function.getArguments().stream().map(IArgument::toSignature)
109                       .collect(Collectors.joining(","))
110                       + (function.isArityUnbounded() ? ", ..." : ""),
111               function.getResult().toSignature()));
112         }
113       }
114     }
115     return ExitCode.OK.exit();
116   }
117 }