1   /*
2    * SPDX-FileCopyrightText: none
3    * SPDX-License-Identifier: CC0-1.0
4    */
5   
6   package gov.nist.secauto.metaschema.cli.processor.command;
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.core.util.ObjectUtils;
12  
13  import org.apache.commons.cli.CommandLine;
14  
15  import java.util.Collection;
16  import java.util.Collections;
17  import java.util.LinkedHashMap;
18  import java.util.Map;
19  import java.util.stream.Collectors;
20  
21  import edu.umd.cs.findbugs.annotations.NonNull;
22  
23  /**
24   * A base class for a command that supports hierarchical command structure with
25   * child commands. This class provides the foundation for implementing complex
26   * CLI commands that can have multiple levels of sub-commands.
27   * <p>
28   * This class is thread-safe and supports concurrent access to command handlers.
29   */
30  public abstract class AbstractParentCommand implements ICommand {
31    @NonNull
32    private final Map<String, ICommand> commandToSubcommandHandlerMap;
33  
34    /**
35     * Construct a new parent command.
36     */
37    protected AbstractParentCommand() {
38      this.commandToSubcommandHandlerMap = ObjectUtils.notNull(Collections.synchronizedMap(new LinkedHashMap<>()));
39    }
40  
41    /**
42     * Add a child command.
43     *
44     * @param handler
45     *          the command handler for the child command
46     */
47    protected final void addCommandHandler(ICommand handler) {
48      String commandName = handler.getName();
49      this.commandToSubcommandHandlerMap.put(commandName, handler);
50    }
51  
52    @Override
53    public ICommand getSubCommandByName(String name) {
54      return commandToSubcommandHandlerMap.get(name);
55    }
56  
57    @Override
58    public Collection<ICommand> getSubCommands() {
59      return ObjectUtils.notNull(Collections.unmodifiableCollection(commandToSubcommandHandlerMap.values()));
60    }
61  
62    @Override
63    public boolean isSubCommandRequired() {
64      return true;
65    }
66  
67    @Override
68    public ICommandExecutor newExecutor(CallingContext callingContext, CommandLine cmdLine) {
69      return ICommandExecutor.using(callingContext, cmdLine, this::executeCommand);
70    }
71  
72    @NonNull
73    private ExitStatus executeCommand(
74        @NonNull CallingContext callingContext,
75        @SuppressWarnings("unused") @NonNull CommandLine commandLine) {
76      callingContext.showHelp();
77      ExitStatus status;
78      if (isSubCommandRequired()) {
79        status = ExitCode.INVALID_COMMAND
80            .exitMessage("Please use one of the following sub-commands: " +
81                getSubCommands().stream()
82                    .map(ICommand::getName)
83                    .collect(Collectors.joining(", ")));
84      } else {
85        status = ExitCode.OK.exit();
86      }
87      return status;
88    }
89  
90  }