001/*
002 * SPDX-FileCopyrightText: none
003 * SPDX-License-Identifier: CC0-1.0
004 */
005
006package gov.nist.secauto.metaschema.cli.processor.command;
007
008import gov.nist.secauto.metaschema.cli.processor.CLIProcessor.CallingContext;
009import gov.nist.secauto.metaschema.cli.processor.ExitCode;
010import gov.nist.secauto.metaschema.cli.processor.ExitStatus;
011import gov.nist.secauto.metaschema.core.util.ObjectUtils;
012
013import org.apache.commons.cli.CommandLine;
014
015import java.util.Collection;
016import java.util.Collections;
017import java.util.LinkedHashMap;
018import java.util.Map;
019import java.util.stream.Collectors;
020
021import edu.umd.cs.findbugs.annotations.NonNull;
022
023/**
024 * A base class for a command that supports hierarchical command structure with
025 * child commands. This class provides the foundation for implementing complex
026 * CLI commands that can have multiple levels of sub-commands.
027 * <p>
028 * This class is thread-safe and supports concurrent access to command handlers.
029 */
030public abstract class AbstractParentCommand implements ICommand {
031  @NonNull
032  private final Map<String, ICommand> commandToSubcommandHandlerMap;
033
034  /**
035   * Construct a new parent command.
036   */
037  protected AbstractParentCommand() {
038    this.commandToSubcommandHandlerMap = ObjectUtils.notNull(Collections.synchronizedMap(new LinkedHashMap<>()));
039  }
040
041  /**
042   * Add a child command.
043   *
044   * @param handler
045   *          the command handler for the child command
046   */
047  protected final void addCommandHandler(ICommand handler) {
048    String commandName = handler.getName();
049    this.commandToSubcommandHandlerMap.put(commandName, handler);
050  }
051
052  @Override
053  public ICommand getSubCommandByName(String name) {
054    return commandToSubcommandHandlerMap.get(name);
055  }
056
057  @Override
058  public Collection<ICommand> getSubCommands() {
059    return ObjectUtils.notNull(Collections.unmodifiableCollection(commandToSubcommandHandlerMap.values()));
060  }
061
062  @Override
063  public boolean isSubCommandRequired() {
064    return true;
065  }
066
067  @Override
068  public ICommandExecutor newExecutor(CallingContext callingContext, CommandLine cmdLine) {
069    return ICommandExecutor.using(callingContext, cmdLine, this::executeCommand);
070  }
071
072  @NonNull
073  private ExitStatus executeCommand(
074      @NonNull CallingContext callingContext,
075      @SuppressWarnings("unused") @NonNull CommandLine commandLine) {
076    callingContext.showHelp();
077    ExitStatus status;
078    if (isSubCommandRequired()) {
079      status = ExitCode.INVALID_COMMAND
080          .exitMessage("Please use one of the following sub-commands: " +
081              getSubCommands().stream()
082                  .map(ICommand::getName)
083                  .collect(Collectors.joining(", ")));
084    } else {
085      status = ExitCode.OK.exit();
086    }
087    return status;
088  }
089
090}