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