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.StaticContext;
14  import gov.nist.secauto.metaschema.core.metapath.function.FunctionService;
15  import gov.nist.secauto.metaschema.core.metapath.function.IArgument;
16  import gov.nist.secauto.metaschema.core.metapath.function.IFunction;
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().getNamespaceURI(),
78              Collectors.groupingBy(
79                  IFunction::getName,
80                  Collectors.toList())));
81  
82      Map<String, String> namespaceToPrefixMap = StaticContext.getWellKnownNamespacesMap().entrySet().stream()
83          .collect(Collectors.toMap(entry -> entry.getValue().toASCIIString(), Map.Entry::getKey));
84  
85      List<String> namespaces = new ArrayList<>(namespaceToNameToFunctionMap.keySet());
86  
87      Collections.sort(namespaces);
88  
89      for (String namespace : namespaces) {
90        String prefix = namespaceToPrefixMap.get(namespace);
91  
92        if (prefix == null) {
93          LOGGER.atInfo().log("In namespace '{}':", namespace);
94        } else {
95          LOGGER.atInfo().log("In namespace '{}' as '{}':", namespace, prefix);
96        }
97  
98        Map<String, List<IFunction>> namespacedFunctions = namespaceToNameToFunctionMap.get(namespace);
99  
100       List<String> names = new ArrayList<>(namespacedFunctions.keySet());
101       Collections.sort(names);
102 
103       for (String name : names) {
104         List<IFunction> functions = namespacedFunctions.get(name);
105         Collections.sort(functions, Comparator.comparing(IFunction::arity));
106 
107         for (IFunction function : functions) {
108           String functionRef = prefix == null
109               ? String.format("Q{%s}%s", function.getQName().getNamespaceURI(), function.getName())
110               : String.format("%s:%s", prefix, function.getName());
111 
112           LOGGER.atInfo().log(String.format("%s(%s) as %s",
113               functionRef,
114               function.getArguments().isEmpty()
115                   ? ""
116                   : function.getArguments().stream().map(IArgument::toSignature)
117                       .collect(Collectors.joining(","))
118                       + (function.isArityUnbounded() ? ", ..." : ""),
119               function.getResult().toSignature()));
120         }
121       }
122     }
123     return ExitCode.OK.exit();
124   }
125 }