-
Notifications
You must be signed in to change notification settings - Fork 23
/
Parameter.java
551 lines (512 loc) · 18.5 KB
/
Parameter.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
package uk.ac.starlink.task;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.util.Collection;
/**
* A Parameter describes the function of one of a task's parameters.
* An instance of the class encapsulates a parameter's name, value type,
* prompt string, default value, position on the command line,
* and so on. It can also validate, hold and clear the value of the
* parameter. The parameter value is acquired from an associated
* Environment in either String or typed form, and the Parameter
* object configures its value from that.
* <p>
* This class must be subclassed according to the type of values it obtains.
* Such concrete subclasses must implement the {@link #stringToObject}
* method to perform mapping from an environment-supplied string value
* to a typed parameter value, including validation.
*
* @author Mark Taylor (Starlink)
* @see Task
* @see Environment
*/
public abstract class Parameter<T> {
private final Class<T> clazz_;
private boolean allowClassnameValue_;
private String name_;
private String prompt_;
private String description_;
private String usage_ = "<value>";
private String stringDflt_;
private int pos_;
private boolean nullPermitted_;
private boolean preferExplicit_;
private String stringValue_;
private T objectValue_;
private boolean gotValue_;
/**
* Constructs a parameter with a given name. This name should be unique
* within a given Task.
*
* <p>If the <code>allowClassnameValue</code> flag is set,
* then in addition to the options provided by
* {@link #stringToObject stringToObject}, any supplied string value
* equal to the classname of an available class matching
* this parameter's value type which has a no-arg constructor
* will cause a newly constructed instance of that class to be
* used as a value.
*
* @param name the identifying name of this parameter
* @param clazz the class of this parameter's typed values
* @param allowClassnameValue whether suitable classnames may be
* supplied as string values
*/
public Parameter( String name, Class<T> clazz,
boolean allowClassnameValue ) {
name_ = name;
clazz_ = clazz;
allowClassnameValue_ = allowClassnameValue;
}
/**
* Takes a non-blank string, as supplied by the execution environment,
* and turns it into a typed value for this parameter.
* This method also performs validation, so if the string value
* is unacceptable in any way, a ParameterValueException should
* be thrown.
*
* <p>It is an error to supply a null or empty string value.
*
* <p>If this method fails (throws a ParameterValueException)
* and if <code>allowClassnameValue</code> is set, then a subsequent
* attempt will be made to interpret the <code>stringVal</code>
* as the classname of a suitable class with a no-arg constructor.
*
* @param env execution environment; in most cases this is not required
* but for some purposes environment-specific characteristics
* may influence the result
* @param stringVal non-null, non-empty string value
* @return typed value
*/
public abstract T stringToObject( Environment env, String stringVal )
throws TaskException;
/**
* Takes a typed value of this parameter and formats it as a string
* which may be used for presentation to the user.
* Ideally, round-tripping between this method and
* {@link #stringToObject stringToObject} should be possible,
* but that is not in general required/guaranteed.
*
* <p>The default implementation uses the value's toString method,
* but subclasses can override this for smarter behaviour.
*
* @param env execution environment
* @param objectVal typed parameter value
* @return string value for presentation
*/
public String objectToString( Environment env, T objectVal )
throws TaskException {
return objectVal == null ? null : objectVal.toString();
}
/**
* Sets the value of this parameter from a String. This method is
* called by the Environment to configure the value of this parameter.
*
* <p>A null or empty string is intercepted by this method and translated
* to a null object value or triggers a ParameterValueException
* according to the value of isNullPermitted().
*
* @param env execution environment; in most cases this is not required
* but for some purposes environment-specific characteristics
* may influence the result
* @param stringval string representation of value
*/
public void setValueFromString( Environment env, String stringval )
throws TaskException {
final String sval;
final T oval;
/* Null or empty string: convert to a null value. */
if ( stringval == null || stringval.trim().length() == 0 ) {
if ( isNullPermitted() ) {
sval = null;
oval = null;
}
else {
throw new ParameterValueException( this, "Null value "
+ "not permitted" );
}
}
/* Otherwise, use this parameter's custom string-to-object
* conversion. */
else {
sval = stringval;
T ov;
try {
ov = stringToObject( env, stringval );
}
/* If that fails, maybe try interpreting the string
* as a classname. */
catch ( TaskException e ) {
if ( allowClassnameValue_ ) {
T instance = attemptGetClassInstance( stringval );
if ( instance != null ) {
ov = instance;
}
else {
throw e;
}
}
else {
throw e;
}
}
oval = ov;
}
setValue( sval, oval );
}
/**
* Sets the value of this parameter directly from a typed object.
* In this case the reported string value is obtained by calling
* {@link #objectToString objectToString}.
*
* @param env execution environment; in most cases this is not required
* but for some purposes environment-specific characteristics
* may influence the result
* @param objectValue typed value
*/
public void setValueFromObject( Environment env, T objectValue )
throws TaskException {
final String stringValue;
if ( objectValue == null ) {
if ( isNullPermitted() ) {
stringValue = null;
}
else {
throw new ParameterValueException( this, "Null value "
+ "not permitted" );
}
}
else {
stringValue = objectToString( env, objectValue );
}
setValue( stringValue, objectValue );
}
/**
* Gets the value of this parameter as a String.
* The value is lazily acquired by the supplied environment object.
*
* <p>The returned value may be <tt>null</tt>
* only if the {@link #isNullPermitted} method returns true.
*
* @param env execution environment from which value is obtained
* @return the value of this parameter as a string, or <tt>null</tt>
* @throws AbortException if during the course of trying to obtain
* a value the Environment determines that the task should
* not continue.
*/
public final String stringValue( Environment env ) throws TaskException {
checkGotValue( env );
return stringValue_;
}
/**
* Gets the value of this parameter as a typed object.
* The value is actually acquired by the supplied environment object.
*
* <p>The returned value will generally be null if the string value
* that generated it is null or empty. That is only possible
* if the {@link #isNullPermitted} method returns true.
*
* @param env execution environment from which value is obtained
* @return the value of this parameter as a string, or <tt>null</tt>
* @throws AbortException if during the course of trying to obtain
* a value the Environment determines that the task should
* not continue.
*/
public final T objectValue( Environment env ) throws TaskException {
checkGotValue( env );
return objectValue_;
}
/**
* Clears the value of this parameter. Subsequent retrievals of the
* parameter value will trigger a request to the environment for a new
* value.
*
* @param env execution environment within which value will be cleared
*/
public void clearValue( Environment env ) {
env.clearValue( this );
stringValue_ = null;
objectValue_ = null;
gotValue_ = false;
}
/**
* Sets the value of this parameter without any additional validation.
* This is invoked by setValueFromString and setValueFromObject,
* and should not normally be invoked directly.
*
* @param stringValue string representation of value
* @param objectValue typed value
*/
protected void setValue( String stringValue, T objectValue ) {
stringValue_ = stringValue;
objectValue_ = objectValue;
gotValue_ = true;
}
/**
* Returns the class of the typed values this parameter takes.
*
* @return the class of this parameter's typed values
*/
public Class<T> getValueClass() {
return clazz_;
}
/**
* Returns the name of this parameter.
*
* @return name the identifying name of this parameter
*/
public String getName() {
return name_;
}
/**
* Sets the name of this parameter.
*
* @param name identifying name of this parameter
*/
public void setName( String name ) {
name_ = name;
}
/**
* Gets the prompt string for this parameter, as displayed to the
* user when the value of the parameter is requested.
* Should be short (<40 characters?).
*
* @return prompt string
*/
public String getPrompt() {
return prompt_;
}
/**
* Sets the prompt string for this parameter, as displayed to the
* user when the value of the parameter is requested.
* Should be short (<40 characters?).
*
* @param prompt the prompt string
*/
public void setPrompt( String prompt ) {
prompt_ = prompt;
}
/**
* Returns the textual description for this parameter.
*
* @return description, if known
*/
public String getDescription() {
return description_;
}
/**
* Sets the textual description for this parameter.
*
* @param description description
*/
public void setDescription( String description ) {
description_ = description;
}
/**
* Convenience method to set the description for this parameter by
* the result of joining an array of lines together.
*
* @param descLines lines of textual description
* @see #setDescription(java.lang.String)
*/
public void setDescription( String[] descLines ) {
StringBuffer sbuf = new StringBuffer();
for ( int i = 0; i < descLines.length; i++ ) {
sbuf.append( descLines[ i ] )
.append( '\n' );
}
setDescription( sbuf.toString() );
}
/**
* Sets a usage string for this parameter. This should be terse
* (in particular no newline characters) and conform to the
* following rules:
* <ul>
* <li>the parameter name is not included in the message
* <li>placeholders are enclosed in angle brackets (<>)
* <li>literals are not enclosed in angle brackets
* <li>a disjunction is represented using the "|" character
* </ul>
* The <code>Parameter</code> class uses the string "<value>"
* as the default usage string.
*
* @param usage usage string
*/
public void setUsage( String usage ) {
usage_ = usage;
}
/**
* Returns the usage string for this parameter.
*
* @return usage string
* @see #setUsage
*/
public String getUsage() {
return usage_;
}
/**
* Set whether it is legal for this parameter's value to be blank.
* By default it is not.
* Note that null and blank string values are treated the same as each
* other, and are translated to null object values.
*
* @param permitted whether null values are to be permitted for this
* parameter
*/
public void setNullPermitted( boolean permitted ) {
nullPermitted_ = permitted;
}
/**
* Determine whether it is legal for this parameter's value to be blank.
* By default it is not.
*
* @return true if null values are permitted for this parameter
*/
public boolean isNullPermitted() {
return nullPermitted_;
}
/**
* Determine whether an explict value is generally preferred to the
* default value for this parameter.
*
* @return true if a default value should generally be avoided
*/
public boolean getPreferExplicit() {
return preferExplicit_;
}
/**
* Set whether an explicit value is generally to be solicited from
* the user rather than taking the default.
*
* @param prefer true if you would like to encourage an explicit
* value to be set for this parameter
*/
public void setPreferExplicit( boolean prefer ) {
preferExplicit_ = prefer;
}
/**
* Gets the default string value for this parameter.
*
* @return the default string value
*/
public String getStringDefault() {
return stringDflt_;
}
/**
* Sets the default string value for this parameter.
* Concrete subclasses may additionally supply type-specific
* default value setter methods, but those ought to operate by invoking
* this method.
*
* @param stringDflt the default string value
*/
public void setStringDefault( String stringDflt ) {
stringDflt_ = stringDflt;
}
/**
* Gets the position of this parameter in a parameter list;
* the first parameter is 1. If the position is 0, the value can only
* be set by name.
*
* @return parameter position
*/
public int getPosition() {
return pos_;
}
/**
* Sets the position of this parameter in a parameter list;
* the first parameter is 1. If the position is 0, the value can only
* be set by name.
*
* @param pos parameter position
*/
public void setPosition( int pos ) {
pos_ = pos;
}
/**
* Utility function to convert a list to an array, where the elements
* are of the value class of this parameter.
*
* @param collection typed collection
* @return typed array with same contents
*/
public T[] toArray( Collection<T> collection ) {
@SuppressWarnings("unchecked")
T[] array = (T[]) Array.newInstance( clazz_, collection.size() );
return collection.toArray( array );
}
/**
* Returns the name of this parameter.
*
* @return string representation
*/
@Override
public String toString() {
return name_;
}
/**
* Ensure that this object is configured with a valid value from the
* environment. If not, the environment is queried so that we are.
*
* @param env execution environment which supplies value
*/
private void checkGotValue( Environment env ) throws TaskException {
if ( ! gotValue_ ) {
env.acquireValue( this );
assert gotValue_ : "Environment did not call setValue";
}
}
/**
* Attempts to interpret a string as a classname to turn it into
* a typed object. The class must exist, must extend this parameter's
* value type, and must have a public no-arg constructor for this to work.
* In most cases, failure just results in a null return, but if it
* looks like a genuine attempt has been made to supply a classname,
* a ParameterValueException with a helpful message is thrown.
*
* @param cname string which might just be the name of a suitable
* class with a no-arg constructor
* @return typed value or null
*/
private T attemptGetClassInstance( String cname )
throws ParameterValueException {
/* See if we have a classname. Most likely the string value is not
* intended to represent a class instance at all, so if it's not
* a class just return null with no error. */
final Class<?> vclazz;
try {
vclazz = Class.forName( cname );
}
catch ( ClassNotFoundException e ) {
return null;
}
/* We have a classname, so it's a fair bet that it is intended to
* name a class whose instance should be the value of this parameter.
* Anything that goes wrong from here generates an exception. */
final Class<T> clazz = getValueClass();
if ( clazz.isAssignableFrom( vclazz ) ) {
Class<? extends T> vtclazz = vclazz.asSubclass( clazz );
Constructor<? extends T> constructor;
try {
constructor = vtclazz.getConstructor( new Class[ 0 ] );
}
catch ( NoSuchMethodException e ) {
throw new ParameterValueException( this,
"No no-arg constructor for "
+ vtclazz );
}
try {
return constructor.newInstance( new Object[ 0 ] );
}
catch ( Throwable e ) {
throw new ParameterValueException( this,
"Error constructing "
+ vtclazz, e );
}
}
else {
throw new ParameterValueException( this,
vclazz + " is not of type "
+ clazz );
}
}
}