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 private int argIndex = -1;
126 public String toLongString()
128 StringBuilder sb = new StringBuilder();
129 sb.append("Arg: ").append(this.name());
130 for (String name : getNames())
132 sb.append(", '").append(name).append("'");
134 sb.append("\nOptions: ");
135 boolean first = true;
136 for (Opt o : argOptions)
142 sb.append(o.toString());
146 return sb.toString();
154 private Arg(String... names)
156 int length = (names == null || names.length == 0
157 || (names.length == 1 && names[0] == null)) ? 1
159 this.argNames = new String[length];
160 this.argNames[0] = this.getName();
162 System.arraycopy(names, 0, this.argNames, 1, names.length);
165 public String[] getNames()
170 public String getName()
172 return this.name().toLowerCase(Locale.ROOT).replace('_', '-');
176 public final String toString()
181 public boolean hasOption(Opt o)
183 if (argOptions == null)
185 for (Opt option : argOptions)
193 protected void setOptions(Opt... options)
195 setOptions(false, options);
198 protected void setOptions(boolean defaultBoolValue, Opt... options)
200 this.defaultBoolValue = defaultBoolValue;
201 argOptions = options;
204 protected boolean getDefaultBoolValue()
206 return defaultBoolValue;
209 private void setArgIndex(int i)
214 protected int getArgIndex()
216 return this.argIndex;
220 public static class ArgValues
224 private int argCount = 0;
226 private boolean boolValue = false;
228 private boolean negated = false;
230 private int singleArgIndex = -1;
232 private List<Integer> argsIndexes;
234 private List<ArgValue> argsList;
236 protected ArgValues(Arg a)
239 this.argsList = new ArrayList<ArgValue>();
240 this.boolValue = arg.getDefaultBoolValue();
248 protected int getCount()
253 protected void incrementCount()
258 protected void setNegated(boolean b)
263 protected boolean isNegated()
268 protected void setBoolean(boolean b)
273 protected boolean getBoolean()
275 return this.boolValue;
279 public String toString()
281 if (argsList == null)
283 StringBuilder sb = new StringBuilder();
284 sb.append(arg.toLongString());
285 if (arg.hasOption(Opt.BOOLEAN) || arg.hasOption(Opt.UNARY))
286 sb.append("Boolean: ").append(boolValue).append("; Default: ")
287 .append(arg.getDefaultBoolValue()).append("; Negated: ")
288 .append(negated).append("\n");
289 if (arg.hasOption(Opt.STRING))
291 sb.append("Values:");
292 boolean first = true;
293 for (ArgValue av : argsList)
295 String v = av.getValue();
299 sb.append(v).append("'");
304 sb.append("Count: ").append(argCount).append("\n");
305 return sb.toString();
308 protected void addValue()
313 protected void addValue(String val, int argIndex)
315 addValue(val, argIndex, false);
318 protected void addValue(String val, int argIndex, boolean noDuplicates)
320 if ((!arg.hasOption(Opt.MULTI) && argsList.size() > 0)
321 || (noDuplicates && argsList.contains(val)))
323 if (argsList == null)
325 argsList = new ArrayList<ArgValue>();
327 argsList.add(new ArgValue(val, argIndex));
330 protected boolean hasValue(String val)
332 return argsList.contains(val);
335 protected ArgValue getArgValue()
337 if (arg.hasOption(Opt.MULTI))
338 Console.warn("Requesting single value for multi value argument");
339 return argsList.size() > 0 ? argsList.get(0) : null;
343 protected String getValue()
345 ArgValue av = getArgValue();
346 return av == null ? null : av.getValue();
350 protected List<ArgValue> getArgValueList()
357 private List<String> vargs = null;
359 private boolean isApplet;
361 // private AppletParams appletParams;
363 public boolean isApplet()
368 public String nextValue()
370 return vargs.remove(0);
378 public String getValue(String arg)
380 return getValue(arg, false);
383 public String getValue(String arg, boolean utf8decode)
385 int index = vargs.indexOf(arg);
386 String dc = null, ret = null;
389 ret = vargs.get(index + 1).toString();
392 if (utf8decode && ret != null)
396 dc = URLDecoder.decode(ret, "UTF-8");
398 } catch (Exception e)
400 // TODO: log failure to decode
408 public Object getAppletValue(String key, String def, boolean asString)
411 return (appletParams == null ? null
412 : (value = appletParams.get(key.toLowerCase())) == null ? def
413 : asString ? "" + value : value);
418 private static final Map<String, Arg> argMap;
420 private Map<String, HashMap<Arg, ArgValues>> linkedArgs = new HashMap<>();
422 private List<String> linkedOrder = null;
424 private List<Arg> argList;
428 argMap = new HashMap<>();
429 for (Arg a : EnumSet.allOf(Arg.class))
431 ARGNAME: for (String argName : a.getNames())
433 if (argMap.containsKey(argName))
435 Console.warn("Trying to add argument name multiple times: '"
436 + argName + "'"); // RESTORE THIS WHEN MERGED
437 if (argMap.get(argName) != a)
440 "Trying to add argument name multiple times for different Args: '"
441 + argMap.get(argName).getName() + ":" + argName
442 + "' and '" + a.getName() + ":" + argName
447 argMap.put(argName, a);
452 public ArgParser(String[] args)
455 vargs = new ArrayList<>();
456 isApplet = (args.length > 0 && args[0].startsWith("<applet"));
459 // appletParams = AppletParams.getAppletParams(args, vargs);
468 // AppletParams.getAppletParams(Platform.getAppletInfoAsMap(), vargs);
470 for (int i = 0; i < args.length; i++)
472 String arg = args[i].trim();
473 if (arg.charAt(0) == '-')
475 arg = arg.substring(1);
482 Enumeration<String> argE = Collections.enumeration(Arrays.asList(args));
484 while (argE.hasMoreElements())
486 String arg = argE.nextElement();
487 String argName = null;
489 String linkedId = null;
490 if (arg.startsWith("--"))
492 int equalPos = arg.indexOf('=');
495 argName = arg.substring(2, equalPos);
496 val = arg.substring(equalPos + 1);
500 argName = arg.substring(2);
502 int idOpen = argName.indexOf('[');
503 int idClose = argName.indexOf(']');
505 if (idOpen > -1 && idClose == argName.length() - 1)
507 linkedId = argName.substring(idOpen + 1, idClose);
508 argName = argName.substring(0, idOpen);
511 Arg a = argMap.get(argName);
512 // check for boolean prepended by "no"
513 boolean negated = false;
514 if (a == null && argName.startsWith(NEGATESTRING) && argMap
515 .containsKey(argName.substring(NEGATESTRING.length())))
517 argName = argName.substring(NEGATESTRING.length());
518 a = argMap.get(argName);
522 // check for config errors
526 Console.error("Argument '" + arg + "' not recognised. Ignoring.");
529 if (!a.hasOption(Opt.BOOLEAN) && negated)
531 // used "no" with a non-boolean option
532 Console.error("Argument '--" + NEGATESTRING + argName
533 + "' not a boolean option. Ignoring.");
536 if (!a.hasOption(Opt.STRING) && equalPos > -1)
538 // set --argname=value when arg does not accept values
539 Console.error("Argument '--" + argName
540 + "' does not expect a value (given as '" + arg
544 if (!a.hasOption(Opt.LINKED) && linkedId != null)
546 // set --argname[linkedId] when arg does not use linkedIds
547 Console.error("Argument '--" + argName
548 + "' does not expect a linked id (given as '" + arg
553 if (a.hasOption(Opt.STRING) && equalPos == -1)
555 // take next arg as value if required, and '=' was not found
556 if (!argE.hasMoreElements())
558 // no value to take for arg, which wants a value
559 Console.error("Argument '" + a.getName()
560 + "' requires a value, none given. Ignoring.");
563 val = argE.nextElement();
566 // use default linkedId for linked arguments
567 if (a.hasOption(Opt.LINKED) && linkedId == null)
568 linkedId = DEFAULTLINKEDID;
570 if (!linkedArgs.containsKey(linkedId))
571 linkedArgs.put(linkedId, new HashMap<>());
573 Map<Arg, ArgValues> valuesMap = linkedArgs.get(linkedId);
574 if (!valuesMap.containsKey(a))
575 valuesMap.put(a, new ArgValues(a));
577 ArgValues values = valuesMap.get(a);
580 values = new ArgValues(a);
582 // store appropriate value
583 if (a.hasOption(Opt.STRING))
585 values.addValue(val, argIndex);
587 else if (a.hasOption(Opt.BOOLEAN))
589 values.setBoolean(!negated);
590 values.setNegated(negated);
592 else if (a.hasOption(Opt.UNARY))
594 values.setBoolean(true);
596 values.incrementCount();
598 // store in appropriate place
599 if (a.hasOption(Opt.LINKED))
601 // allow a default linked id for single usage
602 if (linkedId == null)
603 linkedId = DEFAULTLINKEDID;
604 // store the order of linkedIds
605 if (linkedOrder == null)
606 linkedOrder = new ArrayList<>();
607 if (!linkedOrder.contains(linkedId))
608 linkedOrder.add(linkedId);
610 // store the ArgValues
611 valuesMap.put(a, values);
613 // store arg in the list of args
615 argList = new ArrayList<>();
616 if (!argList.contains(a))
622 public boolean isSet(Arg a)
624 return a.hasOption(Opt.LINKED) ? isSet("", a) : isSet(null, a);
627 public boolean isSet(String linkedId, Arg a)
629 Map<Arg, ArgValues> m = linkedArgs.get(linkedId);
630 return m == null ? false : m.containsKey(a);
633 public boolean getBool(Arg a)
635 if (!a.hasOption(Opt.BOOLEAN) && !a.hasOption(Opt.UNARY))
637 Console.warn("Getting boolean from non boolean Arg '" + a.getName()
640 return a.hasOption(Opt.LINKED) ? getBool("", a) : getBool(null, a);
643 public boolean getBool(String linkedId, Arg a)
645 Map<Arg, ArgValues> m = linkedArgs.get(linkedId);
647 return a.getDefaultBoolValue();
648 ArgValues v = m.get(a);
649 return v == null ? a.getDefaultBoolValue() : v.getBoolean();
652 public List<String> linkedIds()
657 public HashMap<Arg, ArgValues> linkedArgs(String id)
659 return linkedArgs.get(id);
663 public String toString()
665 StringBuilder sb = new StringBuilder();
666 sb.append("UNLINKED\n");
667 sb.append(argMapToString(linkedArgs.get(null)));
668 if (linkedIds() != null)
670 sb.append("LINKED\n");
671 for (String id : linkedIds())
673 // already listed these as UNLINKED args
677 Map<Arg, ArgValues> m = linkedArgs(id);
678 sb.append("ID: '").append(id).append("'\n");
679 sb.append(argMapToString(m));
682 return sb.toString();
685 private static String argMapToString(Map<Arg, ArgValues> m)
689 StringBuilder sb = new StringBuilder();
690 for (Arg a : m.keySet())
692 ArgValues v = m.get(a);
693 sb.append(v.toString());
696 return sb.toString();
699 public static SubVals getSubVals(String item)
701 return new SubVals(item);
705 * A helper class to keep an index of argument position with argument values
707 public static class ArgValue
709 private int argIndex;
711 private String value;
713 protected ArgValue(String value, int argIndex)
716 this.argIndex = argIndex;
719 protected String getValue()
724 protected int getArgIndex()
731 * A helper class to parse a string of the possible forms "content"
732 * "[index]content", "[keyName=keyValue]content" and return the integer index,
733 * the strings keyName and keyValue, and the content after the square brackets
734 * (if present). Values not set `will be -1 or null.
736 public static class SubVals
738 private static int NOTSET = -1;
740 private int index = NOTSET;
742 private Map<String, String> subVals = null;
744 private static char SEPARATOR = ';';
746 private String content = null;
748 public SubVals(String item)
750 this.parseVals(item);
753 public void parseVals(String item)
755 if (item.indexOf('[') == 0 && item.indexOf(']') > 1)
757 int openBracket = item.indexOf('[');
758 int closeBracket = item.indexOf(']');
759 String subvalsString = item.substring(openBracket + 1,
761 this.content = item.substring(closeBracket + 1);
762 boolean setIndex = false;
763 for (String subvalString : subvalsString
764 .split(Character.toString(SEPARATOR)))
766 int equals = subvalString.indexOf('=');
770 subVals = new HashMap<>();
771 subVals.put(subvalString.substring(0, equals),
772 subvalString.substring(equals + 1));
778 this.index = Integer.parseInt(subvalString);
780 } catch (NumberFormatException e)
782 Console.warn("Failed to obtain subvalue or index from '"
783 + item + "'. Setting index=0 and using content='"
797 public boolean notSet()
799 // notSet is true if content present but nonsensical
800 return index == NOTSET && subVals == null;
803 public String get(String key)
805 return subVals == null ? null : subVals.get(key);
808 public boolean has(String key)
810 return subVals == null ? false : subVals.containsKey(key);
813 public int getIndex()
818 public String getContent()
825 * Helper class to allow easy extraction of information about specific
826 * argument values (without having to check for null etc all the time)
828 protected static class ArgValuesMap
830 protected Map<Arg, ArgValues> m;
832 protected ArgValuesMap(Map<Arg, ArgValues> map)
837 protected ArgValues getArgValues(Arg a)
839 return m == null ? null : m.get(a);
842 protected List<ArgValue> getArgValueList(Arg a)
844 ArgValues av = getArgValues(a);
845 return av == null ? null : av.getArgValueList();
848 protected ArgValue getArgValue(Arg a)
850 List<ArgValue> vals = getArgValueList(a);
851 return (vals == null || vals.size() == 0) ? null : vals.get(0);
854 protected String getValue(Arg a)
856 ArgValue av = getArgValue(a);
857 return av == null ? null : av.getValue();
860 protected boolean hasValue(Arg a)
862 if (!m.containsKey(a))
864 return getArgValue(a) != null;
867 protected boolean getBoolean(Arg a)
869 ArgValues av = getArgValues(a);
870 return av == null ? false : av.getBoolean();
874 private static final Collection<String> bootstrapArgs = new ArrayList(
875 Arrays.asList("props", "debug"));
877 public static Map<String, String> bootstrapArgs(String[] args)
879 Map<String, String> argMap = new HashMap<>();
882 Enumeration<String> argE = Collections.enumeration(Arrays.asList(args));
883 while (argE.hasMoreElements())
885 String arg = argE.nextElement();
886 String argName = null;
888 if (arg.startsWith("--"))
890 int equalPos = arg.indexOf('=');
893 argName = arg.substring(2, equalPos);
894 val = arg.substring(equalPos + 1);
898 argName = arg.substring(2);
900 if (bootstrapArgs.contains(argName))
901 argMap.put(argName, val);