JAL-629 fixes to typed args
[jalview.git] / src / jalview / bin / argparser / Arg.java
1 package jalview.bin.argparser;
2
3 import java.util.ArrayList;
4 import java.util.Arrays;
5 import java.util.Collections;
6 import java.util.Comparator;
7 import java.util.EnumSet;
8 import java.util.Iterator;
9 import java.util.List;
10 import java.util.Locale;
11 import java.util.stream.Collectors;
12
13 import jalview.bin.Cache;
14 import jalview.util.ChannelProperties;
15
16 public enum Arg
17 {
18
19   // Initialising arguments (BOOTSTRAP)
20   HELP(Type.HELP, "h", "Display this help statement", Opt.UNARY,
21           Opt.BOOTSTRAP, Opt.HASTYPE, Opt.MULTI),
22   /*
23    * Other --help-type Args will be added by the static block.
24    */
25   VERSION(Type.CONFIG, "v",
26           "Display the version of "
27                   + ChannelProperties.getProperty("app_name"),
28           Opt.UNARY, Opt.BOOTSTRAP),
29   HEADLESS(Type.CONFIG,
30           "Run Jalview in headless mode. No GUI interface will be created and Jalview will quit after all arguments have been processed.",
31           Opt.UNARY, Opt.BOOTSTRAP),
32   JABAWS(Type.CONFIG, "Set a different URL to connect to a JABAWS server.",
33           Opt.STRING, Opt.BOOTSTRAP),
34   NEWS(Type.CONFIG, "Show (or don't show) the news feed.", true,
35           Opt.BOOLEAN, Opt.BOOTSTRAP),
36   SPLASH(Type.CONFIG,
37           "Show (or don't show) the About Jalview splash screen.", true,
38           Opt.BOOLEAN, Opt.BOOTSTRAP),
39   QUESTIONNAIRE(Type.CONFIG,
40           "Show (or don't show) the questionnaire if one is available.",
41           true, Opt.BOOLEAN, Opt.BOOTSTRAP),
42   USAGESTATS(Type.CONFIG,
43           "Send (or don't send) initial launch usage stats.", true,
44           Opt.BOOLEAN, Opt.BOOTSTRAP),
45   WEBSERVICEDISCOVERY(Type.CONFIG,
46           "Attempt (or don't attempt) to connect to JABAWS web services.",
47           true, Opt.BOOLEAN, Opt.BOOTSTRAP),
48   PROPS(Type.CONFIG,
49           "Use a file as the preferences file instead of the usual ~/"
50                   + ChannelProperties.getProperty("preferences.filename")
51                   + " file.",
52           Opt.STRING, Opt.BOOTSTRAP),
53   DEBUG(Type.CONFIG, "d", "Start Jalview in debug log level.", Opt.BOOLEAN,
54           Opt.BOOTSTRAP),
55   TRACE(Type.CONFIG, "Start Jalview in trace log level.", Opt.BOOLEAN,
56           Opt.BOOTSTRAP, Opt.SECRET),
57   QUIET(Type.CONFIG, "q",
58           "Stop all output to STDOUT (after the Java Virtual Machine has started). Use ‑‑quiet a second time to stop all output to STDERR.",
59           Opt.UNARY, Opt.MULTI, Opt.BOOTSTRAP),
60   INITSUBSTITUTIONS(Type.CONFIG,
61           "Set ‑‑substitutions to be initially enabled (or initially disabled).",
62           true, Opt.BOOLEAN, Opt.BOOTSTRAP, Opt.NOACTION, Opt.SECRET),
63
64   // Opening an alignment
65   OPEN(Type.OPENING,
66           "Opens one or more alignment files or URLs in new alignment windows.",
67           Opt.STRING, Opt.LINKED, Opt.INCREMENTDEFAULTCOUNTER, Opt.MULTI,
68           Opt.GLOB, Opt.ALLOWSUBSTITUTIONS, Opt.INPUT, Opt.STORED,
69           Opt.PRIMARY),
70   APPEND(Type.OPENING,
71           "Appends one or more alignment files or URLs to the open alignment window (or opens a new alignment if none already open).",
72           Opt.STRING, Opt.LINKED, Opt.MULTI, Opt.GLOB,
73           Opt.ALLOWSUBSTITUTIONS, Opt.INPUT, Opt.PRIMARY),
74   TITLE(Type.OPENING,
75           "Specifies the title for the open alignment window as string.",
76           Opt.STRING, Opt.LINKED),
77   COLOUR(Type.OPENING, "color", // being a bit soft on the Americans!
78           "Applies the colour scheme to the open alignment window. Valid values are:\n"
79                   + "clustal,\n" + "blosum62,\n" + "pc-identity,\n"
80                   + "zappo,\n" + "taylor,\n" + "gecos-flower,\n"
81                   + "gecos-blossom,\n" + "gecos-sunset,\n"
82                   + "gecos-ocean,\n" + "hydrophobic,\n"
83                   + "helix-propensity,\n" + "strand-propensity,\n"
84                   + "turn-propensity,\n" + "buried-index,\n"
85                   + "nucleotide,\n" + "nucleotide-ambiguity,\n"
86                   + "purine-pyrimidine,\n" + "rna-helices,\n"
87                   + "t-coffee-scores,\n" + "sequence-id.",
88           Opt.STRING, Opt.LINKED, Opt.ALLOWALL),
89   FEATURES(Type.OPENING, "Add a feature file or URL to the open alignment.",
90           Opt.STRING, Opt.LINKED, Opt.MULTI, Opt.ALLOWSUBSTITUTIONS),
91   TREE(Type.OPENING, "Add a tree file or URL to the open alignment.",
92           Opt.STRING, Opt.LINKED, Opt.MULTI, Opt.ALLOWSUBSTITUTIONS),
93   SORTBYTREE(Type.OPENING,
94           "Enforces sorting (or not sorting) the open alignment in the order of an attached phylogenetic tree.",
95           true, Opt.LINKED, Opt.BOOLEAN, Opt.ALLOWALL),
96   ANNOTATIONS(Type.OPENING,
97           "Add an annotations file or URL to the open alignment.",
98           Opt.STRING, Opt.LINKED, Opt.MULTI, Opt.ALLOWSUBSTITUTIONS),
99   SHOWANNOTATIONS(Type.OPENING,
100           "Enforces showing (or not showing) alignment annotations.",
101           Opt.BOOLEAN, Opt.LINKED, Opt.ALLOWALL),
102   WRAP(Type.OPENING,
103           "Enforces wrapped (or not wrapped) alignment formatting.",
104           Opt.BOOLEAN, Opt.LINKED, Opt.ALLOWALL),
105   NOSTRUCTURE(Type.OPENING,
106           "Do not open or process any 3D structure in the ‑‑open or ‑‑append files.",
107           Opt.UNARY, Opt.LINKED, Opt.ALLOWALL),
108
109   // Adding a 3D structure
110   STRUCTURE(Type.STRUCTURE,
111           "Load a structure file or URL associated with a sequence in the open alignment.\n"
112                   + "The sequence to be associated with can be specified with a following --seqid argument, or the subval modifier seqid=ID can be used. A subval INDEX can also be used to specify the INDEX-th sequence in the open alignment.",
113           Opt.STRING, Opt.LINKED, Opt.MULTI, Opt.ALLOWSUBSTITUTIONS,
114           Opt.PRIMARY),
115   SEQID(Type.STRUCTURE,
116           "Specify the sequence name for the preceding --structure to be associated with.",
117           Opt.STRING, Opt.LINKED, Opt.MULTI, Opt.ALLOWSUBSTITUTIONS),
118   PAEMATRIX(Type.STRUCTURE,
119           "Add a PAE json matrix file to the preceding --structure.",
120           Opt.STRING, Opt.LINKED, Opt.MULTI, Opt.ALLOWSUBSTITUTIONS),
121   TEMPFAC(Type.STRUCTURE,
122           "Set the type of temperature factor. Possible values are:\n"
123                   + "default,\n" + "plddt.",
124           Opt.STRING, Opt.LINKED),
125   STRUCTUREVIEWER(Type.STRUCTURE,
126           "Set the structure viewer to use to open the 3D structure file specified in previous --structure to name. Possible values of name are:\n"
127                   + "none,\n" + "jmol,\n" + "chimera,\n" + "chimerax,\n"
128                   + "pymol.",
129           Opt.STRING, Opt.LINKED, Opt.MULTI),
130   STRUCTUREIMAGE(Type.STRUCTURE,
131           "Export an image of a 3D structure opened in JMOL", Opt.STRING,
132           Opt.LINKED, Opt.MULTI),
133   NOTEMPFAC(Type.STRUCTURE,
134           "Do not show the temperature factor annotation for the preceding --structure.",
135           Opt.UNARY, Opt.LINKED, Opt.ALLOWALL, Opt.SECRET), // keep this secret
136                                                             // until it
137   // works!
138   SHOWSSANNOTATIONS(Type.STRUCTURE, null, Opt.BOOLEAN, Opt.LINKED,
139           Opt.ALLOWALL),
140
141   // Outputting files
142   IMAGE(Type.IMAGE,
143           "Output an image of the open alignment window. Format is specified by the subval modifier, a following --type argument or guessed from the file extension. Valid formats/extensions are:\n"
144                   + "svg,\n" + "png,\n" + "eps,\n" + "html,\n" + "biojs.",
145           Opt.STRING, Opt.LINKED, Opt.ALLOWSUBSTITUTIONS, Opt.ALLOWALL,
146           Opt.REQUIREINPUT, Opt.OUTPUT, Opt.PRIMARY),
147   TYPE(Type.IMAGE,
148           "Set the image format for the preceding --image to name. Valid values for name are: svg,\n"
149                   + "png,\n" + "eps,\n" + "html,\n" + "biojs.",
150           Opt.STRING, Opt.LINKED, Opt.ALLOWALL),
151   TEXTRENDERER(Type.IMAGE,
152           "Sets whether text in a vector image format (SVG, HTML, EPS) should be rendered as text or vector line-art. Possible values for name are:\n"
153                   + "text,\n" + "lineart.",
154           Opt.STRING, Opt.LINKED, Opt.ALLOWALL),
155   SCALE(Type.IMAGE,
156           "Sets a scaling for bitmap image format (PNG). Should be given as a floating point number. If used in conjunction with --width and --height then the smallest scaling will be used (scale, width and height provide bounds for the image).",
157           Opt.STRING, Opt.LINKED, Opt.ALLOWALL),
158   WIDTH(Type.IMAGE,
159           "Sets a width for bitmap image format (PNG) with the height maintaining the aspect ratio. Should be given as a positive integer. If used in conjunction with --scale and --height then the smallest scaling will be used (scale, width and height provide bounds for the image).",
160           Opt.STRING, Opt.LINKED, Opt.ALLOWALL),
161   HEIGHT(Type.IMAGE,
162           "Sets a height for bitmap image format (PNG) with the width maintaining the aspect ratio. Should be given as a positive integer. If used in conjunction with --scale and --width then the smallest scaling will be used (scale, width and height provide bounds for the image).",
163           Opt.STRING, Opt.LINKED, Opt.ALLOWALL),
164   OUTPUT(Type.OUTPUT,
165           "Export the open alignment to file filename. The format name is specified by the subval modifier format=name, a following --format name argument or guessed from the file extension. Valid format names (and file extensions) are:\n"
166                   + "fasta (fa, fasta, mfa, fastq),\n" + "pfam (pfam),\n"
167                   + "stockholm (sto, stk),\n" + "pir (pir),\n"
168                   + "blc (blc),\n" + "amsa (amsa),\n" + "json (json),\n"
169                   + "pileup (pileup),\n" + "msf (msf),\n"
170                   + "clustal (aln),\n" + "phylip (phy),\n"
171                   + "jalview (jvp, jar).",
172           Opt.STRING, Opt.LINKED, Opt.ALLOWSUBSTITUTIONS, Opt.ALLOWALL,
173           Opt.REQUIREINPUT, Opt.OUTPUT, Opt.PRIMARY),
174   FORMAT(Type.OUTPUT,
175           "Sets the format for the preceding --output file. Valid formats are:\n"
176                   + "fasta,\n" + "pfam,\n" + "stockholm,\n" + "pir,\n"
177                   + "blc,\n" + "amsa,\n" + "json,\n" + "pileup,\n"
178                   + "msf,\n" + "clustal,\n" + "phylip,\n" + "jalview.",
179           Opt.STRING, Opt.LINKED, Opt.ALLOWALL),
180   GROOVY(Type.PROCESS,
181           "Process a groovy script in the file for the open alignment.",
182           Opt.STRING, Opt.LINKED, Opt.MULTI, Opt.ALLOWSUBSTITUTIONS,
183           Opt.ALLOWALL),
184   BACKUPS(Type.OUTPUT,
185           "Enable (or disable) writing backup files when saving an ‑‑output file. This applies to the current open alignment.  To apply to all ‑‑output and ‑‑image files, use after ‑‑all.",
186           true, Opt.BOOLEAN, Opt.LINKED, Opt.ALLOWALL),
187   OVERWRITE(Type.OUTPUT,
188           "Enable (or disable) overwriting of output files without backups enabled. This applies to the current open alignment.  To apply to all ‑‑output and ‑‑image files, use after ‑‑all.",
189           Opt.BOOLEAN, Opt.LINKED, Opt.ALLOWALL),
190   CLOSE(Type.OPENING,
191           "Close the current open alignment window. This occurs after other output arguments. This applies to the current open alignment.  To apply to all ‑‑output and ‑‑image files, use after ‑‑all.",
192           Opt.UNARY, Opt.LINKED, Opt.ALLOWALL),
193
194   // controlling flow of arguments
195   NEW(Type.FLOW,
196           "Move on to a new alignment window. This will ensure --append will start a new alignment window and other linked arguments will apply to the new alignment window.",
197           Opt.UNARY, Opt.MULTI, Opt.NOACTION, Opt.INCREMENTDEFAULTCOUNTER),
198   SUBSTITUTIONS(Type.FLOW,
199           "The following argument values allow (or don't allow) subsituting filename parts. This is initially true. Valid substitutions are {basename} - the filename-without-extension of the currently --opened file (or first --appended file),\n"
200                   + "{dirname}, - the directory (folder) name of the currently --opened file (or first --appended file),\n"
201                   + "{argfilebasename} - the filename-without-extension of the current --argfile,\n"
202                   + "{argfiledirname} - the directory (folder) name of the current --argfile,\n"
203                   + "{n} - the value of the index counter (starting at 0).\n"
204                   + "{++n} - increase and substitute the value of the index counter,\n"
205                   + "{} - the value of the current alignment window default index.",
206           true, Opt.BOOLEAN, Opt.MULTI, Opt.NOACTION),
207   ARGFILE(Type.FLOW,
208           "Open one or more files filename and read, line-by-line, as arguments to Jalview.\n"
209                   + "Values in an argfile should be given with an equals sign (\"=\") separator with no spaces.\n"
210                   + "Note that if you use one or more --argfile arguments then all other non-initialising arguments will be ignored.",
211           Opt.STRING, Opt.MULTI, Opt.BOOTSTRAP, Opt.GLOB,
212           Opt.ALLOWSUBSTITUTIONS),
213   NPP(Type.FLOW, "n++",
214           "Increase the index counter used in argument value substitutions.",
215           Opt.UNARY, Opt.MULTI, Opt.NOACTION),
216   ALL(Type.FLOW,
217           "Apply the following output arguments to all sets of linked arguments.",
218           Opt.BOOLEAN, Opt.MULTI, Opt.NOACTION),
219   OPENED(Type.FLOW,
220           "Apply the following output arguments to all of the last --open'ed set of linked arguments.",
221           Opt.BOOLEAN, Opt.MULTI, Opt.NOACTION),
222   QUIT(Type.FLOW,
223           "After all files have been opened, appended and output, quit Jalview. In ‑‑headless mode this already happens.",
224           Opt.UNARY),
225
226   // secret options
227   TESTOUTPUT(Type.CONFIG,
228           "Allow specific stdout information.  For testing purposes only.",
229           Opt.UNARY, Opt.BOOTSTRAP, Opt.SECRET), // do not show this to the user
230   SETPROP(Type.CONFIG, "Set an individual Java System property.",
231           Opt.STRING, Opt.MULTI, Opt.BOOTSTRAP, Opt.SECRET), // not in use yet
232   NIL(Type.FLOW,
233           "This argument does nothing on its own, but can be used with linkedIds.",
234           Opt.UNARY, Opt.LINKED, Opt.MULTI, Opt.NOACTION, Opt.SECRET),
235
236   // private options (inserted during arg processing)
237   SETARGFILE(Type.FLOW,
238           "Sets the current value of the argfilename.  Inserted before argfilecontents.",
239           Opt.UNARY, Opt.LINKED, Opt.STRING, Opt.MULTI, Opt.PRIVATE,
240           Opt.NOACTION),
241   UNSETARGFILE(Type.FLOW,
242           "Unsets the current value of the argfilename.  Inserted after argfile contents.",
243           Opt.UNARY, Opt.LINKED, Opt.MULTI, Opt.PRIVATE, Opt.NOACTION),
244
245   // these last two have no purpose in the normal Jalview application but are
246   // used by jalview.bin.Launcher to set memory settings. They are not used by
247   // argparser but are here for Usage statement reasons.
248   JVMMEMPC(Type.CONFIG,
249           "Limit maximum heap size (memory) to PERCENT% of total physical memory detected. This defaults to 90 if total physical memory can be detected.\n"
250                   + "The equals sign (\"=\") separator must be used with no spaces.",
251           Opt.NOACTION, Opt.BOOTSTRAP, Opt.STRING),
252   JVMMEMMAX(Type.CONFIG,
253           "Limit maximum heap size (memory) to MAXMEMORY. MAXMEMORY can be specified in bytes, kilobytes(k), megabytes(m), gigabytes(g) or if you're lucky enough, terabytes(t). This defaults to 32g if total physical memory can be detected, or to 8g if total physical memory cannot be detected.\n"
254                   + "The equals sign (\"=\") separator must be used with no spaces.",
255           Opt.NOACTION, Opt.BOOTSTRAP, Opt.STRING),
256
257   ;
258
259   public static enum Opt
260   {
261     BOOLEAN, // This Arg can be specified as --arg or --noarg to give true or
262              // false. A default can be given with setOptions(bool, Opt....).
263              // Use ArgParser.isSet(Arg) to see if this arg was not specified.
264     STRING, // This Arg can accept a value either through --arg=value or --arg
265             // value.
266     UNARY, // This Arg is a boolean value, true if present, false if not. Like
267            // BOOLEAN but without the --noarg option.
268     MULTI, // This Arg can be specified multiple times. Multiple values are
269            // stored in the ArgValuesMap (along with their positional index) for
270            // each linkedId.
271     LINKED, // This Arg can be linked to others through a --arg[linkedId] or
272             // --arg[linkedId]=value. If no linkedId is specified then the
273             // current default linkedId will be used.
274     NODUPLICATEVALUES, // This Arg can only have one value (per linkedId). The
275                        // first value will be used and subsequent values ignored
276                        // with a warning.
277     BOOTSTRAP, // This Arg value(s) can be determined at an earlier stage than
278                // non-BOOTSTRAP Args. Substitutions do not happen in BOOTSTRAP
279                // Args and they cannot be linked or contain SubVals. See
280                // jalview.bin.argparser.BootstrapArgs.
281     GLOB, // This Arg can expand wildcard filename "globs" (e.g.
282           // path/*/filename*). If the Arg value is given as --arg filename*
283           // then the shell will have expanded the glob already, but if
284           // specified as --arg=filename* then the Java glob expansion method
285           // will be used (see FileUtils.getFilenamesFromGlob()). Note that this
286           // might be different from the shell expansion rules.
287     NOACTION, // This Arg does not perform a data task, usually used to control
288               // flow in ArgParser.parse(args).
289     ALLOWSUBSTITUTIONS, // This Arg allows substitutions in its linkedId,
290                         // SubVals and values.
291     PRIVATE, // This Arg is used internally, and cannot be specified by the
292              // user.
293     SECRET, // This Arg is used by development processes and although it can be
294             // set by the user, it is not displayed to the user.
295     ALLOWALL, // This Arg can use the '*' linkedId to apply to all known
296               // linkedIds
297     INCREMENTDEFAULTCOUNTER, // If an Arg has this option and the default
298                              // linkedId is used, the defaultLinkedIdCounter is
299                              // incremented *first*.
300     INPUT, // This Arg counts as an input for REQUIREINPUT
301     REQUIREINPUT, // This Arg can only be applied via --all if there is an
302                   // input (i.e. --open or --append)
303     OUTPUT, // This Arg provides an output filename. With Opt.ALLOWALL *.ext is
304             // shorthand for --all --output={basename}.ext
305     STORED, // This Arg resets and creates a new set of "opened" linkedIds
306     HELP, // This Arg is a --help type arg
307     PRIMARY, // This Arg is the main Arg for its type
308     HASTYPE, // This Arg can have an Arg.Type assigned to it (and no value)
309   }
310
311   public static enum Type
312   {
313     // Type restricts argument to certain usage output
314     CONFIG("Arguments user to configure "
315             + ChannelProperties.getProperty("app_name") + " from startup"),
316     OPENING("Arguments used to open and format alignments"),
317     STRUCTURE("Arguments used to add and format 3D structure data"),
318     PROCESS("Arguments used to process an alignment once opened"),
319     OUTPUT("Arguments used to save data from a processed alignment"),
320     IMAGE("Arguments used to export an image of an alignment"),
321     FLOW("Arguments that control processing of the other arguments"),
322     HELP("Arguments to provide help text"), // --help
323     ALL("All arguments"), // mostly just a place-holder for --help-all
324     NONE("No specific arguments"), // mostly a place-holder for --help
325     INVALID("This type of argument doesn't exist");
326
327     private String description;
328
329     private Type(String description)
330     {
331       this.description = description;
332     }
333
334     public String description()
335     {
336       return description;
337     }
338   }
339
340   static
341   {
342     for (Type t : EnumSet.allOf(Type.class))
343     {
344       String type = t.name();
345
346     }
347   }
348
349   private final String[] argNames;
350
351   private Opt[] argOptions;
352
353   private boolean defaultBoolValue;
354
355   private String description;
356
357   private Type type;
358
359   private Arg(Type type, String description, Opt... options)
360   {
361     this(type, null, description, false, options);
362   }
363
364   private Arg(Type type, String description, boolean defaultBoolean,
365           Opt... options)
366   {
367     this(type, null, description, defaultBoolean, options);
368   }
369
370   private Arg(Type type, String alternativeName, String description,
371           Opt... options)
372   {
373     this(type, alternativeName, description, false, options);
374   }
375
376   private Arg(Type type, String alternativeName, String description,
377           boolean defaultBoolean, Opt... options)
378   {
379     this.argNames = alternativeName != null
380             ? new String[]
381             { this.getName(), alternativeName }
382             : new String[]
383             { this.getName() };
384     this.type = type;
385     this.description = description;
386     this.defaultBoolValue = defaultBoolean;
387     this.setOptions(options);
388   }
389
390   public String argString()
391   {
392     return argString(false);
393   }
394
395   public String negateArgString()
396   {
397     return argString(true);
398   }
399
400   private String argString(boolean negate)
401   {
402     StringBuilder sb = new StringBuilder(ArgParser.DOUBLEDASH);
403     if (negate && hasOption(Opt.BOOLEAN))
404       sb.append(ArgParser.NEGATESTRING);
405     sb.append(getName());
406     return sb.toString();
407   }
408
409   public String toLongString()
410   {
411     StringBuilder sb = new StringBuilder();
412     sb.append(this.getClass().getName()).append('.').append(this.name());
413     sb.append('(');
414     if (getNames().length > 0)
415       sb.append('"');
416     sb.append(String.join("\", \"", getNames()));
417     if (getNames().length > 0)
418       sb.append('"');
419     sb.append(")\n");
420     sb.append("\nType: " + type.name());
421     sb.append("\nOpt: ");
422     // map List<Opt> to List<String> for the String.join
423     List<String> optList = Arrays.asList(argOptions).stream()
424             .map(opt -> opt.name()).collect(Collectors.toList());
425     sb.append(String.join(", ", optList));
426     sb.append("\n");
427     return sb.toString();
428   }
429
430   public String[] getNames()
431   {
432     return argNames;
433   }
434
435   public String getName()
436   {
437     return this.name().toLowerCase(Locale.ROOT).replace('_', '-');
438   }
439
440   @Override
441   public final String toString()
442   {
443     return getName();
444   }
445
446   public boolean hasOption(Opt o)
447   {
448     if (argOptions == null)
449       return false;
450     for (Opt option : argOptions)
451     {
452       if (o == option)
453         return true;
454     }
455     return false;
456   }
457
458   public boolean hasAllOptions(Opt... opts)
459   {
460     for (Opt o : opts)
461     {
462       if (!this.hasOption(o))
463         return false;
464     }
465     return true;
466   }
467
468   protected void setOptions(Opt... options)
469   {
470     this.argOptions = options;
471   }
472
473   protected boolean getDefaultBoolValue()
474   {
475     return defaultBoolValue;
476   }
477
478   public Type getType()
479   {
480     return this.type;
481   }
482
483   protected String getDescription()
484   {
485     return description;
486   }
487
488   public static String booleanArgString(Arg a)
489   {
490     StringBuilder sb = new StringBuilder(a.argString());
491     if (a.hasOption(Opt.BOOLEAN))
492     {
493       sb.append('/');
494       sb.append(a.negateArgString());
495     }
496     return sb.toString();
497   }
498
499   public static final String usage()
500   {
501     return usage(null);
502   }
503
504   public static final String usage(List<Arg> helpArgs)
505   {
506     StringBuilder sb = new StringBuilder();
507
508     sb.append(ChannelProperties.getProperty("app_name"));
509     String version = Cache.getDefault("VERSION", null);
510     if (version != null)
511     {
512       sb.append(" version ");
513       sb.append(Cache.getDefault("VERSION", "unknown"));
514     }
515     sb.append(System.lineSeparator());
516     sb.append("Usage: jalview [files...] [args]");
517     sb.append(System.lineSeparator());
518     sb.append(System.lineSeparator());
519
520     // PROGRAM THIS!
521     Type type = Type.HELP;
522     Iterator<Arg> typeArgs = Arg.getAllOfType(type);
523
524     int maxArgLength = 0;
525     for (Arg a : EnumSet.allOf(Arg.class))
526     {
527       if (a.hasOption(Opt.PRIVATE) || a.hasOption(Opt.SECRET))
528         continue;
529
530       StringBuilder argSb = new StringBuilder();
531       argSb.append(a.hasOption(Opt.BOOLEAN) ? booleanArgString(a)
532               : a.argString());
533       if (a.hasOption(Opt.STRING))
534         argSb.append("=value");
535       if (argSb.length() > maxArgLength)
536         maxArgLength = argSb.length();
537     }
538
539     // might want to sort these
540     for (Arg a : EnumSet.allOf(Arg.class))
541     {
542       if (a.hasOption(Opt.PRIVATE) || a.hasOption(Opt.SECRET))
543         continue;
544       StringBuilder argSb = new StringBuilder();
545       argSb.append(a.hasOption(Opt.BOOLEAN) ? booleanArgString(a)
546               : a.argString());
547       if (a.hasOption(Opt.STRING))
548         argSb.append("=value");
549       Iterator<String> descLines = null;
550       if (a.getDescription() != null)
551       {
552         descLines = Arrays.stream(a.getDescription().split("\\n"))
553                 .iterator();
554       }
555       sb.append(String.format("%-" + maxArgLength + "s", argSb.toString()));
556       boolean first = true;
557       if (descLines != null)
558       {
559         while (descLines.hasNext())
560         {
561           if (first)
562             sb.append(" - ");
563           else
564             sb.append(" ".repeat(maxArgLength + 3));
565           sb.append(descLines.next());
566           sb.append(System.lineSeparator());
567           first = false;
568         }
569       }
570
571       List<String> options = new ArrayList<>();
572
573       if (a.hasOption(Opt.BOOLEAN))
574       {
575         options.add("default " + (a.getDefaultBoolValue() ? a.argString()
576                 : a.negateArgString()));
577       }
578
579       if (a.hasOption(Opt.MULTI))
580       {
581         options.add("multiple");
582       }
583
584       if (a.hasOption(Opt.LINKED))
585       {
586         options.add("can be linked");
587       }
588
589       if (a.hasOption(Opt.GLOB))
590       {
591         options.add("allows file globs");
592       }
593
594       if (a.hasOption(Opt.ALLOWSUBSTITUTIONS))
595       {
596         options.add("allows substitutions");
597       }
598
599       if (a.hasOption(Opt.ALLOWALL))
600       {
601         options.add("can be applied to all linked arguments");
602       }
603
604       if (a.hasOption(Opt.PRIVATE))
605       {
606         options.add("for internal use only");
607       }
608
609       if (a.hasOption(Opt.SECRET))
610       {
611         options.add("for development use only");
612       }
613
614       if (options.size() > 0)
615       {
616         if (first)
617           sb.append(" - ");
618         else
619           sb.append(" ".repeat(maxArgLength + 3));
620         sb.append("(");
621         sb.append(String.join("; ", options));
622         sb.append(')');
623         sb.append(System.lineSeparator());
624       }
625       sb.append(System.lineSeparator());
626     }
627     return sb.toString();
628   }
629
630   protected static Iterator<Arg> getAllOfType(Type type)
631   {
632     return getAllOfType(type, new Opt[] {});
633   }
634
635   protected static Iterator<Arg> getAllOfType(Type type, Opt... options)
636   {
637     Opt[] opts = options == null ? new Opt[] {} : options;
638     return EnumSet.allOf(Arg.class).stream().filter(a -> {
639       if (a.getType() != type)
640         return false;
641       for (Opt o : opts)
642       {
643         if (!a.hasOption(o))
644           return false;
645       }
646       return true;
647     }).iterator();
648   }
649
650   private static List<Arg> sortForDisplay(Type... types)
651   {
652     List<Arg> argsToSort;
653     // if no types provided, do all
654     if (types == null || types.length == 0)
655     {
656       argsToSort = Arrays
657               .asList(EnumSet.allOf(Arg.class).toArray(new Arg[] {}));
658     }
659     else
660     {
661       argsToSort = new ArrayList<>();
662       for (Type type : types)
663       {
664         Arg.getAllOfType(type).forEachRemaining(a -> argsToSort.add(a));
665       }
666     }
667
668     Collections.sort(argsToSort, new ArgComparator());
669     return argsToSort;
670   }
671
672   protected int compareForDisplay(Arg other)
673   {
674     if (other == null)
675       return 1;
676     // first compare types (in order of appearance)
677     int i = this.getType().compareTo(other.getType());
678     if (i == 0)
679     {
680       // next prioritise primary arguments
681       i = this.hasOption(Opt.PRIMARY)
682               ? (other.hasOption(Opt.PRIMARY) ? 0 : 1)
683               : (other.hasOption(Opt.PRIMARY) ? -1 : 0);
684       if (i == 0)
685       {
686         // finally order of appearance in enum declarations
687         i = this.compareTo(other);
688       }
689     }
690     return i;
691   }
692
693 }
694
695 class ArgComparator implements Comparator<Arg>
696 {
697   @Override
698   public int compare(Arg a, Arg b)
699   {
700     return a.compareForDisplay(b);
701   }
702 }