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