2 * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
3 * Copyright (C) $$Year-Rel$$ The Jalview Authors
5 * This file is part of Jalview.
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.
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.
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.
23 import java.net.URLDecoder;
24 import java.util.ArrayList;
25 import java.util.Arrays;
26 import java.util.Collections;
27 import java.util.EnumSet;
28 import java.util.Enumeration;
29 import java.util.HashMap;
30 import java.util.List;
31 import java.util.Locale;
34 import jalview.util.Platform;
36 public class ArgParser
38 private static final String NEGATESTRING = "no";
40 private static final String DEFAULTLINKEDID = "";
42 private static enum Opt
44 BOOLEAN, STRING, UNARY, MULTI, LINKED, ORDERED
50 NOCALCULATION, NOMENUBAR, NOSTATUS, SHOWOVERVIEW, ANNOTATIONS, COLOUR,
51 FEATURES, GROOVY, GROUPS, HEADLESS, JABAWS, NOANNOTATION, NOANNOTATION2,
52 NODISPLAY, NOGUI, NONEWS, NOQUESTIONNAIRE, NOSORTBYTREE, NOUSAGESTATS,
53 OPEN, OPEN2, PROPS, QUESTIONNAIRE, SETPROP, SORTBYTREE, TREE, VDOC,
56 HELP("h"), CALCULATION, MENUBAR, STATUS, SHOWOVERVIEW, ANNOTATIONS,
57 COLOUR, FEATURES, GROOVY, GROUPS, HEADLESS, JABAWS, ANNOTATION,
58 ANNOTATION2, DISPLAY, GUI, NEWS, NOQUESTIONNAIRE, SORTBYTREE,
59 USAGESTATS, OPEN, OPEN2, PROPS, QUESTIONNAIRE, SETPROP, TREE, VDOC,
60 VSESS, OUTPUT, OUTPUTTYPE, SSANNOTATION, NOTEMPFAC, TEMPFAC,
61 TEMPFAC_LABEL, TEMPFAC_DESC, TEMPFAC_SHADING, TITLE, PAEMATRIX, WRAP,
62 NOSTRUCTURE, STRUCTURE;
66 HELP.setOptions(Opt.UNARY);
67 CALCULATION.setOptions(true, Opt.BOOLEAN); // default "true" implies only
68 // expecting "--nocalculation"
69 MENUBAR.setOptions(true, Opt.BOOLEAN);
70 STATUS.setOptions(true, Opt.BOOLEAN);
71 SHOWOVERVIEW.setOptions(Opt.UNARY, Opt.LINKED);
72 ANNOTATIONS.setOptions(Opt.STRING, Opt.LINKED);
73 COLOUR.setOptions(Opt.STRING, Opt.LINKED);
74 FEATURES.setOptions(Opt.STRING, Opt.LINKED, Opt.MULTI);
75 GROOVY.setOptions(Opt.STRING, Opt.LINKED, Opt.MULTI);
76 GROUPS.setOptions(Opt.STRING, Opt.LINKED);
77 HEADLESS.setOptions(Opt.UNARY);
78 JABAWS.setOptions(Opt.STRING);
79 ANNOTATION.setOptions(true, Opt.BOOLEAN);
80 ANNOTATION2.setOptions(true, Opt.BOOLEAN);
81 DISPLAY.setOptions(true, Opt.BOOLEAN);
82 GUI.setOptions(true, Opt.BOOLEAN);
83 NEWS.setOptions(true, Opt.BOOLEAN);
84 NOQUESTIONNAIRE.setOptions(Opt.UNARY); // unary as --questionnaire=val
85 // expects a string value
86 SORTBYTREE.setOptions(true, Opt.BOOLEAN);
87 USAGESTATS.setOptions(true, Opt.BOOLEAN);
88 OPEN.setOptions(Opt.STRING, Opt.LINKED, Opt.MULTI);
89 OPEN2.setOptions(Opt.STRING, Opt.LINKED);
90 PROPS.setOptions(Opt.STRING);
91 QUESTIONNAIRE.setOptions(Opt.STRING);
92 SETPROP.setOptions(Opt.STRING);
93 TREE.setOptions(Opt.STRING);
95 VDOC.setOptions(Opt.UNARY);
96 VSESS.setOptions(Opt.UNARY);
98 OUTPUT.setOptions(Opt.STRING, Opt.LINKED);
99 OUTPUTTYPE.setOptions(Opt.STRING, Opt.LINKED, Opt.MULTI);
101 SSANNOTATION.setOptions(Opt.BOOLEAN, Opt.LINKED);
102 NOTEMPFAC.setOptions(Opt.UNARY, Opt.LINKED);
103 TEMPFAC.setOptions(Opt.STRING, Opt.LINKED);
104 TEMPFAC_LABEL.setOptions(Opt.STRING, Opt.LINKED);
105 TEMPFAC_DESC.setOptions(Opt.STRING, Opt.LINKED);
106 TEMPFAC_SHADING.setOptions(Opt.STRING, Opt.LINKED);
107 TITLE.setOptions(Opt.STRING, Opt.LINKED);
108 PAEMATRIX.setOptions(Opt.STRING, Opt.LINKED, Opt.MULTI);
109 NOSTRUCTURE.setOptions(Opt.UNARY, Opt.LINKED);
110 STRUCTURE.setOptions(Opt.STRING, Opt.LINKED, Opt.MULTI);
111 WRAP.setOptions(Opt.BOOLEAN, Opt.LINKED);
114 private final String[] argNames;
116 private Opt[] argOptions;
118 private boolean defaultBoolValue = false;
120 public String toLongString()
122 StringBuilder sb = new StringBuilder();
123 sb.append("Arg: ").append(this.name());
124 for (String name : getNames())
126 sb.append(", '").append(name).append("'");
128 sb.append("\nOptions: ");
129 boolean first = true;
130 for (Opt o : argOptions)
136 sb.append(o.toString());
140 return sb.toString();
148 private Arg(String... names)
150 int length = (names == null || names.length == 0
151 || (names.length == 1 && names[0] == null)) ? 1
153 this.argNames = new String[length];
154 this.argNames[0] = this.getName();
156 System.arraycopy(names, 0, this.argNames, 1, names.length);
159 public String[] getNames()
164 public String getName()
166 return this.name().toLowerCase(Locale.ROOT).replace('_', '-');
170 public final String toString()
175 public boolean hasOption(Opt o)
177 if (argOptions == null)
179 for (Opt option : argOptions)
187 protected void setOptions(Opt... options)
189 setOptions(false, options);
192 protected void setOptions(boolean defaultBoolValue, Opt... options)
194 this.defaultBoolValue = defaultBoolValue;
195 argOptions = options;
198 protected boolean getDefaultBoolValue()
200 return defaultBoolValue;
204 public static class ArgValues
208 private int argCount = 0;
210 private boolean boolValue = false;
212 private boolean negated = false;
214 private List<String> argsList;
216 protected ArgValues(Arg a)
219 this.argsList = new ArrayList<String>();
220 this.boolValue = arg.getDefaultBoolValue();
228 protected int getCount()
233 protected void incrementCount()
238 protected void setNegated(boolean b)
243 protected boolean isNegated()
248 protected void setBoolean(boolean b)
253 protected boolean getBoolean()
255 return this.boolValue;
259 public String toString()
261 if (argsList == null)
263 StringBuilder sb = new StringBuilder();
264 sb.append(arg.toLongString());
265 if (arg.hasOption(Opt.BOOLEAN) || arg.hasOption(Opt.UNARY))
266 sb.append("Boolean: ").append(boolValue).append("; Default: ")
267 .append(arg.getDefaultBoolValue()).append("; Negated: ")
268 .append(negated).append("\n");
269 if (arg.hasOption(Opt.STRING))
271 sb.append("Values:");
272 boolean first = true;
273 for (String v : argsList)
278 sb.append(v).append("'");
283 sb.append("Count: ").append(argCount).append("\n");
284 return sb.toString();
287 protected void addValue()
292 protected void addValue(String val)
294 addValue(val, false);
297 protected void addValue(String val, boolean noDuplicates)
299 if ((!arg.hasOption(Opt.MULTI) && argsList.size() > 0)
300 || (noDuplicates && argsList.contains(val)))
302 if (argsList == null)
304 Console.warn("** inst");
305 argsList = new ArrayList<String>();
310 protected boolean hasValue(String val)
312 return argsList.contains(val);
315 protected String getValue()
317 if (arg.hasOption(Opt.MULTI))
318 Console.warn("Requesting single value for multi value argument");
319 return argsList.size() > 0 ? argsList.get(0) : null;
322 protected List<String> getValues()
329 private List<String> vargs = null;
331 private boolean isApplet;
333 // private AppletParams appletParams;
335 public boolean isApplet()
340 public String nextValue()
342 return vargs.remove(0);
350 public String getValue(String arg)
352 return getValue(arg, false);
355 public String getValue(String arg, boolean utf8decode)
357 int index = vargs.indexOf(arg);
358 String dc = null, ret = null;
361 ret = vargs.get(index + 1).toString();
364 if (utf8decode && ret != null)
368 dc = URLDecoder.decode(ret, "UTF-8");
370 } catch (Exception e)
372 // TODO: log failure to decode
380 public Object getAppletValue(String key, String def, boolean asString)
383 return (appletParams == null ? null
384 : (value = appletParams.get(key.toLowerCase())) == null ? def
385 : asString ? "" + value : value);
390 private static final Map<String, Arg> argMap;
392 private Map<String, HashMap<Arg, ArgValues>> linkedArgs = new HashMap<>();
394 private List<String> linkedOrder = null;
396 private List<Arg> argList;
400 argMap = new HashMap<>();
401 for (Arg a : EnumSet.allOf(Arg.class))
403 ARGNAME: for (String argName : a.getNames())
405 if (argMap.containsKey(argName))
407 Console.warn("Trying to add argument name multiple times: '"
408 + argName + "'"); // RESTORE THIS WHEN MERGED
409 if (argMap.get(argName) != a)
412 "Trying to add argument name multiple times for different Args: '"
413 + argMap.get(argName).getName() + ":" + argName
414 + "' and '" + a.getName() + ":" + argName
419 argMap.put(argName, a);
424 public ArgParser(String[] args)
427 vargs = new ArrayList<>();
428 isApplet = (args.length > 0 && args[0].startsWith("<applet"));
431 // appletParams = AppletParams.getAppletParams(args, vargs);
440 // AppletParams.getAppletParams(Platform.getAppletInfoAsMap(), vargs);
442 for (int i = 0; i < args.length; i++)
444 String arg = args[i].trim();
445 if (arg.charAt(0) == '-')
447 arg = arg.substring(1);
454 Enumeration<String> argE = Collections.enumeration(Arrays.asList(args));
455 ARG: while (argE.hasMoreElements())
457 String arg = argE.nextElement();
458 String argName = null;
460 String linkedId = null;
461 if (arg.startsWith("--"))
463 int equalPos = arg.indexOf('=');
466 argName = arg.substring(2, equalPos);
467 val = arg.substring(equalPos + 1);
471 argName = arg.substring(2);
473 int idOpen = argName.indexOf('[');
474 int idClose = argName.indexOf(']');
476 if (idOpen > -1 && idClose == argName.length() - 1)
478 linkedId = argName.substring(idOpen + 1, idClose);
479 argName = argName.substring(0, idOpen);
482 Arg a = argMap.get(argName);
483 // check for boolean prepended by "no"
484 boolean negated = false;
485 if (a == null && argName.startsWith(NEGATESTRING) && argMap
486 .containsKey(argName.substring(NEGATESTRING.length())))
488 argName = argName.substring(NEGATESTRING.length());
489 a = argMap.get(argName);
493 // check for config errors
497 Console.error("Argument '" + arg + "' not recognised. Ignoring.");
500 if (!a.hasOption(Opt.BOOLEAN) && negated)
502 // used "no" with a non-boolean option
503 Console.error("Argument '--" + NEGATESTRING + argName
504 + "' not a boolean option. Ignoring.");
507 if (!a.hasOption(Opt.STRING) && equalPos > -1)
509 // set --argname=value when arg does not accept values
510 Console.error("Argument '--" + argName
511 + "' does not expect a value (given as '" + arg
515 if (!a.hasOption(Opt.LINKED) && linkedId != null)
517 // set --argname[linkedId] when arg does not use linkedIds
518 Console.error("Argument '--" + argName
519 + "' does not expect a linked id (given as '" + arg
524 if (a.hasOption(Opt.STRING) && equalPos == -1)
526 // take next arg as value if required, and '=' was not found
527 if (!argE.hasMoreElements())
529 // no value to take for arg, which wants a value
530 Console.error("Argument '" + a.getName()
531 + "' requires a value, none given. Ignoring.");
534 val = argE.nextElement();
537 // use default linkedId for linked arguments
538 if (a.hasOption(Opt.LINKED) && linkedId == null)
539 linkedId = DEFAULTLINKEDID;
541 if (!linkedArgs.containsKey(linkedId))
542 linkedArgs.put(linkedId, new HashMap<>());
544 Map<Arg, ArgValues> valuesMap = linkedArgs.get(linkedId);
545 if (!valuesMap.containsKey(a))
546 valuesMap.put(a, new ArgValues(a));
548 ArgValues values = valuesMap.get(a);
551 values = new ArgValues(a);
553 // store appropriate value
554 if (a.hasOption(Opt.STRING))
556 values.addValue(val);
558 else if (a.hasOption(Opt.BOOLEAN))
560 values.setBoolean(!negated);
561 values.setNegated(negated);
563 else if (a.hasOption(Opt.UNARY))
565 values.setBoolean(true);
567 values.incrementCount();
569 // store in appropriate place
570 if (a.hasOption(Opt.LINKED))
572 // allow a default linked id for single usage
573 if (linkedId == null)
574 linkedId = DEFAULTLINKEDID;
575 // store the order of linkedIds
576 if (linkedOrder == null)
577 linkedOrder = new ArrayList<>();
578 if (!linkedOrder.contains(linkedId))
579 linkedOrder.add(linkedId);
581 // store the ArgValues
582 valuesMap.put(a, values);
584 // store arg in the list of args
586 argList = new ArrayList<>();
587 if (!argList.contains(a))
593 public boolean isSet(Arg a)
595 return a.hasOption(Opt.LINKED) ? isSet("", a) : isSet(null, a);
598 public boolean isSet(String linkedId, Arg a)
600 Map<Arg, ArgValues> m = linkedArgs.get(linkedId);
601 return m == null ? false : m.containsKey(a);
604 public boolean getBool(Arg a)
606 if (!a.hasOption(Opt.BOOLEAN))
608 Console.warn("Getting boolean from non boolean Arg '" + a.getName()
611 return a.hasOption(Opt.LINKED) ? getBool("", a) : getBool(null, a);
614 public boolean getBool(String linkedId, Arg a)
616 Map<Arg, ArgValues> m = linkedArgs.get(linkedId);
618 return a.getDefaultBoolValue();
619 ArgValues v = m.get(a);
620 return v == null ? a.getDefaultBoolValue() : v.getBoolean();
623 public List<String> linkedIds()
628 public HashMap<Arg, ArgValues> linkedArgs(String id)
630 return linkedArgs.get(id);
634 public String toString()
636 StringBuilder sb = new StringBuilder();
637 sb.append("UNLINKED\n");
638 sb.append(argMapToString(linkedArgs.get(null)));
639 if (linkedIds() != null)
641 sb.append("LINKED\n");
642 for (String id : linkedIds())
644 // already listed these as UNLINKED args
648 Map<Arg, ArgValues> m = linkedArgs(id);
649 sb.append("ID: '").append(id).append("'\n");
650 sb.append(argMapToString(m));
653 return sb.toString();
656 private static String argMapToString(Map<Arg, ArgValues> m)
660 StringBuilder sb = new StringBuilder();
661 for (Arg a : m.keySet())
663 ArgValues v = m.get(a);
664 sb.append(v.toString());
667 return sb.toString();
670 public static SubId getSubId(String item)
672 return new SubId(item);
676 * A helper class to parse a string of the possible forms "content"
677 * "[index]content", "[keyName=keyValue]content" and return the integer index,
678 * the strings keyName and keyValue, and the content after the square brackets
679 * (if present). Values not set `will be -1 or null.
681 public static class SubId
683 protected int index = 0;
685 protected String keyName = null;
687 protected String keyValue = null;
689 protected String content = null;
695 public SubId(String item)
700 public void parseVal(String item)
702 if (item.indexOf('[') == 0 && item.indexOf(']') > 1)
704 int openBracket = item.indexOf('[');
705 int closeBracket = item.indexOf(']');
706 String indexString = item.substring(openBracket + 1, closeBracket);
707 this.content = item.substring(closeBracket + 1);
708 int equals = indexString.indexOf('=');
711 this.keyName = indexString.substring(0, equals);
712 this.keyValue = indexString.substring(equals + 1);
719 this.index = Integer.parseInt(indexString);
720 } catch (NumberFormatException e)
722 Console.warn("Failed to obtain sequenced id or index from '"
723 + item + "'. Setting index=0 and using content='"