Clover icon

Coverage Report

  1. Project Clover database Mon Oct 6 2025 17:39:40 BST
  2. Package jalview.bin.argparser

File ArgParser.java

 

Coverage histogram

../../../img/srcFileCovDistChart8.png
21% of files have more coverage

Code metrics

228
405
35
2
1,322
948
211
0.52
11.57
17.5
6.03

Classes

Class Line # Actions
ArgParser 48 405 211
0.8098802681%
ArgParser.Op 1112 0 0
-1.0 -
 

Contributing tests

This file is covered by 130 tests. .

Source view

1    /*
2    * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3    * Copyright (C) $$Year-Rel$$ The Jalview Authors
4    *
5    * This file is part of Jalview.
6    *
7    * Jalview is free software: you can redistribute it and/or
8    * modify it under the terms of the GNU General Public License
9    * as published by the Free Software Foundation, either version 3
10    * of the License, or (at your option) any later version.
11    *
12    * Jalview is distributed in the hope that it will be useful, but
13    * WITHOUT ANY WARRANTY; without even the implied warranty
14    * of MERCHANTABILITY or FITNESS FOR A PARTICULAR
15    * PURPOSE. See the GNU General Public License for more details.
16    *
17    * You should have received a copy of the GNU General Public License
18    * along with Jalview. If not, see <http://www.gnu.org/licenses/>.
19    * The Jalview Authors are detailed in the 'AUTHORS' file.
20    */
21    package jalview.bin.argparser;
22   
23    import java.io.File;
24    import java.io.IOException;
25    import java.nio.file.Files;
26    import java.nio.file.Paths;
27    import java.util.ArrayList;
28    import java.util.Arrays;
29    import java.util.Collections;
30    import java.util.EnumSet;
31    import java.util.Enumeration;
32    import java.util.HashMap;
33    import java.util.List;
34    import java.util.Map;
35   
36    import jalview.bin.Cache;
37    import jalview.bin.Console;
38    import jalview.bin.Jalview;
39    import jalview.bin.Jalview.ExitCode;
40    import jalview.bin.argparser.Arg.Opt;
41    import jalview.bin.argparser.Arg.Type;
42    import jalview.io.AppletFormatAdapter;
43    import jalview.io.DataSourceType;
44    import jalview.util.ArgParserUtils;
45    import jalview.util.FileUtils;
46    import jalview.util.HttpUtils;
47   
 
