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.InvalidArgumentException; 010import gov.nist.secauto.metaschema.core.util.CollectionUtil; 011 012import org.apache.commons.cli.CommandLine; 013import org.apache.commons.cli.Option; 014 015import java.util.Collection; 016import java.util.List; 017import java.util.stream.Collectors; 018 019import edu.umd.cs.findbugs.annotations.NonNull; 020import edu.umd.cs.findbugs.annotations.Nullable; 021 022/** 023 * A command line interface command. 024 */ 025public interface ICommand { 026 /** 027 * Get the name of the command. 028 * <p> 029 * This name is used to call the command as a command line argument. 030 * 031 * @return the command's name 032 */ 033 @NonNull 034 String getName(); 035 036 /** 037 * Get a description of what the command does. 038 * <p> 039 * This description is displayed in help output. 040 * 041 * @return the description 042 */ 043 @NonNull 044 String getDescription(); 045 046 /** 047 * Get the non-option arguments. 048 * 049 * @return the arguments, or an empty list if there are no arguments 050 */ 051 @NonNull 052 default List<ExtraArgument> getExtraArguments() { 053 return CollectionUtil.emptyList(); 054 } 055 056 /** 057 * Used to gather options directly associated with this command. 058 * 059 * @return the options 060 */ 061 @NonNull 062 default Collection<? extends Option> gatherOptions() { 063 // by default there are no options to handle 064 return CollectionUtil.emptyList(); 065 } 066 067 /** 068 * Get any sub-commands associated with this command. 069 * 070 * @return the sub-commands 071 */ 072 @NonNull 073 default Collection<ICommand> getSubCommands() { 074 // no sub-commands by default 075 return CollectionUtil.emptyList(); 076 } 077 078 /** 079 * Get a sub-command by it's command name. 080 * 081 * @param name 082 * the requested sub-command name 083 * @return the command or {@code null} if no sub-command exists with that name 084 */ 085 @Nullable 086 default ICommand getSubCommandByName(@NonNull String name) { 087 // no sub-commands by default 088 return null; 089 } 090 091 /** 092 * Determine if this command requires the use of a sub-command. 093 * 094 * @return {@code true} if a sub-command is required or {@code false} otherwise 095 */ 096 default boolean isSubCommandRequired() { 097 // no sub-commands by default 098 return false; 099 } 100 101 /** 102 * Validate the options provided on the command line based on what is required 103 * for this command. 104 * 105 * @param callingContext 106 * the context of the command execution 107 * @param commandLine 108 * the parsed command line details 109 * @throws InvalidArgumentException 110 * if a problem was found while validating the options 111 */ 112 default void validateOptions( 113 @NonNull CallingContext callingContext, 114 @NonNull CommandLine commandLine) throws InvalidArgumentException { 115 // by default there are no options to handle 116 } 117 118 /** 119 * Create a new executor for this command. 120 * 121 * @param callingContext 122 * the context of the command execution 123 * @param commandLine 124 * the parsed command line details 125 * @return the executor 126 */ 127 @NonNull 128 ICommandExecutor newExecutor( 129 @NonNull CallingContext callingContext, 130 @NonNull CommandLine commandLine); 131 132 /** 133 * Validates that the provided extra arguments meet expectations. 134 * 135 * @param callingContext 136 * the context of the command execution 137 * @param commandLine 138 * the parsed command line details 139 * @throws InvalidArgumentException 140 * if a problem was found while validating the extra arguments 141 */ 142 default void validateExtraArguments( 143 @NonNull CallingContext callingContext, 144 @NonNull CommandLine commandLine) 145 throws InvalidArgumentException { 146 147 validateSubCommandRequirement(); 148 validateArgumentCount(commandLine); 149 validateRequiredArguments(commandLine); 150 } 151 152 private void validateSubCommandRequirement() throws InvalidArgumentException { 153 if (isSubCommandRequired()) { 154 throw new InvalidArgumentException("Please choose a valid sub-command."); 155 } 156 } 157 158 private void validateArgumentCount(@NonNull CommandLine commandLine) throws InvalidArgumentException { 159 List<ExtraArgument> extraArguments = getExtraArguments(); 160 int maxArguments = extraArguments.size(); 161 List<String> actualArgs = commandLine.getArgList(); 162 163 if (actualArgs.size() > maxArguments) { 164 throw new InvalidArgumentException( 165 String.format("Too many extra arguments provided. Expected at most %d, but got %d.", 166 maxArguments, actualArgs.size())); 167 } 168 169 } 170 171 private void validateRequiredArguments(@NonNull CommandLine commandLine) throws InvalidArgumentException { 172 List<String> actualArgs = commandLine.getArgList(); 173 List<ExtraArgument> requiredExtraArguments = getExtraArguments().stream() 174 .filter(ExtraArgument::isRequired) 175 .collect(Collectors.toUnmodifiableList()); 176 177 if (actualArgs.size() < requiredExtraArguments.size()) { 178 throw new InvalidArgumentException( 179 String.format("Missing required arguments: %s. Expected %d required arguments, but got %d.", 180 requiredExtraArguments.stream() 181 .map(arg -> "<" + arg.getName() + ">") 182 .collect(Collectors.joining(" ")), 183 requiredExtraArguments.size(), 184 actualArgs.size())); 185 } 186 } 187}