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}