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  public class ListFunctionsSubcommand
32      extends AbstractTerminalCommand {
33    private static final Logger LOGGER = LogManager.getLogger(ListFunctionsSubcommand.class);
34  
35    @NonNull
36    private static final String COMMAND = "list-functions";
37  
38    @Override
39    public String getName() {
40      return COMMAND;
41    }
42  
43    @Override
44    public String getDescription() {
45      return "Get a listing of supported Metapath functions";
46    }
47  
48    @Override
49    public ICommandExecutor newExecutor(CallingContext callingContext, CommandLine cmdLine) {
50      return ICommandExecutor.using(callingContext, cmdLine, this::executeCommand);
51    }
52  
53    /**
54     * Execute the list functions command.
55     *
56     * @param callingContext
57     *          the context of the command execution
58     * @param cmdLine
59     *          the parsed command line details
60     * @return the execution result
61     */
62    @SuppressWarnings({
63        "PMD.OnlyOneReturn" // readability
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().getNamespaceURI(),
72              Collectors.groupingBy(
73                  IFunction::getName,
74                  Collectors.toList())));
75  
76      Map<String, String> namespaceToPrefixMap = StaticContext.getWellKnownNamespacesMap().entrySet().stream()
77          .collect(Collectors.toMap(entry -> entry.getValue().toASCIIString(), Map.Entry::getKey));
78  
79      List<String> namespaces = new ArrayList<>(namespaceToNameToFunctionMap.keySet());
80  
81      Collections.sort(namespaces);
82  
83      for (String namespace : namespaces) {
84        String prefix = namespaceToPrefixMap.get(namespace);
85  
86        if (prefix == null) {
87          LOGGER.atInfo().log("In namespace '{}':", namespace);
88        } else {
89          LOGGER.atInfo().log("In namespace '{}' as '{}':", namespace, prefix);
90        }
91  
92        Map<String, List<IFunction>> namespacedFunctions = namespaceToNameToFunctionMap.get(namespace);
93  
94        List<String> names = new ArrayList<>(namespacedFunctions.keySet());
95        Collections.sort(names);
96  
97        for (String name : names) {
98          List<IFunction> functions = namespacedFunctions.get(name);
99          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 }