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.Collection;
27 import java.util.Collections;
28 import java.util.EnumSet;
29 import java.util.Enumeration;
30 import java.util.HashMap;
31 import java.util.List;
32 import java.util.Locale;
35 import jalview.util.Platform;
37 public class ArgParser
39 private static final String NEGATESTRING = "no";
41 private static final String DEFAULTLINKEDID = "";
43 private static enum Opt
45 BOOLEAN, STRING, UNARY, MULTI, LINKED, ORDERED
51 NOCALCULATION, NOMENUBAR, NOSTATUS, SHOWOVERVIEW, ANNOTATIONS, COLOUR,
52 FEATURES, GROOVY, GROUPS, HEADLESS, JABAWS, NOANNOTATION, NOANNOTATION2,
53 NODISPLAY, NOGUI, NONEWS, NOQUESTIONNAIRE, NOSORTBYTREE, NOUSAGESTATS,
54 OPEN, OPEN2, PROPS, QUESTIONNAIRE, SETPROP, SORTBYTREE, TREE, VDOC,
57 HELP("h"), CALCULATION, MENUBAR, STATUS, SHOWOVERVIEW, ANNOTATIONS,
58 COLOUR, FEATURES, GROOVY, GROUPS, HEADLESS, JABAWS, ANNOTATION,
59 ANNOTATION2, DISPLAY, GUI, NEWS, NOQUESTIONNAIRE, SORTBYTREE,
60 USAGESTATS, OPEN, OPEN2, PROPS, QUESTIONNAIRE, SETPROP, TREE, VDOC,
61 VSESS, OUTPUT, OUTPUTTYPE, SSANNOTATION, NOTEMPFAC, TEMPFAC,
62 TEMPFAC_LABEL, TEMPFAC_DESC, TEMPFAC_SHADING, TITLE, PAEMATRIX, WRAP,
63 NOSTRUCTURE, STRUCTURE, IMAGE, QUIT, DEBUG("d");
67 HELP.setOptions(Opt.UNARY);
68 CALCULATION.setOptions(true, Opt.BOOLEAN); // default "true" implies only
69 // expecting "--nocalculation"
70 MENUBAR.setOptions(true, Opt.BOOLEAN);
71 STATUS.setOptions(true, Opt.BOOLEAN);
72 SHOWOVERVIEW.setOptions(Opt.UNARY, Opt.LINKED);
73 ANNOTATIONS.setOptions(Opt.STRING, Opt.LINKED);
74 COLOUR.setOptions(Opt.STRING, Opt.LINKED);
75 FEATURES.setOptions(Opt.STRING, Opt.LINKED, Opt.MULTI);
76 GROOVY.setOptions(Opt.STRING, Opt.LINKED, Opt.MULTI);
77 GROUPS.setOptions(Opt.STRING, Opt.LINKED);
78 HEADLESS.setOptions(Opt.UNARY);
79 JABAWS.setOptions(Opt.STRING);
80 ANNOTATION.setOptions(true, Opt.BOOLEAN);
81 ANNOTATION2.setOptions(true, Opt.BOOLEAN);
82 DISPLAY.setOptions(true, Opt.BOOLEAN);
83 GUI.setOptions(true, Opt.BOOLEAN);
84 NEWS.setOptions(true, Opt.BOOLEAN);
85 NOQUESTIONNAIRE.setOptions(Opt.UNARY); // unary as --questionnaire=val
86 // expects a string value
87 SORTBYTREE.setOptions(true, Opt.BOOLEAN);
88 USAGESTATS.setOptions(true, Opt.BOOLEAN);
89 OPEN.setOptions(Opt.STRING, Opt.LINKED, Opt.MULTI);
90 OPEN2.setOptions(Opt.STRING, Opt.LINKED);
91 PROPS.setOptions(Opt.STRING);
92 QUESTIONNAIRE.setOptions(Opt.STRING);
93 SETPROP.setOptions(Opt.STRING);
94 TREE.setOptions(Opt.STRING);
96 VDOC.setOptions(Opt.UNARY);
97 VSESS.setOptions(Opt.UNARY);
99 OUTPUT.setOptions(Opt.STRING, Opt.LINKED);
100 OUTPUTTYPE.setOptions(Opt.STRING, Opt.LINKED, Opt.MULTI);
102 SSANNOTATION.setOptions(Opt.BOOLEAN, Opt.LINKED);
103 NOTEMPFAC.setOptions(Opt.UNARY, Opt.LINKED);
104 TEMPFAC.setOptions(Opt.STRING, Opt.LINKED);
105 TEMPFAC_LABEL.setOptions(Opt.STRING, Opt.LINKED);
106 TEMPFAC_DESC.setOptions(Opt.STRING, Opt.LINKED);
107 TEMPFAC_SHADING.setOptions(Opt.BOOLEAN, Opt.LINKED);
108 TITLE.setOptions(Opt.STRING, Opt.LINKED);
109 PAEMATRIX.setOptions(Opt.STRING, Opt.LINKED, Opt.MULTI);
110 NOSTRUCTURE.setOptions(Opt.UNARY, Opt.LINKED);
111 STRUCTURE.setOptions(Opt.STRING, Opt.LINKED, Opt.MULTI);
112 WRAP.setOptions(Opt.BOOLEAN, Opt.LINKED);
113 IMAGE.setOptions(Opt.STRING, Opt.LINKED);
114 QUIT.setOptions(Opt.UNARY);
115 DEBUG.setOptions(Opt.BOOLEAN);
118 private final String[] argNames;
120 private Opt[] argOptions;
122 private boolean defaultBoolValue = false;
124 public String toLongString()
126 StringBuilder sb = new StringBuilder();
127 sb.append("Arg: ").append(this.name());
128 for (String name : getNames())
130 sb.append(", '").append(name).append("'");
132 sb.append("\nOptions: ");
133 boolean first = true;
134 for (Opt o : argOptions)
140 sb.append(o.toString());
144 return sb.toString();
152 private Arg(String... names)
154 int length = (names == null || names.length == 0
155 || (names.length == 1 && names[0] == null)) ? 1
157 this.argNames = new String[length];
158 this.argNames[0] = this.getName();
160 System.arraycopy(names, 0, this.argNames, 1, names.length);
163 public String[] getNames()
168 public String getName()
170 return this.name().toLowerCase(Locale.ROOT).replace('_', '-');
174 public final String toString()
179 public boolean hasOption(Opt o)
181 if (argOptions == null)
183 for (Opt option : argOptions)
191 protected void setOptions(Opt... options)
193 setOptions(false, options);
196 protected void setOptions(boolean defaultBoolValue, Opt... options)
198 this.defaultBoolValue = defaultBoolValue;
199 argOptions = options;
202 protected boolean getDefaultBoolValue()
204 return defaultBoolValue;
208 public static class ArgValues
212 private int argCount = 0;
214 private boolean boolValue = false;
216 private boolean negated = false;
218 private int singleArgIndex = -1;
220 private List<Integer> argsIndexes;
222 private List<ArgValue> argsList;
224 protected ArgValues(Arg a)
227 this.argsList = new ArrayList<ArgValue>();
228 this.boolValue = arg.getDefaultBoolValue();
236 protected int getCount()
241 protected void incrementCount()
246 protected void setNegated(boolean b)
251 protected boolean isNegated()
256 protected void setBoolean(boolean b)
261 protected boolean getBoolean()
263 return this.boolValue;
267 public String toString()
269 if (argsList == null)
271 StringBuilder sb = new StringBuilder();
272 sb.append(arg.toLongString());
273 if (arg.hasOption(Opt.BOOLEAN) || arg.hasOption(Opt.UNARY))
274 sb.append("Boolean: ").append(boolValue).append("; Default: ")
275 .append(arg.getDefaultBoolValue()).append("; Negated: ")
276 .append(negated).append("\n");
277 if (arg.hasOption(Opt.STRING))
279 sb.append("Values:");
280 boolean first = true;
281 for (ArgValue av : argsList)
283 String v = av.getValue();
287 sb.append(v).append("'");
292 sb.append("Count: ").append(argCount).append("\n");
293 return sb.toString();
296 protected void addValue()
301 protected void addValue(String val, int argIndex)
303 addValue(val, argIndex, false);
306 protected void addValue(String val, int argIndex, boolean noDuplicates)
308 if ((!arg.hasOption(Opt.MULTI) && argsList.size() > 0)
309 || (noDuplicates && argsList.contains(val)))
311 if (argsList == null)
313 argsList = new ArrayList<ArgValue>();
315 argsList.add(new ArgValue(val, argIndex));
318 protected boolean hasValue(String val)
320 return argsList.contains(val);
323 protected ArgValue getArgValue()
325 if (arg.hasOption(Opt.MULTI))
326 Console.warn("Requesting single value for multi value argument");
327 return argsList.size() > 0 ? argsList.get(0) : null;
331 protected String getValue()
333 ArgValue av = getArgValue();
334 return av == null ? null : av.getValue();
338 protected List<ArgValue> getArgValueList()
345 private List<String> vargs = null;
347 private boolean isApplet;
349 // private AppletParams appletParams;
351 public boolean isApplet()
356 public String nextValue()
358 return vargs.remove(0);
366 public String getValue(String arg)
368 return getValue(arg, false);
371 public String getValue(String arg, boolean utf8decode)
373 int index = vargs.indexOf(arg);
374 String dc = null, ret = null;
377 ret = vargs.get(index + 1).toString();
380 if (utf8decode && ret != null)
384 dc = URLDecoder.decode(ret, "UTF-8");
386 } catch (Exception e)
388 // TODO: log failure to decode
396 public Object getAppletValue(String key, String def, boolean asString)
399 return (appletParams == null ? null
400 : (value = appletParams.get(key.toLowerCase())) == null ? def
401 : asString ? "" + value : value);
406 private static final Map<String, Arg> argMap;
408 private Map<String, HashMap<Arg, ArgValues>> linkedArgs = new HashMap<>();
410 private List<String> linkedOrder = null;
412 private List<Arg> argList;
416 argMap = new HashMap<>();
417 for (Arg a : EnumSet.allOf(Arg.class))
419 ARGNAME: for (String argName : a.getNames())
421 if (argMap.containsKey(argName))
423 Console.warn("Trying to add argument name multiple times: '"
424 + argName + "'"); // RESTORE THIS WHEN MERGED
425 if (argMap.get(argName) != a)
428 "Trying to add argument name multiple times for different Args: '"
429 + argMap.get(argName).getName() + ":" + argName
430 + "' and '" + a.getName() + ":" + argName
435 argMap.put(argName, a);
440 public ArgParser(String[] args)
443 vargs = new ArrayList<>();
444 isApplet = (args.length > 0 && args[0].startsWith("<applet"));
447 // appletParams = AppletParams.getAppletParams(args, vargs);
456 // AppletParams.getAppletParams(Platform.getAppletInfoAsMap(), vargs);
458 for (int i = 0; i < args.length; i++)
460 String arg = args[i].trim();
461 if (arg.charAt(0) == '-')
463 arg = arg.substring(1);
470 Enumeration<String> argE = Collections.enumeration(Arrays.asList(args));
472 while (argE.hasMoreElements())
474 String arg = argE.nextElement();
475 String argName = null;
477 String linkedId = null;
478 if (arg.startsWith("--"))
480 int equalPos = arg.indexOf('=');
483 argName = arg.substring(2, equalPos);
484 val = arg.substring(equalPos + 1);
488 argName = arg.substring(2);
490 int idOpen = argName.indexOf('[');
491 int idClose = argName.indexOf(']');
493 if (idOpen > -1 && idClose == argName.length() - 1)
495 linkedId = argName.substring(idOpen + 1, idClose);
496 argName = argName.substring(0, idOpen);
499 Arg a = argMap.get(argName);
500 // check for boolean prepended by "no"
501 boolean negated = false;
502 if (a == null && argName.startsWith(NEGATESTRING) && argMap
503 .containsKey(argName.substring(NEGATESTRING.length())))
505 argName = argName.substring(NEGATESTRING.length());
506 a = argMap.get(argName);
510 // check for config errors
514 Console.error("Argument '" + arg + "' not recognised. Ignoring.");
517 if (!a.hasOption(Opt.BOOLEAN) && negated)
519 // used "no" with a non-boolean option
520 Console.error("Argument '--" + NEGATESTRING + argName
521 + "' not a boolean option. Ignoring.");
524 if (!a.hasOption(Opt.STRING) && equalPos > -1)
526 // set --argname=value when arg does not accept values
527 Console.error("Argument '--" + argName
528 + "' does not expect a value (given as '" + arg
532 if (!a.hasOption(Opt.LINKED) && linkedId != null)
534 // set --argname[linkedId] when arg does not use linkedIds
535 Console.error("Argument '--" + argName
536 + "' does not expect a linked id (given as '" + arg
541 if (a.hasOption(Opt.STRING) && equalPos == -1)
543 // take next arg as value if required, and '=' was not found
544 if (!argE.hasMoreElements())
546 // no value to take for arg, which wants a value
547 Console.error("Argument '" + a.getName()
548 + "' requires a value, none given. Ignoring.");
551 val = argE.nextElement();
554 // use default linkedId for linked arguments
555 if (a.hasOption(Opt.LINKED) && linkedId == null)
556 linkedId = DEFAULTLINKEDID;
558 if (!linkedArgs.containsKey(linkedId))
559 linkedArgs.put(linkedId, new HashMap<>());
561 Map<Arg, ArgValues> valuesMap = linkedArgs.get(linkedId);
562 if (!valuesMap.containsKey(a))
563 valuesMap.put(a, new ArgValues(a));
565 ArgValues values = valuesMap.get(a);
568 values = new ArgValues(a);
570 // store appropriate value
571 if (a.hasOption(Opt.STRING))
573 values.addValue(val, argIndex);
575 else if (a.hasOption(Opt.BOOLEAN))
577 values.setBoolean(!negated);
578 values.setNegated(negated);
580 else if (a.hasOption(Opt.UNARY))
582 values.setBoolean(true);
584 values.incrementCount();
586 // store in appropriate place
587 if (a.hasOption(Opt.LINKED))
589 // allow a default linked id for single usage
590 if (linkedId == null)
591 linkedId = DEFAULTLINKEDID;
592 // store the order of linkedIds
593 if (linkedOrder == null)
594 linkedOrder = new ArrayList<>();
595 if (!linkedOrder.contains(linkedId))
596 linkedOrder.add(linkedId);
598 // store the ArgValues
599 valuesMap.put(a, values);
601 // store arg in the list of args
603 argList = new ArrayList<>();
604 if (!argList.contains(a))
610 public boolean isSet(Arg a)
612 return a.hasOption(Opt.LINKED) ? isSet("", a) : isSet(null, a);
615 public boolean isSet(String linkedId, Arg a)
617 Map<Arg, ArgValues> m = linkedArgs.get(linkedId);
618 return m == null ? false : m.containsKey(a);
621 public boolean getBool(Arg a)
623 if (!a.hasOption(Opt.BOOLEAN) && !a.hasOption(Opt.UNARY))
625 Console.warn("Getting boolean from non boolean Arg '" + a.getName()
628 return a.hasOption(Opt.LINKED) ? getBool("", a) : getBool(null, a);
631 public boolean getBool(String linkedId, Arg a)
633 Map<Arg, ArgValues> m = linkedArgs.get(linkedId);
635 return a.getDefaultBoolValue();
636 ArgValues v = m.get(a);
637 return v == null ? a.getDefaultBoolValue() : v.getBoolean();
640 public List<String> linkedIds()
645 public HashMap<Arg, ArgValues> linkedArgs(String id)
647 return linkedArgs.get(id);
651 public String toString()
653 StringBuilder sb = new StringBuilder();
654 sb.append("UNLINKED\n");
655 sb.append(argMapToString(linkedArgs.get(null)));
656 if (linkedIds() != null)
658 sb.append("LINKED\n");
659 for (String id : linkedIds())
661 // already listed these as UNLINKED args
665 Map<Arg, ArgValues> m = linkedArgs(id);
666 sb.append("ID: '").append(id).append("'\n");
667 sb.append(argMapToString(m));
670 return sb.toString();
673 private static String argMapToString(Map<Arg, ArgValues> m)
677 StringBuilder sb = new StringBuilder();
678 for (Arg a : m.keySet())
680 ArgValues v = m.get(a);
681 sb.append(v.toString());
684 return sb.toString();
687 public static SubVals getSubVals(String item)
689 return new SubVals(item);
693 * A helper class to keep an index of argument position with argument values
695 public static class ArgValue
697 private int argIndex;
699 private String value;
701 protected ArgValue(String value, int argIndex)
704 this.argIndex = argIndex;
707 protected String getValue()
712 protected int getArgIndex()
719 * A helper class to parse a string of the possible forms "content"
720 * "[index]content", "[keyName=keyValue]content" and return the integer index,
721 * the strings keyName and keyValue, and the content after the square brackets
722 * (if present). Values not set `will be -1 or null.
724 public static class SubVals
726 private static int NOTSET = -1;
728 private int index = NOTSET;
730 private Map<String, String> subVals = null;
732 private static char SEPARATOR = ';';
734 private String content = null;
736 public SubVals(String item)
738 this.parseVals(item);
741 public void parseVals(String item)
743 if (item.indexOf('[') == 0 && item.indexOf(']') > 1)
745 int openBracket = item.indexOf('[');
746 int closeBracket = item.indexOf(']');
747 String subvalsString = item.substring(openBracket + 1,
749 this.content = item.substring(closeBracket + 1);
750 boolean setIndex = false;
751 for (String subvalString : subvalsString
752 .split(Character.toString(SEPARATOR)))
754 int equals = subvalString.indexOf('=');
758 subVals = new HashMap<>();
759 subVals.put(subvalString.substring(0, equals),
760 subvalString.substring(equals + 1));
766 this.index = Integer.parseInt(subvalString);
768 } catch (NumberFormatException e)
770 Console.warn("Failed to obtain subvalue or index from '"
771 + item + "'. Setting index=0 and using content='"
785 public boolean notSet()
787 // notSet is true if content present but nonsensical
788 return index == NOTSET && subVals == null;
791 public String get(String key)
793 return subVals == null ? null : subVals.get(key);
796 public boolean has(String key)
798 return subVals == null ? false : subVals.containsKey(key);
801 public int getIndex()
806 public String getContent()
813 * Helper class to allow easy extraction of information about specific
814 * argument values (without having to check for null etc all the time)
816 protected static class ArgValuesMap
818 protected Map<Arg, ArgValues> m;
820 protected ArgValuesMap(Map<Arg, ArgValues> map)
825 protected ArgValues getArgValues(Arg a)
827 return m == null ? null : m.get(a);
830 protected List<ArgValue> getArgValueList(Arg a)
832 ArgValues av = getArgValues(a);
833 return av == null ? null : av.getArgValueList();
836 protected ArgValue getArgValue(Arg a)
838 List<ArgValue> vals = getArgValueList(a);
839 return (vals == null || vals.size() == 0) ? null : vals.get(0);
842 protected String getValue(Arg a)
844 ArgValue av = getArgValue(a);
845 return av == null ? null : av.getValue();
848 protected boolean hasValue(Arg a)
850 if (!m.containsKey(a))
852 return getArgValue(a) != null;
855 protected boolean getBoolean(Arg a)
857 ArgValues av = getArgValues(a);
858 return av == null ? false : av.getBoolean();
861 protected ArgValue getClosestPreviousArgValueOfArg(ArgValue thisAv,
864 ArgValue closestAv = null;
865 int thisArgIndex = thisAv.getArgIndex();
866 ArgValues compareAvs = this.getArgValues(a);
867 int closestPreviousIndex = -1;
868 for (ArgValue av : compareAvs.getArgValueList())
870 int argIndex = av.getArgIndex();
871 if (argIndex < thisArgIndex && argIndex > closestPreviousIndex)
873 closestPreviousIndex = argIndex;
881 private static final Collection<Arg> bootstrapArgs = new ArrayList(
882 Arrays.asList(Arg.PROPS, Arg.DEBUG));
884 public static Map<Arg, String> bootstrapArgs(String[] args)
886 Map<Arg, String> bootstrapArgMap = new HashMap<>();
888 return bootstrapArgMap;
889 Enumeration<String> argE = Collections.enumeration(Arrays.asList(args));
890 while (argE.hasMoreElements())
892 String arg = argE.nextElement();
893 String argName = null;
895 if (arg.startsWith("--"))
897 int equalPos = arg.indexOf('=');
900 argName = arg.substring(2, equalPos);
901 val = arg.substring(equalPos + 1);
905 argName = arg.substring(2);
907 Arg a = argMap.get(argName);
908 if (a != null && bootstrapArgs.contains(a))
909 bootstrapArgMap.put(a, val);
912 return bootstrapArgMap;