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}