001/* 002 * ============================================================================ 003 * Copyright © 2002-2024 by Thomas Thrien. 004 * All Rights Reserved. 005 * ============================================================================ 006 * Licensed to the public under the agreements of the GNU Lesser General Public 007 * License, version 3.0 (the "License"). You may obtain a copy of the License at 008 * 009 * http://www.gnu.org/licenses/lgpl.html 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 013 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 014 * License for the specific language governing permissions and limitations 015 * under the License. 016 */ 017 018package org.tquadrat.foundation.config.spi; 019 020import static java.lang.Character.isWhitespace; 021import static java.util.Locale.ROOT; 022import static org.apiguardian.api.API.Status.STABLE; 023import static org.tquadrat.foundation.config.CLIBeanSpec.LEAD_IN; 024import static org.tquadrat.foundation.config.internal.Commons.retrieveMessage; 025import static org.tquadrat.foundation.i18n.TextUse.USAGE; 026import static org.tquadrat.foundation.lang.Objects.nonNull; 027import static org.tquadrat.foundation.lang.Objects.requireNonNullArgument; 028import static org.tquadrat.foundation.lang.Objects.requireNotEmptyArgument; 029 030import java.util.Optional; 031 032import org.apiguardian.api.API; 033import org.tquadrat.foundation.annotation.ClassVersion; 034import org.tquadrat.foundation.config.CmdLineException; 035import org.tquadrat.foundation.config.cli.CmdLineValueHandler; 036import org.tquadrat.foundation.i18n.Message; 037import org.tquadrat.foundation.i18n.Translation; 038 039/** 040 * Base class for the run-time copies of the 041 * {@link org.tquadrat.foundation.config.Option @Option} or 042 * {@link org.tquadrat.foundation.config.Argument @Argument} 043 * annotation. By definition, unnamed options are arguments, and named options 044 * are <i>real</i> command line options. 045 * 046 * @see CLIOptionDefinition 047 * @see CLIArgumentDefinition 048 * 049 * @extauthor Thomas Thrien - thomas.thrien@tquadrat.org 050 * @thanks Mark Sinke 051 * @version $Id: CLIDefinition.java 1258 2026-06-04 18:33:06Z tquadrat $ 052 * @since 0.0.1 053 * 054 * @UMLGraph.link 055 */ 056@SuppressWarnings( {"ConstantExpression", "BooleanMethodNameMustStartWithQuestion"} ) 057@ClassVersion( sourceVersion = "$Id: CLIDefinition.java 1258 2026-06-04 18:33:06Z tquadrat $" ) 058@API( status = STABLE, since = "0.0.1" ) 059public abstract class CLIDefinition 060{ 061 /*-----------*\ 062 ====** Constants **======================================================== 063 \*-----------*/ 064 /** 065 * The message indicating that the empty String is not allowed as an 066 * option name. 067 */ 068 @SuppressWarnings( "SimplifiableAnnotation" ) 069 @API( status = STABLE, since = "0.1.0" ) 070 @Message 071 ( 072 description = "The message indicating that the empty String is not allowed as an option name.", 073 translations = 074 { 075 @Translation( language = "en", text = "The empty String is not a valid option name" ) 076 } 077 ) 078 public static final int MSGKEY_EmptyIsInvalid = 31; 079 080 /** 081 * The message indicating an invalid option name. 082 */ 083 @SuppressWarnings( "SimplifiableAnnotation" ) 084 @API( status = STABLE, since = "0.1.0" ) 085 @Message 086 ( 087 description = "The message indicating an invalid option name.", 088 translations = 089 { 090 @Translation( language = "en", text = "'%1$s' is not a valid option name" ) 091 } 092 ) 093 public static final int MSGKEY_InvalidName = 32; 094 095 /** 096 * The message indicating that the option name is reserved. 097 */ 098 @SuppressWarnings( "SimplifiableAnnotation" ) 099 @API( status = STABLE, since = "0.1.0" ) 100 @Message 101 ( 102 description = "The message indicating that the option name is reserved.", 103 translations = 104 { 105 @Translation( language = "en", text = "'%1$s' is a reserved name" ) 106 } 107 ) 108 public static final int MSGKEY_ReservedName = 33; 109 110 /** 111 * The message indicating a whitespace character as option name. 112 */ 113 @SuppressWarnings( "SimplifiableAnnotation" ) 114 @API( status = STABLE, since = "0.1.0" ) 115 @Message 116 ( 117 description = "The message indicating a whitespace character as option name.", 118 translations = 119 { 120 @Translation( language = "en", text = "Character after '%s' may not be whitespace" ) 121 } 122 ) 123 public static final int MSGKEY_Whitespace1 = 34; 124 125 /** 126 * The message indicating a whitespace character as part of an option 127 * name. 128 */ 129 @SuppressWarnings( "SimplifiableAnnotation" ) 130 @API( status = STABLE, since = "0.1.0" ) 131 @Message 132 ( 133 description = "The message indicating a whitespace character as part of an option name.", 134 translations = 135 { 136 @Translation( language = "en", text = "An option name may not contain any whitespace characters" ) 137 } 138 ) 139 public static final int MSGKEY_Whitespace2 = 35; 140 141 /** 142 * The message indicating an invalid lead-in for an option name. 143 */ 144 @SuppressWarnings( "SimplifiableAnnotation" ) 145 @API( status = STABLE, since = "0.0.1" ) 146 @Message 147 ( 148 description = "The message indicating an invalid lead-in for an option name.", 149 translations = 150 { 151 @Translation( language = "en", text = "The name '%2$s' must start with '%1$s'" ) 152 } 153 ) 154 public static final int MSGKEY_WrongLeadIn = 36; 155 156 /** 157 * <p>{@summary The format for the default usage keys.}</p> 158 * <p>The resource bundle key for a 159 * {@link org.tquadrat.foundation.i18n.TextUse#USAGE USAGE} 160 * text is prepended with the name of the class that defines it.</p> 161 */ 162 public static final String USAGE_KEY_FORMAT = String.format( "%%s.%s_%%s", USAGE.name() ); 163 164 /*------------*\ 165 ====** Attributes **======================================================= 166 \*------------*/ 167 /** 168 * The optional format string. 169 */ 170 @SuppressWarnings( "OptionalUsedAsFieldOrParameterType" ) 171 private final Optional<String> m_Format; 172 173 /** 174 * The handler that is used to parse and store the option or argument 175 * value. 176 */ 177 private final CmdLineValueHandler<?> m_Handler; 178 179 /** 180 * {@true} if this is an argument, {@false} if it is 181 * an option. 182 */ 183 private final boolean m_IsArgument; 184 185 /** 186 * {@true} if the target property is multivalued, 187 * {@false} otherwise. 188 */ 189 private final boolean m_IsMultiValued; 190 191 /** 192 * {@true} if the option or argument is required, 193 * {@false} otherwise. 194 */ 195 private final boolean m_IsRequired; 196 197 /** 198 * The name of the meta variable that is used in examples. 199 */ 200 private final String m_MetaVar; 201 202 /** 203 * The name of the property. 204 */ 205 private final String m_Property; 206 207 /** 208 * The usage text. 209 */ 210 @SuppressWarnings( "OptionalUsedAsFieldOrParameterType" ) 211 private final Optional<String> m_Usage; 212 213 /** 214 * The resource key for the usage text. 215 */ 216 @SuppressWarnings( "OptionalUsedAsFieldOrParameterType" ) 217 private final Optional<String> m_UsageKey; 218 219 /*--------------*\ 220 ====** Constructors **===================================================== 221 \*--------------*/ 222 /** 223 * Creates a new {@code CLIDefinition} instance. 224 * 225 * @param property The name of the property. 226 * @param isArgument {@true} for an argument, 227 * {@false} for an option. 228 * @param usage The usage text. 229 * @param usageKey The resource bundle key for the usage text. 230 * @param metaVar The meta variable name. 231 * @param required {@true} if the argument or option is 232 * mandatory. 233 * @param handler The handler for the option or argument value. 234 * @param multiValued {@true} if the option or argument allows 235 * more than one value. 236 * @param format The optional format. 237 */ 238 @SuppressWarnings( "ConstructorWithTooManyParameters" ) 239 protected CLIDefinition( final String property, final boolean isArgument, final String usage, final String usageKey, final String metaVar, final boolean required, final CmdLineValueHandler<?> handler, final boolean multiValued, final String format ) 240 { 241 m_Property = requireNotEmptyArgument( property, "property" ); 242 m_IsArgument = isArgument; 243 m_Usage = Optional.ofNullable( usage ); 244 m_UsageKey = Optional.ofNullable( usageKey ); 245 m_MetaVar = nonNull( metaVar ) ? metaVar : property.toUpperCase( ROOT ); 246 m_IsRequired = required; 247 m_Handler = requireNonNullArgument( handler, "handler" ); 248 //noinspection ThisEscapedInObjectConstruction 249 m_Handler.setContext( this ); 250 m_IsMultiValued = multiValued; 251 m_Format = Optional.ofNullable( format ); 252 } // CLIDefinition() 253 254 /*---------*\ 255 ====** Methods **========================================================== 256 \*---------*/ 257 /** 258 * Returns the optional format. 259 * 260 * @return An instance of 261 * {@link Optional} 262 * that holds the format. 263 * 264 * @see org.tquadrat.foundation.config.Argument#format() 265 * @see org.tquadrat.foundation.config.Option#format() 266 */ 267 public final Optional<String> format() { return m_Format; } 268 269 /** 270 * Returns the sort key for the option or argument. 271 * 272 * @return The sort key. 273 */ 274 public abstract String getSortKey(); 275 276 /** 277 * Returns the handler. 278 * 279 * @return The handler 280 */ 281 public final CmdLineValueHandler<?> handler() { return m_Handler; } 282 283 /** 284 * Returns a flag that indicates whether this is the definition for an 285 * argument or an option. 286 * 287 * @return {@true} if this instance of {@code OptionDef} is defined 288 * by an 289 * {@link org.tquadrat.foundation.config.Argument @Argument} 290 * annotation, {@false} if it is defined by an 291 * {@link org.tquadrat.foundation.config.Option @Option} 292 * annotation. 293 */ 294 public final boolean isArgument() { return m_IsArgument; } 295 296 /** 297 * Returns a flag that indicates whether this option or argument is 298 * multivalued. 299 * 300 * @return {@true} if multivalued, {@false} 301 * otherwise. 302 */ 303 public final boolean isMultiValued() { return m_IsMultiValued; } 304 305 /** 306 * The name of the meta variable. 307 * 308 * @return The meta variable. 309 */ 310 public final String metaVar() { return m_MetaVar; } 311 312 /** 313 * Processes the given parameter(s). 314 * 315 * @param params A reference to the command line arguments as for this 316 * option or argument definition. This method can use this object to 317 * access the values if necessary. The object is valid only during the 318 * method call. 319 * @return The number of command line arguments consumed by this method. 320 * For example, it will return 0 if option defined by this instance 321 * does not take any parameters. 322 * @throws CmdLineException Parsing the parameter(s) failed. 323 */ 324 public final int processParameters( final Parameters params ) throws CmdLineException 325 { 326 final var retValue = m_Handler.parseCmdLine( params ); 327 328 //---* Done *---------------------------------------------------------- 329 return retValue; 330 } // processParameters() 331 332 /** 333 * Returns the property name for this CLI element. 334 * 335 * @return The property name. 336 */ 337 public final String propertyName() { return m_Property; } 338 339 /** 340 * Returns a flag indicating if the option or argument is mandatory. 341 * 342 * @return {@true} if the argument or option is required, 343 * {@false} otherwise. 344 * 345 * @see org.tquadrat.foundation.config.Argument#required() 346 * @see org.tquadrat.foundation.config.Option#required() 347 */ 348 public final boolean required() { return m_IsRequired; } 349 350 /** 351 * {@inheritDoc}<br> 352 */ 353 @Override 354 public abstract String toString(); 355 356 /** 357 * Returns the usage text. 358 * 359 * @return An instance of 360 * {@link Optional} 361 * that holds the usage text. 362 */ 363 public final Optional<String> usage() { return m_Usage; } 364 365 /** 366 * Returns the resource key for the usage text. 367 * 368 * @return An instance of 369 * {@link Optional} 370 * that holds the key for the usage text. 371 */ 372 public final Optional<String> usageKey() { return m_UsageKey; } 373 374 /** 375 * Checks whether the given name for a command line option is valid. 376 * 377 * @param name The intended name for the command line option. 378 * @return {@true} if the given name is valid, {@false} 379 * otherwise. 380 * @throws IllegalArgumentException The given name is invalid. 381 */ 382 @SuppressWarnings({"StaticMethodOnlyUsedInOneClass", "ConstantValue"}) 383 public static final boolean validateOptionName( final String name ) throws IllegalArgumentException 384 { 385 final var retValue = switch( requireNonNullArgument( name, "name" ).length() ) 386 { 387 case 0 -> throw new IllegalArgumentException( retrieveMessage( MSGKEY_EmptyIsInvalid, true ) ); 388 case 1 -> 389 { 390 if( name.equals( LEAD_IN ) ) 391 { 392 throw new IllegalArgumentException( retrieveMessage( MSGKEY_InvalidName, true, LEAD_IN ) ); 393 } 394 throw new IllegalArgumentException( retrieveMessage( MSGKEY_WrongLeadIn, true, LEAD_IN, name ) ); 395 } 396 397 case 2 -> 398 { 399 if( name.equals( LEAD_IN + LEAD_IN ) ) 400 { 401 throw new IllegalArgumentException( retrieveMessage( MSGKEY_ReservedName, true, LEAD_IN + LEAD_IN ) ); 402 } 403 else if( !name.startsWith( LEAD_IN ) ) 404 { 405 throw new IllegalArgumentException( retrieveMessage( MSGKEY_WrongLeadIn, true, LEAD_IN, name ) ); 406 } 407 else if( isWhitespace( name.charAt( 1 ) ) ) 408 { 409 throw new IllegalArgumentException( retrieveMessage( MSGKEY_Whitespace1, true, LEAD_IN ) ); 410 } 411 yield true; 412 } 413 414 default -> 415 { 416 if( !name.startsWith( LEAD_IN + LEAD_IN ) ) 417 { 418 throw new IllegalArgumentException( retrieveMessage( MSGKEY_WrongLeadIn, true, LEAD_IN + LEAD_IN, name ) ); 419 } 420 else if( name.codePoints().anyMatch( Character::isWhitespace ) ) 421 { 422 throw new IllegalArgumentException( retrieveMessage( MSGKEY_Whitespace2, true ) ); 423 } 424 yield true; 425 } 426 }; 427 428 //---* Done *---------------------------------------------------------- 429 return retValue; 430 } // validateOptionName() 431} 432// class CLIDefinition 433 434/* 435 * End of File 436 */