48    public class ArgParser
49    {
50    protected static final String SINGLEDASH = "-";
51   
52    protected static final String DOUBLEDASH = "--";
53   
54    public static final char EQUALS = '=';
55   
56    public static final String STDOUTFILENAME = "-";
57   
58    protected static final String NEGATESTRING = "no";
59   
60    /**
61    * the default linked id prefix used for no id (ie when not even square braces
62    * are provided)
63    */
64    protected static final String DEFAULTLINKEDIDPREFIX = "JALVIEW:";
65   
66    /**
67    * the linkedId string used to match all linkedIds seen so far
68    */
69    protected static final String MATCHALLLINKEDIDS = "*";
70   
71    /**
72    * the linkedId string used to match all of the last --open'ed linkedIds
73    */
74    protected static final String MATCHOPENEDLINKEDIDS = "open*";
75   
76    /**
77    * the counter added to the default linked id prefix
78    */
79    private int defaultLinkedIdCounter = 0;
80   
81    /**
82    * the substitution string used to use the defaultLinkedIdCounter
83    */
84    private static final String DEFAULTLINKEDIDCOUNTER = "{}";
85   
86    /**
87    * the linked id prefix used for --open files. NOW the same as DEFAULT
88    */
89    protected static final String OPENLINKEDIDPREFIX = DEFAULTLINKEDIDPREFIX;
90   
91    /**
92    * the counter used for {n} substitutions
93    */
94    private int linkedIdAutoCounter = 0;
95   
96    /**
97    * the linked id substitution string used to increment the idCounter (and use
98    * the incremented value)
99    */
100    private static final String INCREMENTLINKEDIDAUTOCOUNTER = "{++n}";
101   
102    /**
103    * the linked id substitution string used to use the idCounter
104    */
105    private static final String LINKEDIDAUTOCOUNTER = "{n}";
106   
107    /**
108    * the linked id substitution string used to use the filename extension of
109    * --append or --open
110    */
111    private static final String LINKEDIDEXTENSION = "{extension}";
112   
113    /**
114    * the linked id substitution string used to use the base filename of --append
115    */
116    /** or --open */
117    private static final String LINKEDIDBASENAME = "{basename}";
118   
119    /**
120    * the linked id substitution string used to use the dir path of --append or
121    * --open
122    */
123    private static final String LINKEDIDDIRNAME = "{dirname}";
124   
125    /**
126    * On-the-fly substitution (not made at argument parsing time)! the current
127    * structure filename extension
128    */
129    private static final String STRUCTUREEXTENSION = "{structureextension}";
130   
131    /**
132    * On-the-fly substitution (not made at argument parsing time)! the current
133    * structure filename base
134    */
135    private static final String STRUCTUREBASENAME = "{structurebasename}";
136   
137    /**
138    * On-the-fly substitution (not made at argument parsing time)! the current
139    * structure filename dir path
140    */
141    private static final String STRUCTUREDIRNAME = "{structuredirname}";
142   
143    /**
144    * On-the-fly substitution (not made at argument parsing time)! increment the
145    * on-the-fly counter and substitute the incremented value
146    */
147    private static final String INCREMENTONTHEFLYCOUNTER = "{++m}";
148   
149    /**
150    * On-the-fly substitution (not made at argument parsing time)! the current
151    * substitute with the on-the-fly counter
152    */
153    private static final String ONTHEFLYCOUNTER = "{m}";
154   
155    /**
156    * the string used for on-the-fly structure filename substitutions
157    */
158    private String currentStructureFilename = null;
159   
160    /**
161    * the counter used for on-the-fly {m} substitutions
162    */
163    private int ontheflyCounter = 0;
164   
165    /**
166    * the current argfile
167    */
168    private String argFile = null;
169   
170    /**
171    * the linked id substitution string used to use the dir path of the latest
172    */
173    /** --argfile name */
174    private static final String ARGFILEBASENAME = "{argfilebasename}";
175   
176    /**
177    * the linked id substitution string used to use the dir path of the latest
178    * --argfile name
179    */
180    private static final String ARGFILEDIRNAME = "{argfiledirname}";
181   
182    /**
183    * flag to say whether {n} subtitutions in output filenames should be made.
184    * Turn on and off with --substitutions and --nosubstitutions Start with it on
185    */
186    private boolean substitutions = true;
187   
188    /**
189    * flag to say whether the default linkedId is the current default linked id
190    *
191    * or ALL linkedIds
192    */
193    private boolean allLinkedIds = false;
194   
195    /**
196    * flag to say whether the structure arguments should be applied to all
197    * structures with this linked id
198    */
199    private boolean allStructures = false;
200   
201    protected static final Map<String, Arg> argMap;
202   
203    protected Map<String, ArgValuesMap> linkedArgs = new HashMap<>();
204   
205    protected List<String> linkedOrder = new ArrayList<>();
206   
207    protected List<String> storedLinkedIds = new ArrayList<>();
208   
209    protected List<Arg> argList = new ArrayList<>();
210   
211    private static final char ARGFILECOMMENT = '#';
212   
213    private int argIndex = 0;
214   
215    private BootstrapArgs bootstrapArgs = null;
216   
217    private boolean oldArguments = false;
218   
219    private boolean mixedArguments = false;
220   
221    /**
222    * saved examples of mixed arguments
223    */
224    private String[] mixedExamples = new String[] { null, null };
225   
 
226  54 toggle static
227    {
228  54 argMap = new HashMap<>();
229  54 for (Arg a : EnumSet.allOf(Arg.class))
230    {
231  3888 for (String argName : a.getNames())
232    {
233  4320 if (argMap.containsKey(argName))
234    {
235  0 Console.warn("Trying to add argument name multiple times: '"
236    + argName + "'");
237  0 if (argMap.get(argName) != a)
238    {
239  0 Console.error(
240    "Trying to add argument name multiple times for different Args: '"
241    + argMap.get(argName).getName() + ":" + argName
242    + "' and '" + a.getName() + ":" + argName
243    + "'");
244    }
245  0 continue;
246    }
247  4320 argMap.put(argName, a);
248    }
249    }
250    }
251   
 
252  48 toggle public ArgParser(String[] args)
253    {
254  48 this(args, false, null);
255    }
256   
 
257  184 toggle public ArgParser(String[] args, boolean initsubstitutions,
258    BootstrapArgs bsa)
259    {
260    /*
261    * Make a mutable new ArrayList so that shell globbing parser works.
262    * (When shell file globbing is used, there are a sequence of non-Arg
263    * arguments (which are the expanded globbed filenames) that need to be
264    * consumed by the --append/--argfile/etc Arg which is most easily done
265    * by removing these filenames from the list one at a time. This can't be
266    * done with an ArrayList made with only Arrays.asList(String[] args) as
267    * that is not mutable. )
268    */
269  184 this(new ArrayList<>(Arrays.asList(args)), initsubstitutions, false,
270    bsa);
271    }
272   
 
273  0 toggle public ArgParser(List<String> args, boolean initsubstitutions)
274    {
275  0 this(args, initsubstitutions, false, null);
276    }
277   
 
278  192 toggle public ArgParser(List<String> args, boolean initsubstitutions,
279    boolean allowPrivate, BootstrapArgs bsa)
280    {
281    // do nothing if there are no "--" args and (some "-" args || >0 arg is
282    // "open")
283  192 boolean d = false;
284  192 boolean dd = false;
285  192 for (String arg : args)
286    {
287  1226 if (arg.startsWith(DOUBLEDASH))
288    {
289  991 dd = true;
290  991 if (mixedExamples[1] == null)
291    {
292  165 mixedExamples[1] = arg;
293    }
294    }
295  235 else if ((arg.startsWith("-") && !arg.equals(STDOUTFILENAME))
296    || arg.equals("open"))
297    {
298  50 d = true;
299  50 if (mixedExamples[0] == null)
300    {
301  19 mixedExamples[0] = arg;
302    }
303    }
304    }
305  192 if (d)
306    {
307  19 if (dd)
308    {
309  1 mixedArguments = true;
310    }
311    else
312    {
313  18 oldArguments = true;
314    }
315    }
316   
317  192 if (oldArguments || mixedArguments)
318    {
319    // leave it to the old style -- parse an empty list
320  19 parse(new ArrayList<String>(), false, false);
321  19 return;
322    }
323   
324    // preprocess for associated files only if no actual --args supplied
325  173 Console.debug("Supplied args are " + args);
326  173 if (!dd && !Cache.getDefault("NOARGPREPROCESSING", false))
327    {
328  9 ArgParserUtils.preProcessArgs(args);
329  9 Console.debug("Preprocessed args are " + args);
330    }
331   
332  173 if (bsa != null)
333    {
334  125 this.bootstrapArgs = bsa;
335    }
336    else
337    {
338  48 this.bootstrapArgs = BootstrapArgs.getBootstrapArgs(args);
339    }
340  173 parse(args, initsubstitutions, allowPrivate);
341    }
342   
 
343  192 toggle private void parse(List<String> args, boolean initsubstitutions,
344    boolean allowPrivate)
345    {
346  192 this.substitutions = initsubstitutions;
347   
348    /*
349    * If the first argument does not start with "--" or "-" or is not "open",
350    * and is a filename that exists or a URL, it is probably a file/list of
351    * files to open so we insert an Arg.OPEN argument before it. This will
352    * mean the list of files at the start of the arguments are all opened
353    * separately.
354    */
355  192 if (args.size() > 0)
356    {
357  164 String arg0 = args.get(0);
358  164 if (arg0 != null
359    && (!arg0.startsWith(DOUBLEDASH) && !arg0.startsWith("-")
360    && !arg0.equals("open") && (new File(arg0).exists()
361    || HttpUtils.startsWithHttpOrHttps(arg0))))
362    {
363    // insert "--open" at the start
364  6 args.add(0, Arg.OPEN.argString());
365    }
366    }
367   
368  1258 for (int i = 0; i < args.size(); i++)
369    {
370  1066 String arg = args.get(i);
371   
372    // look for double-dash, e.g. --arg
373  1066 if (arg.startsWith(DOUBLEDASH))
374    {
375  996 String argName = null;
376  996 String val = null;
377  996 List<String> globVals = null; // for Opt.GLOB only
378  996 SubVals globSubVals = null; // also for use by Opt.GLOB only
379  996 String linkedId = null;
380  996 String givenLinkedId = null; // this is preserved to add to each
381    // "ArgValue"
382  996 Type type = null;
383   
384    // look for equals e.g. --arg=value
385  996 int equalPos = arg.indexOf(EQUALS);
386  996 if (equalPos > -1)
387    {
388  460 argName = arg.substring(DOUBLEDASH.length(), equalPos);
389  460 val = arg.substring(equalPos + 1);
390    }
391    else
392    {
393  536 argName = arg.substring(DOUBLEDASH.length());
394    }
395   
396    // look for linked ID e.g. --arg[linkedID]
397  996 int idOpen = argName.indexOf('[');
398  996 int idClose = argName.indexOf(']');
399  996 if (idOpen > -1 && idClose == argName.length() - 1)
400    {
401  68 linkedId = argName.substring(idOpen + 1, idClose);
402  68 givenLinkedId = linkedId;
403  68 argName = argName.substring(0, idOpen);
404    }
405   
406    // look for type modification e.g. --help-opening
407  996 int dashPos = argName.indexOf(SINGLEDASH);
408  996 if (dashPos > -1)
409    {
410  0 String potentialArgName = argName.substring(0, dashPos);
411  0 Arg potentialArg = argMap.get(potentialArgName);
412  0 if (potentialArg != null && potentialArg.hasOption(Opt.HASTYPE))
413    {
414  0 String typeName = argName.substring(dashPos + 1);
415  0 try
416    {
417  0 type = Type.valueOf(typeName);
418    } catch (IllegalArgumentException e)
419    {
420  0 type = Type.INVALID;
421    }
422  0 argName = argName.substring(0, dashPos);
423    }
424    }
425   
426  996 Arg a = argMap.get(argName);
427    // check for boolean prepended by "no" e.g. --nowrap
428  996 boolean negated = false;
429  996 if (a == null)
430    {
431  148 if (argName.startsWith(NEGATESTRING) && argMap
432    .containsKey(argName.substring(NEGATESTRING.length())))
433    {
434  148 argName = argName.substring(NEGATESTRING.length());
435  148 a = argMap.get(argName);
436  148 negated = true;
437    }
438    else
439    {
440    // after all other args, look for Opt.PREFIXKEV args if still not
441    // found
442  0 for (Arg potentialArg : EnumSet.allOf(Arg.class))
443    {
444  0 if (potentialArg.hasOption(Opt.PREFIXKEV) && argName != null
445    && argName.startsWith(potentialArg.getName())
446    && equalPos > -1)
447    {
448  0 val = argName.substring(potentialArg.getName().length())
449    + EQUALS + val;
450  0 argName = argName.substring(0,
451    potentialArg.getName().length());
452  0 a = potentialArg;
453  0 break;
454    }
455    }
456    }
457    }
458   
459    // check for config errors
460  996 if (a == null)
461    {
462    // arg not found
463  0 Console.error("Argument '" + arg + "' not recognised. Exiting.");
464  0 Jalview.exit(
465    "Invalid argument used." + System.lineSeparator() + "Use"
466    + System.lineSeparator() + "jalview "
467    + Arg.HELP.argString() + System.lineSeparator()
468    + "for a usage statement.",
469    ExitCode.INVALID_ARGUMENT);
470  0 continue;
471    }
472  996 if (a.hasOption(Opt.PRIVATE) && !allowPrivate)
473    {
474  0 Console.error(
475    "Argument '" + a.argString() + "' is private. Ignoring.");
476  0 continue;
477    }
478  996 if (!a.hasOption(Opt.BOOLEAN) && negated)
479    {
480    // used "no" with a non-boolean option
481  1 Console.error("Argument '" + DOUBLEDASH + NEGATESTRING + argName
482    + "' not a boolean option. Ignoring.");
483  1 continue;
484    }
485  995 if (!a.hasOption(Opt.STRING) && equalPos > -1)
486    {
487    // set --argname=value when arg does not accept values
488  0 Console.error("Argument '" + a.argString()
489    + "' does not expect a value (given as '" + arg
490    + "'). Ignoring.");
491  0 continue;
492    }
493  995 if (!a.hasOption(Opt.LINKED) && linkedId != null)
494    {
495    // set --argname[linkedId] when arg does not use linkedIds
496  0 Console.error("Argument '" + a.argString()
497    + "' does not expect a linked id (given as '" + arg
498    + "'). Ignoring.");
499  0 continue;
500    }
501   
502    // String value(s)
503  995 if (a.hasOption(Opt.STRING))
504    {
505  576 if (equalPos >= 0)
506    {
507  460 if (a.hasOption(Opt.GLOB))
508    {
509    // strip off and save the SubVals to be added individually later
510  138 globSubVals = new SubVals(val);
511    // make substitutions before looking for files
512  138 String fileGlob = makeSubstitutions(globSubVals.getContent(),
513    linkedId);
514  138 if (!HttpUtils.startsWithHttpOrHttps(fileGlob)
515    || DataSourceType.FILE.equals(
516    AppletFormatAdapter.checkProtocol(fileGlob)))
517    {
518  135 globVals = FileUtils.getFilenamesFromGlob(fileGlob);
519    }
520    }
521    else
522    {
523    // val is already set -- will be saved in the ArgValue later in
524    // the normal way
525    }
526    }
527    else
528    {
529    // There is no "=" so value is next arg or args (possibly shell
530    // glob-expanded)
531  116 if (i + 1 >= args.size())
532    {
533    // no value to take for arg, which wants a value
534  4 Console.error("Argument '" + a.getName()
535    + "' requires a value, none given. Ignoring.");
536  4 continue;
537    }
538    // deal with bash globs here (--arg val* is expanded before reaching
539    // the JVM). Note that SubVals cannot be used in this case.
540    // If using the --arg=val then the glob is preserved and Java globs
541    // will be used later. SubVals can be used.
542  112 if (a.hasOption(Opt.GLOB))
543    {
544    // if this is the first argument with a file list at the start of
545    // the args we add filenames from index i instead of i+1
546  42 globVals = getShellGlobbedFilenameValues(a, args, i + 1);
547    }
548    else
549    {
550  70 val = args.get(i + 1);
551    }
552    }
553    }
554   
555    // make NOACTION adjustments
556    // default and auto counter increments
557  991 if (a == Arg.NPP)
558    {
559  0 linkedIdAutoCounter++;
560    }
561  991 else if (a == Arg.SUBSTITUTIONS)
562    {
563  28 substitutions = !negated;
564    }
565  963 else if (a == Arg.SETARGFILE)
566    {
567  14 argFile = val;
568    }
569  949 else if (a == Arg.UNSETARGFILE)
570    {
571  14 argFile = null;
572    }
573  935 else if (a == Arg.ALL)
574    {
575  13 allLinkedIds = !negated;
576    }
577  922 else if (a == Arg.ALLSTRUCTURES)
578    {
579  3 allStructures = !negated;
580    }
581   
582  991 if (a.hasOption(Opt.STORED))
583    {
584    // reset the lastOpenedLinkedIds list
585  111 this.storedLinkedIds = new ArrayList<>();
586    }
587   
588    // this is probably only Arg.NEW and Arg.OPEN
589  991 if (a.hasOption(Opt.INCREMENTDEFAULTCOUNTER))
590    {
591    // use the next default prefixed OPENLINKEDID
592  128 defaultLinkedId(true);
593    }
594   
595  991 String autoCounterString = null;
596  991 String defaultLinkedId = defaultLinkedId(false);
597  991 boolean usingDefaultLinkedId = false;
598  991 if (a.hasOption(Opt.LINKED))
599    {
600  618 if (linkedId == null)
601    {
602  550 if (a.hasOption(Opt.OUTPUTFILE) && a.hasOption(Opt.ALLOWMULTIID)
603    && val.contains(MATCHALLLINKEDIDS))
604    {
605    // --output=*.ext is shorthand for --output {basename}.ext
606    // --output=*/*.ext is shorthand for
607    // --output {dirname}/{basename}.ext
608    // (or --image=*.ext)
609  3 linkedId = allLinkedIds ? MATCHALLLINKEDIDS
610    : MATCHOPENEDLINKEDIDS;
611  3 val = FileUtils.convertWildcardsToPath(val, MATCHALLLINKEDIDS,
612    LINKEDIDDIRNAME, LINKEDIDBASENAME);
613    }
614  547 else if (allLinkedIds && a.hasOption(Opt.ALLOWMULTIID))
615    {
616  22 linkedId = MATCHALLLINKEDIDS;
617    }
618  550 if (allLinkedIds)
619    {
620    // user has made conscious decision for these args to apply to
621    // all, so set givenLinkedId too
622  23 givenLinkedId = linkedId;
623    }
624  527 else if (a.hasOption(Opt.ALLOWMULTIID)
625    && this.storedLinkedIds != null
626    && this.storedLinkedIds.size() > 0)
627    {
628  270 linkedId = MATCHOPENEDLINKEDIDS;
629    }
630    else
631    {
632    // use default linkedId for linked arguments
633  257 linkedId = defaultLinkedId;
634  257 usingDefaultLinkedId = true;
635  257 Console.debug("Changing linkedId to '" + linkedId + "' from "
636    + arg);
637    }
638    }
639    else
640    {
641  68 if (linkedId.contains(LINKEDIDAUTOCOUNTER))
642    {
643    // turn {n} to the autoCounter
644  40 autoCounterString = Integer.toString(linkedIdAutoCounter);
645  40 linkedId = linkedId.replace(LINKEDIDAUTOCOUNTER,
646    autoCounterString);
647  40 Console.debug("Changing linkedId to '" + linkedId + "' from "
648    + arg);
649    }
650  68 if (linkedId.contains(INCREMENTLINKEDIDAUTOCOUNTER))
651    {
652    // turn {++n} to the incremented autoCounter
653  15 autoCounterString = Integer.toString(++linkedIdAutoCounter);
654  15 linkedId = linkedId.replace(INCREMENTLINKEDIDAUTOCOUNTER,
655    autoCounterString);
656  15 Console.debug("Changing linkedId to '" + linkedId + "' from "
657    + arg);
658    }
659    }
660    }
661   
662    // do not continue in this block for NOACTION args
663  991 if (a.hasOption(Opt.NOACTION))
664  98 continue;
665   
666  893 ArgValuesMap avm = getOrCreateLinkedArgValuesMap(linkedId);
667   
668    // not dealing with both NODUPLICATEVALUES and GLOB
669  893 if (a.hasOption(Opt.NODUPLICATEVALUES) && avm.hasValue(a, val))
670    {
671  0 Console.error("Argument '" + a.argString()
672    + "' cannot contain a duplicate value ('" + val
673    + "'). Ignoring this and subsequent occurrences.");
674  0 continue;
675    }
676   
677    // check for unique id
678  893 SubVals subvals = new SubVals(val);
679  893 boolean addNewSubVals = false;
680  893 String id = subvals.get(ArgValues.ID);
681  893 if (id != null && avm.hasId(a, id))
682    {
683  0 Console.error("Argument '" + a.argString()
684    + "' has a duplicate id ('" + id + "'). Ignoring.");
685  0 continue;
686    }
687   
688    // set allstructures to all non-primary structure options in this linked
689    // id if --allstructures has been set
690  893 if (allStructures && (a.hasType(Type.STRUCTURE)
691    // || a.getType() == Type.STRUCTUREIMAGE)
692    ) && !a.hasOption(Opt.PRIMARY))
693    {
694  1 if (!subvals.has(Arg.ALLSTRUCTURES.getName()))
695    // && !subvals.has("structureid"))
696    {
697  1 subvals.put(Arg.ALLSTRUCTURES.getName(), "true");
698  1 addNewSubVals = true;
699    }
700    }
701   
702  893 ArgValues avs = avm.getOrCreateArgValues(a);
703   
704    // store appropriate String value(s)
705  893 if (a.hasOption(Opt.STRING))
706    {
707  558 if (a.hasOption(Opt.GLOB) && globVals != null
708    && globVals.size() > 0)
709    {
710  177 Enumeration<String> gve = Collections.enumeration(globVals);
711  434 while (gve.hasMoreElements())
712    {
713  257 String v = gve.nextElement();
714  257 SubVals vsv = new SubVals(globSubVals, v);
715  257 addValue(linkedId, givenLinkedId, type, avs, vsv, v,
716    argIndex++, true);
717    // if we're using defaultLinkedId and the arg increments the
718    // counter:
719  257 if (gve.hasMoreElements() && usingDefaultLinkedId
720    && a.hasOption(Opt.INCREMENTDEFAULTCOUNTER))
721    {
722    // increment the default linkedId
723  59 linkedId = defaultLinkedId(true);
724    // get new avm and avs
725  59 avm = linkedArgs.get(linkedId);
726  59 avs = avm.getOrCreateArgValues(a);
727    }
728    }
729    }
730    else
731    {
732    // addValue(linkedId, type, avs, val, argIndex, true);
733  381 addValue(linkedId, givenLinkedId, type, avs,
734  381 addNewSubVals ? subvals : null, val, argIndex, true);
735    }
736    }
737  335 else if (a.hasOption(Opt.BOOLEAN))
738    {
739  189 setBoolean(linkedId, givenLinkedId, type, avs, !negated,
740    argIndex);
741  189 setNegated(linkedId, avs, negated);
742    }
743  146 else if (a.hasOption(Opt.UNARY))
744    {
745  146 setBoolean(linkedId, givenLinkedId, type, avs, true, argIndex);
746    }
747   
748    // remove the '*' or 'open*' linkedId that should be empty if it was
749    // created
750  893 if ((MATCHALLLINKEDIDS.equals(linkedId)
751    || MATCHOPENEDLINKEDIDS.equals(linkedId))
752    && linkedArgs.containsKey(linkedId))
753    {
754  293 linkedArgs.remove(linkedId);
755    }
756    }
757    }
758    }
759   
 
760  1079 toggle private void finaliseStoringArgValue(String linkedId, ArgValues avs)
761    {
762  1079 Arg a = avs.arg();
763  1079 incrementCount(linkedId, avs);
764  1079 argIndex++;
765   
766    // store in appropriate place
767  1079 if (a.hasOption(Opt.LINKED))
768    {
769    // store the order of linkedIds
770  762 if (!linkedOrder.contains(linkedId))
771  236 linkedOrder.add(linkedId);
772    }
773   
774    // store arg in the list of args used
775  1079 if (!argList.contains(a))
776  720 argList.add(a);
777    }
778   
 
779  1178 toggle private String defaultLinkedId(boolean increment)
780    {
781  1178 String defaultLinkedId = new StringBuilder(DEFAULTLINKEDIDPREFIX)
782    .append(Integer.toString(defaultLinkedIdCounter)).toString();
783  1178 if (increment)
784    {
785  340 while (linkedArgs.containsKey(defaultLinkedId))
786    {
787  153 defaultLinkedIdCounter++;
788  153 defaultLinkedId = new StringBuilder(DEFAULTLINKEDIDPREFIX)
789    .append(Integer.toString(defaultLinkedIdCounter))
790    .toString();
791    }
792    }
793  1178 getOrCreateLinkedArgValuesMap(defaultLinkedId);
794  1178 return defaultLinkedId;
795    }
796   
 
797  836 toggle public String makeSubstitutions(String val, String linkedId)
798    {
799  836 return makeSubstitutions(val, linkedId, false);
800    }
801   
 
802  846 toggle public String makeSubstitutions(String val, String linkedId,
803    boolean onthefly)
804    {
805  846 if (!this.substitutions || val == null)
806  191 return val;
807   
808  655 String subvals;
809  655 String rest;
810  655 if (val.indexOf('[') == 0 && val.indexOf(']') > 1)
811    {
812  16 int closeBracket = val.indexOf(']');
813  16 if (val.length() == closeBracket)
814  0 return val;
815  16 subvals = val.substring(0, closeBracket + 1);
816  16 rest = val.substring(closeBracket + 1);
817    }
818    else
819    {
820  639 subvals = "";
821  639 rest = val;
822    }
823  655 if (rest.contains(LINKEDIDAUTOCOUNTER))
824    {
825  14 rest = rest.replace(LINKEDIDAUTOCOUNTER,
826    String.valueOf(linkedIdAutoCounter));
827    }
828  655 if (rest.contains(INCREMENTLINKEDIDAUTOCOUNTER))
829    {
830  0 rest = rest.replace(INCREMENTLINKEDIDAUTOCOUNTER,
831    String.valueOf(++linkedIdAutoCounter));
832    }
833  655 if (rest.contains(DEFAULTLINKEDIDCOUNTER))
834    {
835  0 rest = rest.replace(DEFAULTLINKEDIDCOUNTER,
836    String.valueOf(defaultLinkedIdCounter));
837    }
838  655 ArgValuesMap avm = linkedArgs.get(linkedId);
839  655 if (avm != null)
840    {
841  629 if (rest.contains(LINKEDIDBASENAME))
842    {
843  74 rest = rest.replace(LINKEDIDBASENAME, avm.getBasename());
844    }
845  629 if (rest.contains(LINKEDIDEXTENSION))
846    {
847  0 rest = rest.replace(LINKEDIDEXTENSION, avm.getExtension());
848    }
849  629 if (rest.contains(LINKEDIDDIRNAME))
850    {
851  86 rest = rest.replace(LINKEDIDDIRNAME, avm.getDirname());
852    }
853    }
854  655 if (argFile != null)
855    {
856  108 if (rest.contains(ARGFILEBASENAME))
857    {
858  0 rest = rest.replace(ARGFILEBASENAME,
859    FileUtils.getBasename(new File(argFile)));
860    }
861  108 if (rest.contains(ARGFILEDIRNAME))
862    {
863  14 rest = rest.replace(ARGFILEDIRNAME,
864    FileUtils.getDirname(new File(argFile)));
865    }
866    }
867  655 if (onthefly)
868    {
869  10 if (rest.contains(ONTHEFLYCOUNTER))
870    {
871  0 rest = rest.replace(ONTHEFLYCOUNTER,
872    String.valueOf(ontheflyCounter));
873    }
874  10 if (rest.contains(INCREMENTONTHEFLYCOUNTER))
875    {
876  0 rest = rest.replace(INCREMENTONTHEFLYCOUNTER,
877    String.valueOf(++ontheflyCounter));
878    }
879  10 if (currentStructureFilename != null)
880    {
881  10 if (rest.contains(STRUCTUREBASENAME))
882    {
883  0 rest = rest.replace(STRUCTUREBASENAME, FileUtils
884    .getBasename(new File(currentStructureFilename)));
885    }
886  10 if (rest.contains(STRUCTUREDIRNAME))
887    {
888  0 rest = rest.replace(STRUCTUREDIRNAME,
889    FileUtils.getDirname(new File(currentStructureFilename)));
890    }
891    }
892    }
893   
894  655 return new StringBuilder(subvals).append(rest).toString();
895    }
896   
897    /*
898    * A helper method to take a list of String args where we're expecting
899    * {"--previousargs", "--arg", "file1", "file2", "file3", "--otheroptionsornot"}
900    * and the index of the globbed arg, here 1. It returns a List<String> {"file1",
901    * "file2", "file3"} *and remove these from the original list object* so that
902    * processing can continue from where it has left off, e.g. args has become
903    * {"--previousargs", "--arg", "--otheroptionsornot"} so the next increment
904    * carries on from the next --arg if available.
905    */
 
906  71 toggle protected static List<String> getShellGlobbedFilenameValues(Arg a,
907    List<String> args, int i)
908    {
909  71 List<String> vals = new ArrayList<>();
910  138 while (i < args.size() && !args.get(i).startsWith(DOUBLEDASH))
911    {
912  88 vals.add(FileUtils.substituteHomeDir(args.remove(i)));
913  88 if (!a.hasOption(Opt.GLOB))
914  21 break;
915    }
916  71 return vals;
917    }
918   
 
919  285 toggle public BootstrapArgs getBootstrapArgs()
920    {
921  285 return bootstrapArgs;
922    }
923   
 
924  112 toggle public boolean isSet(Arg a)
925    {
926  112 return a.hasOption(Opt.LINKED) ? isSetAtAll(a) : isSet(null, a);
927    }
928   
 
929  23 toggle public boolean isSetAtAll(Arg a)
930    {
931  23 for (String linkedId : linkedOrder)
932    {
933  23 if (isSet(linkedId, a))
934  23 return true;
935    }
936  0 return false;
937    }
938   
 
939  112 toggle public boolean isSet(String linkedId, Arg a)
940    {
941  112 ArgValuesMap avm = linkedArgs.get(linkedId);
942  112 return avm == null ? false : avm.containsArg(a);
943    }
944   
 
945  176 toggle public boolean getBoolean(Arg a)
946    {
947  176 if (!a.hasOption(Opt.BOOLEAN) && !a.hasOption(Opt.UNARY))
948    {
949  0 Console.warn("Getting boolean from non boolean Arg '" + a.getName()
950    + "'.");
951    }
952  176 return a.hasOption(Opt.LINKED) ? getBool("", a) : getBool(null, a);
953    }
954   
 
955  176 toggle public boolean getBool(String linkedId, Arg a)
956    {
957  176 ArgValuesMap avm = linkedArgs.get(linkedId);
958  176 if (avm == null)
959  51 return a.getDefaultBoolValue();
960  125 ArgValues avs = avm.getArgValues(a);
961  125 return avs == null ? a.getDefaultBoolValue() : avs.getBoolean();
962    }
963   
 
964  312 toggle public List<String> getLinkedIds()
965    {
966  312 return linkedOrder;
967    }
968   
 
969  884 toggle public ArgValuesMap getLinkedArgs(String id)
970    {
971  884 return linkedArgs.get(id);
972    }
973   
 
974  0 toggle @Override
975    public String toString()
976    {
977  0 StringBuilder sb = new StringBuilder();
978  0 sb.append("UNLINKED\n");
979  0 sb.append(argValuesMapToString(linkedArgs.get(null)));
980  0 if (getLinkedIds() != null)
981    {
982  0 sb.append("LINKED\n");
983  0 for (String id : getLinkedIds())
984    {
985    // already listed these as UNLINKED args
986  0 if (id == null)
987  0 continue;
988   
989  0 ArgValuesMap avm = getLinkedArgs(id);
990  0 sb.append("ID: '").append(id).append("'\n");
991  0 sb.append(argValuesMapToString(avm));
992    }
993    }
994  0 return sb.toString();
995    }
996   
 
997  0 toggle private static String argValuesMapToString(ArgValuesMap avm)
998    {
999  0 if (avm == null)
1000  0 return null;
1001  0 StringBuilder sb = new StringBuilder();
1002  0 for (Arg a : avm.getArgKeys())
1003    {
1004  0 ArgValues v = avm.getArgValues(a);
1005  0 sb.append(v.toString());
1006  0 sb.append("\n");
1007    }
1008  0 return sb.toString();
1009    }
1010   
 
1011  8 toggle public static ArgParser parseArgFiles(List<String> argFilenameGlobs,
1012    boolean initsubstitutions, BootstrapArgs bsa)
1013    {
1014  8 List<File> argFiles = new ArrayList<>();
1015   
1016  8 for (String pattern : argFilenameGlobs)
1017    {
1018    // I don't think we want to dedup files, making life easier
1019  14 argFiles.addAll(FileUtils.getFilesFromGlob(pattern));
1020    }
1021   
1022  8 return parseArgFileList(argFiles, initsubstitutions, bsa);
1023    }
1024   
 
1025  8 toggle public static ArgParser parseArgFileList(List<File> argFiles,
1026    boolean initsubstitutions, BootstrapArgs bsa)
1027    {
1028  8 List<String> argsList = new ArrayList<>();
1029  8 for (File argFile : argFiles)
1030    {
1031  14 if (!argFile.exists())
1032    {
1033  0 String message = Arg.ARGFILE.argString() + EQUALS + "\""
1034    + argFile.getPath() + "\": File does not exist.";
1035  0 Jalview.exit(message, ExitCode.FILE_NOT_FOUND);
1036    }
1037  14 try
1038    {
1039  14 String setargfile = new StringBuilder(Arg.SETARGFILE.argString())
1040    .append(EQUALS).append(argFile.getCanonicalPath())
1041    .toString();
1042  14 argsList.add(setargfile);
1043  14 argsList.addAll(readArgFile(argFile));
1044  14 argsList.add(Arg.UNSETARGFILE.argString());
1045    } catch (IOException e)
1046    {
1047  0 String message = Arg.ARGFILE.argString() + "=\"" + argFile.getPath()
1048    + "\": File could not be read.";
1049  0 Jalview.exit(message, ExitCode.FILE_NOT_READABLE);
1050    }
1051    }
1052    // Third param "true" uses Opt.PRIVATE args --setargile=argfile and
1053    // --unsetargfile
1054  8 return new ArgParser(argsList, initsubstitutions, true, bsa);
1055    }
1056   
 
1057  72 toggle protected static List<String> readArgFile(File argFile)
1058    {
1059  72 List<String> args = new ArrayList<>();
1060  72 if (argFile != null && argFile.exists())
1061    {
1062  72 try
1063    {
1064  72 for (String line : Files.readAllLines(Paths.get(argFile.getPath())))
1065    {
1066  328 if (line != null && line.length() > 0
1067    && line.charAt(0) != ARGFILECOMMENT)
1068  324 args.add(line);
1069    }
1070    } catch (IOException e)
1071    {
1072  0 String message = Arg.ARGFILE.argString() + "=\"" + argFile.getPath()
1073    + "\": File could not be read.";
1074  0 Console.debug(message, e);
1075  0 Jalview.exit(message, ExitCode.FILE_NOT_READABLE);
1076    }
1077    }
1078  72 return args;
1079    }
1080   
1081    // the following methods look for the "*" linkedId and add the argvalue to all
1082    // linkedId ArgValues if it does.
1083    /**
1084    * This version inserts the subvals sv into all created values
1085    */
 
1086  638 toggle private void addValue(String linkedId, String givenLinkedId, Type type,
1087    ArgValues avs, SubVals sv, String v, int argIndex, boolean doSubs)
1088    {
1089  638 this.argValueOperation(Op.ADDVALUE, linkedId, givenLinkedId, type, avs,
1090    sv, v, false, argIndex, doSubs);
1091    }
1092   
 
1093  335 toggle private void setBoolean(String linkedId, String givenLinkedId, Type type,
1094    ArgValues avs, boolean b, int argIndex)
1095    {
1096  335 this.argValueOperation(Op.SETBOOLEAN, linkedId, givenLinkedId, type,
1097    avs, null, null, b, argIndex, false);
1098    }
1099   
 
1100  189 toggle private void setNegated(String linkedId, ArgValues avs, boolean b)
1101    {
1102  189 this.argValueOperation(Op.SETNEGATED, linkedId, null, null, avs, null,
1103    null, b, 0, false);
1104    }
1105   
 
1106  1079 toggle private void incrementCount(String linkedId, ArgValues avs)
1107    {
1108  1079 this.argValueOperation(Op.INCREMENTCOUNT, linkedId, null, null, avs,
1109    null, null, false, 0, false);
1110    }
1111   
 
1112    private enum Op
1113    {
1114    ADDVALUE, SETBOOLEAN, SETNEGATED, INCREMENTCOUNT
1115    }
1116   
 
1117  2241 toggle private void argValueOperation(Op op, String linkedId,
1118    String givenLinkedId, Type type, ArgValues avs, SubVals sv,
1119    String v, boolean b, int argIndex, boolean doSubs)
1120    {
1121    // default to merge subvals if subvals are provided
1122  2241 argValueOperation(op, linkedId, givenLinkedId, type, avs, sv, true, v,
1123    b, argIndex, doSubs);
1124    }
1125   
1126    /**
1127    * The following operations look for the "*" and "open*" linkedIds and add the
1128    * argvalue to all appropriate linkedId ArgValues if it does. If subvals are
1129    * supplied, they are inserted into all new set values.
1130    *
1131    * @param op
1132    * The ArgParser.Op operation
1133    * @param linkedId
1134    * The String linkedId from the ArgValuesMap
1135    * @param type
1136    * The Arg.Type to attach to this ArgValue
1137    * @param avs
1138    * The ArgValues for this linkedId
1139    * @param sv
1140    * Use these SubVals on the ArgValue
1141    * @param merge
1142    * Merge the SubVals with any existing on the value. False will
1143    * replace unless sv is null
1144    * @param v
1145    * The value of the ArgValue (may contain subvals).
1146    * @param b
1147    * The boolean value of the ArgValue.
1148    * @param argIndex
1149    * The argIndex for the ArgValue.
1150    * @param doSubs
1151    * Whether to perform substitutions on the subvals and value.
1152    */
 
1153  2241 toggle private void argValueOperation(Op op, String linkedId,
1154    String givenLinkedId, Type type, ArgValues avs, SubVals sv,
1155    boolean merge, String v, boolean b, int argIndex, boolean doSubs)
1156    {
1157  2241 Arg a = avs.arg();
1158   
1159  2241 List<String> wildcardLinkedIds = null;
1160  2241 if (a.hasOption(Opt.ALLOWMULTIID))
1161    {
1162  962 switch (linkedId)
1163    {
1164  23 case MATCHALLLINKEDIDS:
1165  23 wildcardLinkedIds = getLinkedIds();
1166  23 break;
1167  306 case MATCHOPENEDLINKEDIDS:
1168  306 wildcardLinkedIds = this.storedLinkedIds;
1169  306 break;
1170    }
1171    }
1172   
1173    // if we're not a wildcard linkedId and the arg is marked to be stored, add
1174    // to storedLinkedIds
1175  2241 if (linkedId != null && wildcardLinkedIds == null
1176    && a.hasOption(Opt.STORED)
1177    && !storedLinkedIds.contains(linkedId))
1178    {
1179  170 storedLinkedIds.add(linkedId);
1180    }
1181   
1182    // if we are a wildcard linkedId, apply the arg and value to all appropriate
1183    // linkedIds
1184  2241 if (wildcardLinkedIds != null)
1185    {
1186  329 for (String matchedLinkedId : wildcardLinkedIds)
1187    {
1188    // skip incorrectly stored wildcard ids!
1189  435 if (matchedLinkedId == null
1190    || MATCHALLLINKEDIDS.equals(matchedLinkedId)
1191    || MATCHOPENEDLINKEDIDS.equals(matchedLinkedId))
1192    {
1193  0 continue;
1194    }
1195  435 ArgValuesMap avm = linkedArgs.get(matchedLinkedId);
1196    // don't set an output if there isn't an input
1197  435 if (a.hasOption(Opt.REQUIREINPUT)
1198    && !avm.hasArgWithOption(Opt.INPUT))
1199  0 continue;
1200   
1201  435 ArgValues tavs = avm.getOrCreateArgValues(a);
1202  435 switch (op)
1203    {
1204   
1205  296 case ADDVALUE:
1206  296 String val = v;
1207  296 if (sv != null)
1208    {
1209  1 if (doSubs)
1210    {
1211  1 sv = new SubVals(sv, val, merge);
1212  1 val = makeSubstitutions(sv.getContent(), matchedLinkedId);
1213    }
1214  1 tavs.addValue(sv, type, val, argIndex, true, givenLinkedId);
1215    }
1216    else
1217    {
1218  295 if (doSubs)
1219    {
1220  295 val = makeSubstitutions(v, matchedLinkedId);
1221    }
1222  295 tavs.addValue(type, val, argIndex, true, givenLinkedId);
1223    }
1224  296 finaliseStoringArgValue(matchedLinkedId, tavs);
1225  296 break;
1226   
1227  103 case SETBOOLEAN:
1228  103 tavs.setBoolean(type, b, argIndex, true, givenLinkedId);
1229  103 finaliseStoringArgValue(matchedLinkedId, tavs);
1230  103 break;
1231   
1232  36 case SETNEGATED:
1233  36 tavs.setNegated(b, true);
1234  36 break;
1235   
1236  0 case INCREMENTCOUNT:
1237  0 tavs.incrementCount();
1238  0 break;
1239   
1240  0 default:
1241  0 break;
1242   
1243    }
1244   
1245    }
1246    }
1247    else // no wildcard linkedId -- do it simpler
1248    {
1249  1912 switch (op)
1250    {
1251  396 case ADDVALUE:
1252  396 String val = v;
1253  396 if (sv != null)
1254    {
1255  257 if (doSubs)
1256    {
1257  257 val = makeSubstitutions(v, linkedId);
1258  257 sv = new SubVals(sv, val);
1259    }
1260  257 avs.addValue(sv, type, val, argIndex, false, givenLinkedId);
1261    }
1262    else
1263    {
1264  139 if (doSubs)
1265    {
1266  139 val = makeSubstitutions(v, linkedId);
1267    }
1268  139 avs.addValue(type, val, argIndex, false, givenLinkedId);
1269    }
1270  396 finaliseStoringArgValue(linkedId, avs);
1271  396 break;
1272   
1273  284 case SETBOOLEAN:
1274  284 avs.setBoolean(type, b, argIndex, false, givenLinkedId);
1275  284 finaliseStoringArgValue(linkedId, avs);
1276  284 break;
1277   
1278  153 case SETNEGATED:
1279  153 avs.setNegated(b, false);
1280  153 break;
1281   
1282  1079 case INCREMENTCOUNT:
1283  1079 avs.incrementCount();
1284  1079 break;
1285   
1286  0 default:
1287  0 break;
1288    }
1289    }
1290    }
1291   
 
1292  2071 toggle private ArgValuesMap getOrCreateLinkedArgValuesMap(String linkedId)
1293    {
1294  2071 if (linkedArgs.containsKey(linkedId)
1295    && linkedArgs.get(linkedId) != null)
1296  1308 return linkedArgs.get(linkedId);
1297   
1298  763 linkedArgs.put(linkedId, new ArgValuesMap(linkedId));
1299  763 return linkedArgs.get(linkedId);
1300    }
1301   
 
1302  171 toggle public boolean isOldStyle()
1303    {
1304  171 return oldArguments;
1305    }
1306   
 
1307  144 toggle public boolean isMixedStyle()
1308    {
1309  144 return mixedArguments;
1310    }
1311   
 
1312  1 toggle public String[] getMixedExamples()
1313    {
1314  1 return mixedExamples;
1315    }
1316   
 
1317  168 toggle public void setStructureFilename(String s)
1318    {
1319  168 this.currentStructureFilename = s;
1320    }
1321   
1322    }