Value types enable binary switches and arguments to be converted to a value when the command line option or argument is read. They take the string read in from the command line and convert it to an underlying Java type.
All command line options start life as a string. Value types enable conversion
of the string read from the command line to an underlying type: For example, if
the type is a double, then the command line switch value will be converted
into a double and will return a double when retrieved; likewise, a
datafile type will be converted from a string to a file and the return value
will be the contents of the file as a byte[] array.
Properties can be defined for value types in order to provide error checking and
data manipulation in order to perform the task of converting the command line
value to the underlying type. Value types throw a
ValueTypeConstructionException if there is anything wrong with the properties
(including if the properties themselves are incorrect).
Several value types are provided out of the box but API users can define their own value types, discussed below. Furthermore it is also possible to defined custom value value types, as well as replace existing value types with new implementations if needed.
Creation of new value types and replacing existing value types are discussed below the descriptions of provided value types.
Comprise of the following types:
bigdecimal: Return type represented by java.math.BigDecimal.biginteger: Return type represented by java.math.BigInteger.byte: Return type represented by java.lang.Byte.double: Return type represented by java.lang.Double.float: Return type represented by java.lang.Float.int: Return type represented by java.lang.Integer.long: Return type represented by java.lang.Long.short: Return type represented by java.lang.Short.Properties for all numerical types:
minimum: Set the minimum value that will be accepted. Optional.maximum: Set the maximum value that will be accepted. Optional.Example: Define a float for option name float-val with a minimum value of 0
and a maximum value of 10:
option.float-val.type = float
option.float-val.properties = min = 0, max = 10
booleanInput string as a boolean value.
Return type represented by java.lang.Bool.
Boolean values will be represented by true and false unless overridden using
truthMappings.
Properties:
truthMappings: specify any number of semi-colon separated key-value
pairs of the form x = y values of truth properties. The key is a name
that maps to a boolean value (ignoring case) true or false. Any
number of mappings may be supplied but must contain at least one true
value and at least one false value. Duplicate keys are not permitted.
All keys and values are converted to lower case.Example: Define properties that treats on and y as true and off and n
as false for option name running:
option.running.type = boolean
option.running.properties = truthMappings = on = true ; y = true ; off = false ; n = false
charInput string as a single character.
Return type represented by java.lang.Character.
Properties:
includes: Regular expression or string of characters to match. Unless
regex is set to true, will be treated as a string of characters. Mutually
exclusive with excludes. Optional.excludes: Regular expression or string of characters to exclude. Unless
regex is set to true, will be treated as a string of characters. Mutually
exclusive with includes. Optional.regex: true to treat the includes or excludes string as a regular
expression.Example: Match characters A through C and X through Z as uppercase or lower case
as a non-regular expression for option name char-val:
option.char-val.type = char
option.char-val.properties = includes = abcABCxyzXYZ
Example: As above but use a regular expression:
option.char-val.type = char
option.char-val.properties = includes = [a-cA-Cx-zX-Z], regex = true
datafileInput string as an existing file.
Value type to read a byte array from the given file which must exist.
Return type represented by a byte[] array.
Properties:
encoding: specify the encoding when reading the file. Optional; defaults to
UTF-8.dateInput string as a date.
Value type representing a date/time.
Return type represented by java.util.Date.
Properties:
dateFormat: Required. Specify date format according to Java date formatting
rules, for example yyyy/MM/dd HH:mm:ss.dirInput string as a directory.
Value type representing a directory; by default, no checks are made as to the existence of the directory, although users can specify the file type property that will perform necessary checks.
Return type represented by java.io.File.
Properties:
dirType: Optional. One of:
exists: The directory must exist.!exists: The directory must not exist.mkdirs: Optional; if the directory does not exist, create it. If set, cannot
have any of the following search-based properties set on it.recursive: Optional; one of true or false. If true, recursively
traverse all directories within the specified directory, otherwise just scan
the directory specified for this directory type. Callers should ensure they
have implemented org.statefive.clic.valuetype.DirUpdateListener to receive
updates as the directory/directories are scanned. If using recursion,
directories will be scanned using a breadth-first strategy unless a different
strategy is employed (see below). If this property is set implementations are
also required to set the listener-id (again, see below);listener-id: Optional, unless recursive is set in which case it is
mandatory: The listener ID. When updates to a directory are called by one of
the updates specified inDirUpdateListener, listeners can check to see if the
ID from the update matches their registered ID.suffixes: Optional. Space-separated list of file suffixes that any files
must match . Only files that match the specified suffixes (ignoring case) will
be included in any updates to DirUpdateListener
directoryTraversed(java.io.File, java.io.File[], java.lang.String).To receive updates from the API, callers must implement
org.statefive.clic.valuetype.DirUpdateListener, and add themselves to the list
of directory listeners via
Clc.getInstance().addDirUpdateListener(listener);
The following method must be implemented:
boolean directoryTraversed(File dir, File[] files, String listenerId)
The return value, boolean, determines if the API should continue processing;
if the implementation returns false, the directory will not be recursively
traversed.
For each directory traversed, the API will update the method with the current
directory and the list of files (not directories) within the directory, along
with the listener-id of the directory type (se above).
listener-id enables the same application to register several directory types
that search different directories and enable callers to determine which
directory type the update was for.
fileInput string as a file.
Return type represented by java.io.File.
Value type representing a file; by default, no checks are made, although users can specify the file type property that will perform necessary checks.
Properties:
fileType: Optional. One of:
exists: The file must exist.!exists: The file must not exist.listInput string representing a list of of values. Defaults to a list of string
values unless overridden by the appropriate property. Can contain any value
value type except list.
Lists are defined as a comma-separated list of values, unless the default character is overridden. If elements of a list contain the separator character, escape them with a backslash.
Return type represented by java.util.List.
Properties:
separatorChar: Character used to separate elements. Optional, defaults to a
comma;listValueType: List value type to set. Optional, defaults to string if
not defined;listValueTypeProperties: Properties to pass to the underlying value type of
list elements. Optional; andlistValueTypeDefaultValue: Default value to set list elements to if any of
the items between separator characters are empty. Optional.Example: Create a list type of integers that must be between 0 and 10 (inclusive), with a default value of 5:
listValueType = int, listValueTypeProperties = min = 0, max = 10, listValueTypeDefaultValue = 5
Example: Create a list of strings that must all be lowercase and where the
default value if unset will be set to unknown:
listValueType = string, listValueTypeProperties = match = [a-z]+, listValueTypeDefaultValue = unknown
stringThis is the default value of value types unless the type is specified as
anything other than string.
Return type represented by java.lang.String.
Properties:
match: Regular expression that data must match, an error being produced if
the string doesn't match. Optional.Example: Match oranges, apples or pears:
match = oranges|apples|pears
Example: Match any string that starts with F and ends in s with any number
of alphanumeric characteres in between:
match = F[a-zA-Z0-9]*s
Creating custom value types is easy and requires:
ValueType;ValueTypeFactory;To create a custom value type, create an appropriately named class that
implements ValueType<T>. The following methods must be implemented:
| Method | Description |
|---|---|
T getValue(String data) throws ValueTypeCreationException |
Given the specified data (typically passed in via data from the command line), attempt to convert to the appropriate type. |
void setDefault(String data) throws ValueTypeCreationException |
Set the default value. |
void setProperties(String properties) throws ValueTypeCreationException |
Set any properties for the type. It is up to callers how properties are defined and parsed. |
public String getPackageName() |
The package name of the underlying converted type. If a primitive type, implementations should return null. |
public String getJavaClassName() |
Get the Java class name of the underlying converted type. If a primitive type, implementations should return null. |
public String getJavaPrimitiveName() |
If a Java primitive, get the name of the Java primitive name used in declarations, for example int, boolean etc.; if the underlying type is not a primitive, implementations should return null. |
public String getValueTypeName() |
Get the name of the value type as recognised by the API; this is the <type> name specified when declaring an option.<config-name>.type = <type> or args.<config-name>.type = <type> line in the CLC file. |
It is implementation-specific how properties set via setProperties(String) are
parsed. Typically, = is used to assign values to property keys, and comma or
semi-colon used to separate properties. This is not required and implementations
can define their own syntax for defining how properties are assigned and what
method is used to separate properties.
Let's define a new value type to take a file and check that it's size isn't
greater than a specified (optional) maximum file size. It will provide a
property named maxSize that can be defined to check the file when read in from
the command line and check that the file does not exceed the maximum size
allowed.
First, the class declaration - the class will be named FileUploadType:
public FileUploadType implements ValueType<File>
Since the value type is java.io.File, this means that when option string
values are passed to the value type, the type will be converted to a File.
We'll store the followng member variables in order to create the new value type:
// file to upload:
private File file;
// maximum size of the file, default 5MB:
private long maxSize - 1024 * 1024 * 5;
// property text:
private String maxSizeText;
Prior to T getValue(String) being called by the value type factory, properties
are extracted. We need to cater for the property maxSize which will be a
private long member of the class, which can also be defined in the same
format, e.g. 200MB, etc.
First, the code to parse the property maxSize:
@Override
public void setProperties(String properties) throws ValueTypeCreationException {
if (!properties.contains("=")) {
throw new ValueTypeCreationException("Invalid properties: " + properties);
}
String[] data = properties.split("=");
String propKey = data[0].trim();
String propValue = data[0].trim();
if (!"maxSize".equals(propKey)) {
throw new ValueTypeCreationException("Invalid property: "
+ propKey + "; expected 'maxSize'");
}
// property value must be a whole number followed by either 'MB'
// or 'GB'; use a captured groups to get both parts:
String regex = "([0-9])+([MmGg][Bb])";
Pattern p = Pattern.compile(regex);
Matcher m = p.matcher(propValue);
if (m.matches()) {
maxSizeText = propValue;
long size = Long.parseLong(m.group(1));
String sizeStr = m.group(2);
if ("MB".equalsIgnoreCase(sizeStr)) {
maxSize = size * 1024 * 1024;
} else {
maxSize = size * 1024 * 1024 * 1024;
}
} else {
throw new ValueTypeCreationException("Invalid value."
+ " Expected <size><MB|GB>, e.g. 250MB.");
}
}
Now the actual call to create the file and check the file size when passed in via the command line:
@Override
public File getValue(String data) throws ValueTypeCreationException {
file = new File(data);
if (!file.exists() || !file.isFile()) {
throw new ValueTypeCreationException("File doesn't exist (or"
+ " is not a file): " + file.getName());
}
if (maxSize > 0 && file.length() > maxSize) {
throw new ValueTypeCreationException("Invalid file size; "
+ file.getName() + " exceeds maximum size " + maxSizeText);
}
return file;
}
All that's needed to do now is to complete the remaining method implementations.
First, the name of the type as recognised by the type value in the
configuration will be file-upload:
@Override
public String getValueTypeName() {
return "file-upload";
}
Finally for the implementation, the package name and class name will be those of
the java.io.File class and the primitive name will be null:
@Override
public String getPackageName() {
return File.class.getPackageName();
}
@Override
public String getJavaClassName() {
return File.class.getSimpleName();
}
@Override
public String getJavaPrimitiveName() {
return null;
}
All that's required now is to add the value type to the API using the following calls. The calls must be made at application start, prior to making any calls to the CLC API:
ValueTypeBuilder<FileUploadType> genericFileUploadType
= new ValueTypeBuilder<>(FileUploadType.class);
ValueTypeFactory.getInstance().registerValueTypeBuilder(
"file-upload", genericFileSizeType);
That's it. If an option configuration is added to a CLC file, for example:
option.file-upload.opts = u/upload-ile
option.file-upload.hasArg = true
option.file-upload.argName = file
option.file-upload.description = Files to upload.
option.file-upload.type = file-upload
option.file-upload.properties = maxSize = 150MB
When the CLC file with this entry is parsed, any file passed to the API using
the -u/--upload-file option will be checked by the configuration. For
example if the file is specified by the argument:
--upload-file /imports/2026-04-05-100113.avi
… And the file size is greater than 150MB, the following error will be produced:
Invalid file size; 2026-04-05-100113.avi exceeds maximum size 150MB
Likewise, the type can be assigned to an argument configuration in very much the
same way. Consider the following argument configuration that accepts any number
of files (minimum 1 since optional isn't defined as true), each that will be
checked for their size:
args.file-upload.argName = files...
args.file-upload.description = Files to upload.
args.file-upload.type = file-upload
args.file-upload.properties = maxSize = 150MB
Note that the above configuration would have to be the last argument configuration in order for it to take advantage of being able to be defined for any number of arguments.
It is possible tp replace existing value types.
All that is required is to create a new implementation (outlined above) of the same name and then remove the existing value type from the value type factory, then add the new implementation.
For example, consider a new implementation of the int value type with class
name NewImprovedInt; to remove it call:
ValueTypeFactory.getInstance().removeRegisteredValueType(IntegralType.INTEGRAL);
This is functionally the same as calling
ValueTypeFactory.getInstance().removeRegisteredValueType("int");
Next, add the new implementation:
ValueTypeBuilder<NewImprovedInt> newImprovedInt
= new ValueTypeBuilder<>(NewImprovedInt.class);
ValueTypeFactory.getInstance().registerValueTypeBuilder("int", newImprovedInt);
All CLC options that are defined as ints will now be constructed using the
NewImprovedInt class.