0
|
1 /*
|
|
2 * Copyright 2002-2004 Sun Microsystems, Inc. All Rights Reserved.
|
|
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
|
4 *
|
|
5 * This code is free software; you can redistribute it and/or modify it
|
|
6 * under the terms of the GNU General Public License version 2 only, as
|
|
7 * published by the Free Software Foundation.
|
|
8 *
|
|
9 * This code is distributed in the hope that it will be useful, but WITHOUT
|
|
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
|
12 * version 2 for more details (a copy is included in the LICENSE file that
|
|
13 * accompanied this code).
|
|
14 *
|
|
15 * You should have received a copy of the GNU General Public License version
|
|
16 * 2 along with this work; if not, write to the Free Software Foundation,
|
|
17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
|
|
18 *
|
|
19 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
|
|
20 * CA 95054 USA or visit www.sun.com if you need additional information or
|
|
21 * have any questions.
|
|
22 *
|
|
23 */
|
|
24
|
|
25 package sun.jvm.hotspot.jdi;
|
|
26
|
|
27 import com.sun.jdi.connect.*;
|
|
28 import com.sun.jdi.InternalException;
|
|
29
|
|
30 import java.io.*;
|
|
31 import java.lang.ref.*;
|
|
32 import java.lang.reflect.*;
|
|
33 import java.util.*;
|
|
34
|
|
35 abstract class ConnectorImpl implements Connector {
|
|
36 Map defaultArguments = new LinkedHashMap();
|
|
37
|
|
38 // Used by BooleanArgument
|
|
39 static String trueString = null;
|
|
40 static String falseString;
|
|
41
|
|
42
|
|
43 /** This is not public in VirtualMachineManagerImpl
|
|
44 ThreadGroup mainGroupForJDI() {
|
|
45 return ((VirtualMachineManagerImpl)manager).mainGroupForJDI();
|
|
46 }
|
|
47 ***/
|
|
48
|
|
49 // multiple debuggee support for SA/JDI
|
|
50 private static List freeVMClasses; // List<SoftReference<Class>>
|
|
51 private static ClassLoader myLoader;
|
|
52 // debug mode for SA/JDI connectors
|
|
53 static final protected boolean DEBUG;
|
|
54 static {
|
|
55 myLoader = ConnectorImpl.class.getClassLoader();
|
|
56 freeVMClasses = new ArrayList(0);
|
|
57 DEBUG = System.getProperty("sun.jvm.hotspot.jdi.ConnectorImpl.DEBUG") != null;
|
|
58 }
|
|
59
|
|
60 // add a new free VirtualMachineImpl class
|
|
61 private static synchronized void addFreeVMImplClass(Class clazz) {
|
|
62 if (DEBUG) {
|
|
63 System.out.println("adding free VirtualMachineImpl class");
|
|
64 }
|
|
65 freeVMClasses.add(new SoftReference(clazz));
|
|
66 }
|
|
67
|
|
68 // returns null if we don't have anything free
|
|
69 private static synchronized Class getFreeVMImplClass() {
|
|
70 while (!freeVMClasses.isEmpty()) {
|
|
71 SoftReference ref = (SoftReference) freeVMClasses.remove(0);
|
|
72 Object o = ref.get();
|
|
73 if (o != null) {
|
|
74 if (DEBUG) {
|
|
75 System.out.println("re-using loaded VirtualMachineImpl");
|
|
76 }
|
|
77 return (Class) o;
|
|
78 }
|
|
79 }
|
|
80 return null;
|
|
81 }
|
|
82
|
|
83 private static Class getVMImplClassFrom(ClassLoader cl)
|
|
84 throws ClassNotFoundException {
|
|
85 return Class.forName("sun.jvm.hotspot.jdi.VirtualMachineImpl", true, cl);
|
|
86 }
|
|
87
|
|
88 /* SA has not been designed to support multiple debuggee VMs
|
|
89 * at-a-time. But, JDI supports multiple debuggee VMs. We
|
|
90 * support multiple debuggee VMs in SA/JDI, by creating a new
|
|
91 * class loader instance (refer to comment in SAJDIClassLoader
|
|
92 * for details). But, to avoid excessive class loading (and
|
|
93 * thereby resulting in larger footprint), we re-use 'dispose'd
|
|
94 * VirtualMachineImpl classes.
|
|
95 */
|
|
96 protected static Class loadVirtualMachineImplClass()
|
|
97 throws ClassNotFoundException {
|
|
98 Class vmImplClass = getFreeVMImplClass();
|
|
99 if (vmImplClass == null) {
|
|
100 ClassLoader cl = new SAJDIClassLoader(myLoader);
|
|
101 vmImplClass = getVMImplClassFrom(cl);
|
|
102 }
|
|
103 return vmImplClass;
|
|
104 }
|
|
105
|
|
106 /* We look for System property sun.jvm.hotspot.jdi.<vm version>.
|
|
107 * This property should have the value of JDK HOME directory for
|
|
108 * the given <vm version>.
|
|
109 */
|
|
110 private static String getSAClassPathForVM(String vmVersion) {
|
|
111 final String prefix = "sun.jvm.hotspot.jdi.";
|
|
112 // look for exact match of VM version
|
|
113 String jvmHome = System.getProperty(prefix + vmVersion);
|
|
114 if (DEBUG) {
|
|
115 System.out.println("looking for System property " + prefix + vmVersion);
|
|
116 }
|
|
117
|
|
118 if (jvmHome == null) {
|
|
119 // omit chars after first '-' in VM version and try
|
|
120 // for example, in '1.5.0-b55' we take '1.5.0'
|
|
121 int index = vmVersion.indexOf('-');
|
|
122 if (index != -1) {
|
|
123 vmVersion = vmVersion.substring(0, index);
|
|
124 if (DEBUG) {
|
|
125 System.out.println("looking for System property " + prefix + vmVersion);
|
|
126 }
|
|
127 jvmHome = System.getProperty(prefix + vmVersion);
|
|
128 }
|
|
129
|
|
130 if (jvmHome == null) {
|
|
131 // System property is not set
|
|
132 if (DEBUG) {
|
|
133 System.out.println("can't locate JDK home for " + vmVersion);
|
|
134 }
|
|
135 return null;
|
|
136 }
|
|
137 }
|
|
138
|
|
139 if (DEBUG) {
|
|
140 System.out.println("JDK home for " + vmVersion + " is " + jvmHome);
|
|
141 }
|
|
142
|
|
143 // sa-jdi is in $JDK_HOME/lib directory
|
|
144 StringBuffer buf = new StringBuffer();
|
|
145 buf.append(jvmHome);
|
|
146 buf.append(File.separatorChar);
|
|
147 buf.append("lib");
|
|
148 buf.append(File.separatorChar);
|
|
149 buf.append("sa-jdi.jar");
|
|
150 return buf.toString();
|
|
151 }
|
|
152
|
|
153 /* This method loads VirtualMachineImpl class by a ClassLoader
|
|
154 * configured with sa-jdi.jar path of given 'vmVersion'. This is
|
|
155 * used for cross VM version debugging. Refer to comments in
|
|
156 * SAJDIClassLoader as well.
|
|
157 */
|
|
158 protected static Class loadVirtualMachineImplClass(String vmVersion)
|
|
159 throws ClassNotFoundException {
|
|
160 if (DEBUG) {
|
|
161 System.out.println("attemping to load sa-jdi.jar for version " + vmVersion);
|
|
162 }
|
|
163 String classPath = getSAClassPathForVM(vmVersion);
|
|
164 if (classPath != null) {
|
|
165 ClassLoader cl = new SAJDIClassLoader(myLoader, classPath);
|
|
166 return getVMImplClassFrom(cl);
|
|
167 } else {
|
|
168 return null;
|
|
169 }
|
|
170 }
|
|
171
|
|
172 /* Is the given throwable an instanceof VMVersionMismatchException?
|
|
173 * Note that we can't do instanceof check because the exception
|
|
174 * class might have been loaded by a different class loader.
|
|
175 */
|
|
176 private static boolean isVMVersionMismatch(Throwable throwable) {
|
|
177 String className = throwable.getClass().getName();
|
|
178 return className.equals("sun.jvm.hotspot.runtime.VMVersionMismatchException");
|
|
179 }
|
|
180
|
|
181 /* gets target VM version from the given VMVersionMismatchException.
|
|
182 * Note that we need to reflectively call the method because of we may
|
|
183 * have got this from different classloader's namespace */
|
|
184 private static String getVMVersion(Throwable throwable)
|
|
185 throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
|
|
186 // assert isVMVersionMismatch(throwable), "not a VMVersionMismatch"
|
|
187 Class expClass = throwable.getClass();
|
|
188 Method targetVersionMethod = expClass.getMethod("getTargetVersion", new Class[0]);
|
|
189 return (String) targetVersionMethod.invoke(throwable, null);
|
|
190 }
|
|
191
|
|
192 /** If the causal chain has a sun.jvm.hotspot.runtime.VMVersionMismatchException,
|
|
193 attempt to load VirtualMachineImpl class for target VM version. */
|
|
194 protected static Class handleVMVersionMismatch(InvocationTargetException ite) {
|
|
195 Throwable cause = ite.getCause();
|
|
196 if (DEBUG) {
|
|
197 System.out.println("checking for version mismatch...");
|
|
198 }
|
|
199 while (cause != null) {
|
|
200 try {
|
|
201 if (isVMVersionMismatch(cause)) {
|
|
202 if (DEBUG) {
|
|
203 System.out.println("Triggering cross VM version support...");
|
|
204 }
|
|
205 return loadVirtualMachineImplClass(getVMVersion(cause));
|
|
206 }
|
|
207 } catch (Exception exp) {
|
|
208 if (DEBUG) {
|
|
209 System.out.println("failed to load VirtualMachineImpl class");
|
|
210 exp.printStackTrace();
|
|
211 }
|
|
212 return null;
|
|
213 }
|
|
214 cause = cause.getCause();
|
|
215 }
|
|
216 return null;
|
|
217 }
|
|
218
|
|
219 protected void checkNativeLink(SecurityManager sm, String os) {
|
|
220 if (os.equals("SunOS") || os.equals("Linux")) {
|
|
221 // link "saproc" - SA native library on SunOS and Linux?
|
|
222 sm.checkLink("saproc");
|
|
223 } else if (os.startsWith("Windows")) {
|
|
224 // link "sawindbg" - SA native library on Windows.
|
|
225 sm.checkLink("sawindbg");
|
|
226 } else {
|
|
227 throw new RuntimeException(os + " is not yet supported");
|
|
228 }
|
|
229 }
|
|
230
|
|
231 // we set an observer to detect VirtualMachineImpl.dispose call
|
|
232 // and on dispose we add corresponding VirtualMachineImpl.class to
|
|
233 // free VirtualMachimeImpl Class list.
|
|
234 protected static void setVMDisposeObserver(final Object vm) {
|
|
235 try {
|
|
236 Method setDisposeObserverMethod = vm.getClass().getDeclaredMethod("setDisposeObserver",
|
|
237 new Class[] { java.util.Observer.class });
|
|
238 setDisposeObserverMethod.setAccessible(true);
|
|
239 setDisposeObserverMethod.invoke(vm,
|
|
240 new Object[] {
|
|
241 new Observer() {
|
|
242 public void update(Observable o, Object data) {
|
|
243 if (DEBUG) {
|
|
244 System.out.println("got VM.dispose notification");
|
|
245 }
|
|
246 addFreeVMImplClass(vm.getClass());
|
|
247 }
|
|
248 }
|
|
249 });
|
|
250 } catch (Exception exp) {
|
|
251 if (DEBUG) {
|
|
252 System.out.println("setVMDisposeObserver() got an exception:");
|
|
253 exp.printStackTrace();
|
|
254 }
|
|
255 }
|
|
256 }
|
|
257
|
|
258 public Map defaultArguments() {
|
|
259 Map defaults = new LinkedHashMap();
|
|
260 Collection values = defaultArguments.values();
|
|
261
|
|
262 Iterator iter = values.iterator();
|
|
263 while (iter.hasNext()) {
|
|
264 ArgumentImpl argument = (ArgumentImpl)iter.next();
|
|
265 defaults.put(argument.name(), argument.clone());
|
|
266 }
|
|
267 return defaults;
|
|
268 }
|
|
269
|
|
270 void addStringArgument(String name, String label, String description,
|
|
271 String defaultValue, boolean mustSpecify) {
|
|
272 defaultArguments.put(name,
|
|
273 new StringArgumentImpl(name, label,
|
|
274 description,
|
|
275 defaultValue,
|
|
276 mustSpecify));
|
|
277 }
|
|
278
|
|
279 void addBooleanArgument(String name, String label, String description,
|
|
280 boolean defaultValue, boolean mustSpecify) {
|
|
281 defaultArguments.put(name,
|
|
282 new BooleanArgumentImpl(name, label,
|
|
283 description,
|
|
284 defaultValue,
|
|
285 mustSpecify));
|
|
286 }
|
|
287
|
|
288 void addIntegerArgument(String name, String label, String description,
|
|
289 String defaultValue, boolean mustSpecify,
|
|
290 int min, int max) {
|
|
291 defaultArguments.put(name,
|
|
292 new IntegerArgumentImpl(name, label,
|
|
293 description,
|
|
294 defaultValue,
|
|
295 mustSpecify,
|
|
296 min, max));
|
|
297 }
|
|
298
|
|
299 void addSelectedArgument(String name, String label, String description,
|
|
300 String defaultValue, boolean mustSpecify,
|
|
301 List list) {
|
|
302 defaultArguments.put(name,
|
|
303 new SelectedArgumentImpl(name, label,
|
|
304 description,
|
|
305 defaultValue,
|
|
306 mustSpecify, list));
|
|
307 }
|
|
308
|
|
309 ArgumentImpl argument(String name, Map arguments)
|
|
310 throws IllegalConnectorArgumentsException {
|
|
311
|
|
312 ArgumentImpl argument = (ArgumentImpl)arguments.get(name);
|
|
313 if (argument == null) {
|
|
314 throw new IllegalConnectorArgumentsException(
|
|
315 "Argument missing", name);
|
|
316 }
|
|
317 String value = argument.value();
|
|
318 if (value == null || value.length() == 0) {
|
|
319 if (argument.mustSpecify()) {
|
|
320 throw new IllegalConnectorArgumentsException(
|
|
321 "Argument unspecified", name);
|
|
322 }
|
|
323 } else if(!argument.isValid(value)) {
|
|
324 throw new IllegalConnectorArgumentsException(
|
|
325 "Argument invalid", name);
|
|
326 }
|
|
327
|
|
328 return argument;
|
|
329 }
|
|
330
|
|
331 String getString(String key) {
|
|
332 //fixme jjh; needs i18n
|
|
333 // this is not public return ((VirtualMachineManagerImpl)manager).getString(key);
|
|
334 return key;
|
|
335 }
|
|
336
|
|
337 public String toString() {
|
|
338 String string = name() + " (defaults: ";
|
|
339 Iterator iter = defaultArguments().values().iterator();
|
|
340 boolean first = true;
|
|
341 while (iter.hasNext()) {
|
|
342 ArgumentImpl argument = (ArgumentImpl)iter.next();
|
|
343 if (!first) {
|
|
344 string += ", ";
|
|
345 }
|
|
346 string += argument.toString();
|
|
347 first = false;
|
|
348 }
|
|
349 return string + ")";
|
|
350 }
|
|
351
|
|
352 abstract class ArgumentImpl implements Connector.Argument, Cloneable, Serializable {
|
|
353 private String name;
|
|
354 private String label;
|
|
355 private String description;
|
|
356 private String value;
|
|
357 private boolean mustSpecify;
|
|
358
|
|
359 ArgumentImpl(String name, String label, String description,
|
|
360 String value,
|
|
361 boolean mustSpecify) {
|
|
362 this.name = name;
|
|
363 this.label = label;
|
|
364 this.description = description;
|
|
365 this.value = value;
|
|
366 this.mustSpecify = mustSpecify;
|
|
367 }
|
|
368
|
|
369 public abstract boolean isValid(String value);
|
|
370
|
|
371 public String name() {
|
|
372 return name;
|
|
373 }
|
|
374
|
|
375 public String label() {
|
|
376 return label;
|
|
377 }
|
|
378
|
|
379 public String description() {
|
|
380 return description;
|
|
381 }
|
|
382
|
|
383 public String value() {
|
|
384 return value;
|
|
385 }
|
|
386
|
|
387 public void setValue(String value) {
|
|
388 if (value == null) {
|
|
389 throw new NullPointerException("Can't set null value");
|
|
390 }
|
|
391 this.value = value;
|
|
392 }
|
|
393
|
|
394 public boolean mustSpecify() {
|
|
395 return mustSpecify;
|
|
396 }
|
|
397
|
|
398 public boolean equals(Object obj) {
|
|
399 if ((obj != null) && (obj instanceof Connector.Argument)) {
|
|
400 Connector.Argument other = (Connector.Argument)obj;
|
|
401 return (name().equals(other.name())) &&
|
|
402 (description().equals(other.description())) &&
|
|
403 (mustSpecify() == other.mustSpecify()) &&
|
|
404 (value().equals(other.value()));
|
|
405 } else {
|
|
406 return false;
|
|
407 }
|
|
408 }
|
|
409
|
|
410 public int hashCode() {
|
|
411 return description().hashCode();
|
|
412 }
|
|
413
|
|
414 public Object clone() {
|
|
415 try {
|
|
416 return super.clone();
|
|
417 } catch (CloneNotSupportedException e) {
|
|
418 // Object should always support clone
|
|
419 throw (InternalException) new InternalException().initCause(e);
|
|
420 }
|
|
421 }
|
|
422
|
|
423 public String toString() {
|
|
424 return name() + "=" + value();
|
|
425 }
|
|
426 }
|
|
427
|
|
428 class BooleanArgumentImpl extends ConnectorImpl.ArgumentImpl
|
|
429 implements Connector.BooleanArgument {
|
|
430
|
|
431 BooleanArgumentImpl(String name, String label, String description,
|
|
432 boolean value,
|
|
433 boolean mustSpecify) {
|
|
434 super(name, label, description, null, mustSpecify);
|
|
435 if(trueString == null) {
|
|
436 trueString = getString("true");
|
|
437 falseString = getString("false");
|
|
438 }
|
|
439 setValue(value);
|
|
440 }
|
|
441
|
|
442 /**
|
|
443 * Sets the value of the argument.
|
|
444 */
|
|
445 public void setValue(boolean value) {
|
|
446 setValue(stringValueOf(value));
|
|
447 }
|
|
448
|
|
449 /**
|
|
450 * Performs basic sanity check of argument.
|
|
451 * @return <code>true</code> if value is a string
|
|
452 * representation of a boolean value.
|
|
453 * @see #stringValueOf(boolean)
|
|
454 */
|
|
455 public boolean isValid(String value) {
|
|
456 return value.equals(trueString) || value.equals(falseString);
|
|
457 }
|
|
458
|
|
459 /**
|
|
460 * Return the string representation of the <code>value</code>
|
|
461 * parameter.
|
|
462 * Does not set or examine the value or the argument.
|
|
463 * @return the localized String representation of the
|
|
464 * boolean value.
|
|
465 */
|
|
466 public String stringValueOf(boolean value) {
|
|
467 return value? trueString : falseString;
|
|
468 }
|
|
469
|
|
470 /**
|
|
471 * Return the value of the argument as a boolean. Since
|
|
472 * the argument may not have been set or may have an invalid
|
|
473 * value {@link #isValid(String)} should be called on
|
|
474 * {@link #value()} to check its validity. If it is invalid
|
|
475 * the boolean returned by this method is undefined.
|
|
476 * @return the value of the argument as a boolean.
|
|
477 */
|
|
478 public boolean booleanValue() {
|
|
479 return value().equals(trueString);
|
|
480 }
|
|
481 }
|
|
482
|
|
483 class IntegerArgumentImpl extends ConnectorImpl.ArgumentImpl
|
|
484 implements Connector.IntegerArgument {
|
|
485
|
|
486 private final int min;
|
|
487 private final int max;
|
|
488
|
|
489 IntegerArgumentImpl(String name, String label, String description,
|
|
490 String value,
|
|
491 boolean mustSpecify, int min, int max) {
|
|
492 super(name, label, description, value, mustSpecify);
|
|
493 this.min = min;
|
|
494 this.max = max;
|
|
495 }
|
|
496
|
|
497 /**
|
|
498 * Sets the value of the argument.
|
|
499 * The value should be checked with {@link #isValid(int)}
|
|
500 * before setting it; invalid values will throw an exception
|
|
501 * when the connection is established - for example,
|
|
502 * on {@link LaunchingConnector#launch}
|
|
503 */
|
|
504 public void setValue(int value) {
|
|
505 setValue(stringValueOf(value));
|
|
506 }
|
|
507
|
|
508 /**
|
|
509 * Performs basic sanity check of argument.
|
|
510 * @return <code>true</code> if value represents an int that is
|
|
511 * <code>{@link #min()} <= value <= {@link #max()}</code>
|
|
512 */
|
|
513 public boolean isValid(String value) {
|
|
514 if (value == null) {
|
|
515 return false;
|
|
516 }
|
|
517 try {
|
|
518 return isValid(Integer.decode(value).intValue());
|
|
519 } catch(NumberFormatException exc) {
|
|
520 return false;
|
|
521 }
|
|
522 }
|
|
523
|
|
524 /**
|
|
525 * Performs basic sanity check of argument.
|
|
526 * @return <code>true</code> if
|
|
527 * <code>{@link #min()} <= value <= {@link #max()}</code>
|
|
528 */
|
|
529 public boolean isValid(int value) {
|
|
530 return min <= value && value <= max;
|
|
531 }
|
|
532
|
|
533 /**
|
|
534 * Return the string representation of the <code>value</code>
|
|
535 * parameter.
|
|
536 * Does not set or examine the value or the argument.
|
|
537 * @return the String representation of the
|
|
538 * int value.
|
|
539 */
|
|
540 public String stringValueOf(int value) {
|
|
541 // *** Should this be internationalized????
|
|
542 // *** Even Brian Beck was unsure if an Arabic programmer
|
|
543 // *** would expect port numbers in Arabic numerals,
|
|
544 // *** so punt for now.
|
|
545 return ""+value;
|
|
546 }
|
|
547
|
|
548 /**
|
|
549 * Return the value of the argument as a int. Since
|
|
550 * the argument may not have been set or may have an invalid
|
|
551 * value {@link #isValid(String)} should be called on
|
|
552 * {@link #value()} to check its validity. If it is invalid
|
|
553 * the int returned by this method is undefined.
|
|
554 * @return the value of the argument as a int.
|
|
555 */
|
|
556 public int intValue() {
|
|
557 if (value() == null) {
|
|
558 return 0;
|
|
559 }
|
|
560 try {
|
|
561 return Integer.decode(value()).intValue();
|
|
562 } catch(NumberFormatException exc) {
|
|
563 return 0;
|
|
564 }
|
|
565 }
|
|
566
|
|
567 /**
|
|
568 * The upper bound for the value.
|
|
569 * @return the maximum allowed value for this argument.
|
|
570 */
|
|
571 public int max() {
|
|
572 return max;
|
|
573 }
|
|
574
|
|
575 /**
|
|
576 * The lower bound for the value.
|
|
577 * @return the minimum allowed value for this argument.
|
|
578 */
|
|
579 public int min() {
|
|
580 return min;
|
|
581 }
|
|
582 }
|
|
583
|
|
584 class StringArgumentImpl extends ConnectorImpl.ArgumentImpl
|
|
585 implements Connector.StringArgument {
|
|
586
|
|
587 StringArgumentImpl(String name, String label, String description,
|
|
588 String value,
|
|
589 boolean mustSpecify) {
|
|
590 super(name, label, description, value, mustSpecify);
|
|
591 }
|
|
592
|
|
593 /**
|
|
594 * Performs basic sanity check of argument.
|
|
595 * @return <code>true</code> always
|
|
596 */
|
|
597 public boolean isValid(String value) {
|
|
598 return true;
|
|
599 }
|
|
600 }
|
|
601
|
|
602 class SelectedArgumentImpl extends ConnectorImpl.ArgumentImpl
|
|
603 implements Connector.SelectedArgument {
|
|
604
|
|
605 private final List choices;
|
|
606
|
|
607 SelectedArgumentImpl(String name, String label, String description,
|
|
608 String value,
|
|
609 boolean mustSpecify, List choices) {
|
|
610 super(name, label, description, value, mustSpecify);
|
|
611 this.choices = Collections.unmodifiableList(
|
|
612 new ArrayList(choices));
|
|
613 }
|
|
614
|
|
615 /**
|
|
616 * Return the possible values for the argument
|
|
617 * @return {@link List} of {@link String}
|
|
618 */
|
|
619 public List choices() {
|
|
620 return choices;
|
|
621 }
|
|
622
|
|
623 /**
|
|
624 * Performs basic sanity check of argument.
|
|
625 * @return <code>true</code> if value is one of {@link #choices()}.
|
|
626 */
|
|
627 public boolean isValid(String value) {
|
|
628 return choices.contains(value);
|
|
629 }
|
|
630 }
|
|
631 }
|