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, IMAGE, QUIT;
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);
112 IMAGE.setOptions(Opt.STRING, Opt.LINKED);
113 QUIT.setOptions(Opt.UNARY);
116 private final String[] argNames;
118 private Opt[] argOptions;
120 private boolean defaultBoolValue = false;
122 public String toLongString()
124 StringBuilder sb = new StringBuilder();
125 sb.append("Arg: ").append(this.name());
126 for (String name : getNames())
128 sb.append(", '").append(name).append("'");
130 sb.append("\nOptions: ");
131 boolean first = true;
132 for (Opt o : argOptions)
138 sb.append(o.toString());
142 return sb.toString();
150 private Arg(String... names)
152 int length = (names == null || names.length == 0
153 || (names.length == 1 && names[0] == null)) ? 1
155 this.argNames = new String[length];
156 this.argNames[0] = this.getName();
158 System.arraycopy(names, 0, this.argNames, 1, names.length);
161 public String[] getNames()
166 public String getName()
168 return this.name().toLowerCase(Locale.ROOT).replace('_', '-');
172 public final String toString()
177 public boolean hasOption(Opt o)
179 if (argOptions == null)
181 for (Opt option : argOptions)
189 protected void setOptions(Opt... options)
191 setOptions(false, options);
194 protected void setOptions(boolean defaultBoolValue, Opt... options)
196 this.defaultBoolValue = defaultBoolValue;
197 argOptions = options;
200 protected boolean getDefaultBoolValue()
202 return defaultBoolValue;
206 public static class ArgValues
210 private int argCount = 0;
212 private boolean boolValue = false;
214 private boolean negated = false;
216 private List<String> argsList;
218 protected ArgValues(Arg a)
221 this.argsList = new ArrayList<String>();
222 this.boolValue = arg.getDefaultBoolValue();
230 protected int getCount()
235 protected void incrementCount()
240 protected void setNegated(boolean b)
245 protected boolean isNegated()
250 protected void setBoolean(boolean b)
255 protected boolean getBoolean()
257 return this.boolValue;
261 public String toString()
263 if (argsList == null)
265 StringBuilder sb = new StringBuilder();
266 sb.append(arg.toLongString());
267 if (arg.hasOption(Opt.BOOLEAN) || arg.hasOption(Opt.UNARY))
268 sb.append("Boolean: ").append(boolValue).append("; Default: ")
269 .append(arg.getDefaultBoolValue()).append("; Negated: ")
270 .append(negated).append("\n");
271 if (arg.hasOption(Opt.STRING))
273 sb.append("Values:");
274 boolean first = true;
275 for (String v : argsList)
280 sb.append(v).append("'");
285 sb.append("Count: ").append(argCount).append("\n");
286 return sb.toString();
289 protected void addValue()
294 protected void addValue(String val)
296 addValue(val, false);
299 protected void addValue(String val, boolean noDuplicates)
301 if ((!arg.hasOption(Opt.MULTI) && argsList.size() > 0)
302 || (noDuplicates && argsList.contains(val)))
304 if (argsList == null)
306 Console.warn("** inst");
307 argsList = new ArrayList<String>();
312 protected boolean hasValue(String val)
314 return argsList.contains(val);
317 protected String getValue()
319 if (arg.hasOption(Opt.MULTI))
320 Console.warn("Requesting single value for multi value argument");
321 return argsList.size() > 0 ? argsList.get(0) : null;
324 protected List<String> getValues()
331 private List<String> vargs = null;
333 private boolean isApplet;
335 // private AppletParams appletParams;
337 public boolean isApplet()
342 public String nextValue()
344 return vargs.remove(0);
352 public String getValue(String arg)
354 return getValue(arg, false);
357 public String getValue(String arg, boolean utf8decode)
359 int index = vargs.indexOf(arg);
360 String dc = null, ret = null;
363 ret = vargs.get(index + 1).toString();
366 if (utf8decode && ret != null)
370 dc = URLDecoder.decode(ret, "UTF-8");
372 } catch (Exception e)
374 // TODO: log failure to decode
382 public Object getAppletValue(String key, String def, boolean asString)
385 return (appletParams == null ? null
386 : (value = appletParams.get(key.toLowerCase())) == null ? def
387 : asString ? "" + value : value);
392 private static final Map<String, Arg> argMap;
394 private Map<String, HashMap<Arg, ArgValues>> linkedArgs = new HashMap<>();
396 private List<String> linkedOrder = null;
398 private List<Arg> argList;
402 argMap = new HashMap<>();
403 for (Arg a : EnumSet.allOf(Arg.class))
405 ARGNAME: for (String argName : a.getNames())
407 if (argMap.containsKey(argName))
409 Console.warn("Trying to add argument name multiple times: '"
410 + argName + "'"); // RESTORE THIS WHEN MERGED
411 if (argMap.get(argName) != a)
414 "Trying to add argument name multiple times for different Args: '"
415 + argMap.get(argName).getName() + ":" + argName
416 + "' and '" + a.getName() + ":" + argName
421 argMap.put(argName, a);
426 public ArgParser(String[] args)
429 vargs = new ArrayList<>();
430 isApplet = (args.length > 0 && args[0].startsWith("<applet"));
433 // appletParams = AppletParams.getAppletParams(args, vargs);
442 // AppletParams.getAppletParams(Platform.getAppletInfoAsMap(), vargs);
444 for (int i = 0; i < args.length; i++)
446 String arg = args[i].trim();
447 if (arg.charAt(0) == '-')
449 arg = arg.substring(1);
456 Enumeration<String> argE = Collections.enumeration(Arrays.asList(args));
457 while (argE.hasMoreElements())
459 String arg = argE.nextElement();
460 String argName = null;
462 String linkedId = null;
463 if (arg.startsWith("--"))
465 int equalPos = arg.indexOf('=');
468 argName = arg.substring(2, equalPos);
469 val = arg.substring(equalPos + 1);
473 argName = arg.substring(2);
475 int idOpen = argName.indexOf('[');
476 int idClose = argName.indexOf(']');
478 if (idOpen > -1 && idClose == argName.length() - 1)
480 linkedId = argName.substring(idOpen + 1, idClose);
481 argName = argName.substring(0, idOpen);
484 Arg a = argMap.get(argName);
485 // check for boolean prepended by "no"
486 boolean negated = false;
487 if (a == null && argName.startsWith(NEGATESTRING) && argMap
488 .containsKey(argName.substring(NEGATESTRING.length())))
490 argName = argName.substring(NEGATESTRING.length());
491 a = argMap.get(argName);
495 // check for config errors
499 Console.error("Argument '" + arg + "' not recognised. Ignoring.");
502 if (!a.hasOption(Opt.BOOLEAN) && negated)
504 // used "no" with a non-boolean option
505 Console.error("Argument '--" + NEGATESTRING + argName
506 + "' not a boolean option. Ignoring.");
509 if (!a.hasOption(Opt.STRING) && equalPos > -1)
511 // set --argname=value when arg does not accept values
512 Console.error("Argument '--" + argName
513 + "' does not expect a value (given as '" + arg
517 if (!a.hasOption(Opt.LINKED) && linkedId != null)
519 // set --argname[linkedId] when arg does not use linkedIds
520 Console.error("Argument '--" + argName
521 + "' does not expect a linked id (given as '" + arg
526 if (a.hasOption(Opt.STRING) && equalPos == -1)
528 // take next arg as value if required, and '=' was not found
529 if (!argE.hasMoreElements())
531 // no value to take for arg, which wants a value
532 Console.error("Argument '" + a.getName()
533 + "' requires a value, none given. Ignoring.");
536 val = argE.nextElement();
539 // use default linkedId for linked arguments
540 if (a.hasOption(Opt.LINKED) && linkedId == null)
541 linkedId = DEFAULTLINKEDID;
543 if (!linkedArgs.containsKey(linkedId))
544 linkedArgs.put(linkedId, new HashMap<>());
546 Map<Arg, ArgValues> valuesMap = linkedArgs.get(linkedId);
547 if (!valuesMap.containsKey(a))
548 valuesMap.put(a, new ArgValues(a));
550 ArgValues values = valuesMap.get(a);
553 values = new ArgValues(a);
555 // store appropriate value
556 if (a.hasOption(Opt.STRING))
558 values.addValue(val);
560 else if (a.hasOption(Opt.BOOLEAN))
562 values.setBoolean(!negated);
563 values.setNegated(negated);
565 else if (a.hasOption(Opt.UNARY))
567 values.setBoolean(true);
569 values.incrementCount();
571 // store in appropriate place
572 if (a.hasOption(Opt.LINKED))
574 // allow a default linked id for single usage
575 if (linkedId == null)
576 linkedId = DEFAULTLINKEDID;
577 // store the order of linkedIds
578 if (linkedOrder == null)
579 linkedOrder = new ArrayList<>();
580 if (!linkedOrder.contains(linkedId))
581 linkedOrder.add(linkedId);
583 // store the ArgValues
584 valuesMap.put(a, values);
586 // store arg in the list of args
588 argList = new ArrayList<>();
589 if (!argList.contains(a))
595 public boolean isSet(Arg a)
597 return a.hasOption(Opt.LINKED) ? isSet("", a) : isSet(null, a);
600 public boolean isSet(String linkedId, Arg a)
602 Map<Arg, ArgValues> m = linkedArgs.get(linkedId);
603 return m == null ? false : m.containsKey(a);
606 public boolean getBool(Arg a)
608 if (!a.hasOption(Opt.BOOLEAN))
610 Console.warn("Getting boolean from non boolean Arg '" + a.getName()
613 return a.hasOption(Opt.LINKED) ? getBool("", a) : getBool(null, a);
616 public boolean getBool(String linkedId, Arg a)
618 Map<Arg, ArgValues> m = linkedArgs.get(linkedId);
620 return a.getDefaultBoolValue();
621 ArgValues v = m.get(a);
622 return v == null ? a.getDefaultBoolValue() : v.getBoolean();
625 public List<String> linkedIds()
630 public HashMap<Arg, ArgValues> linkedArgs(String id)
632 return linkedArgs.get(id);
636 public String toString()
638 StringBuilder sb = new StringBuilder();
639 sb.append("UNLINKED\n");
640 sb.append(argMapToString(linkedArgs.get(null)));
641 if (linkedIds() != null)
643 sb.append("LINKED\n");
644 for (String id : linkedIds())
646 // already listed these as UNLINKED args
650 Map<Arg, ArgValues> m = linkedArgs(id);
651 sb.append("ID: '").append(id).append("'\n");
652 sb.append(argMapToString(m));
655 return sb.toString();
658 private static String argMapToString(Map<Arg, ArgValues> m)
662 StringBuilder sb = new StringBuilder();
663 for (Arg a : m.keySet())
665 ArgValues v = m.get(a);
666 sb.append(v.toString());
669 return sb.toString();
672 // Helper methods with safety checks
673 protected static ArgValues getArgValues(Map<Arg, ArgValues> m, Arg a)
675 return m == null ? null : m.get(a);
678 public static List<String> getValues(Map<Arg, ArgValues> m, Arg a)
680 ArgValues av = getArgValues(m, a);
681 return av == null ? null : av.getValues();
684 public static String getValue(Map<Arg, ArgValues> m, Arg a)
686 List<String> vals = getValues(m, a);
687 return (vals == null || vals.size() == 0) ? null : vals.get(0);
690 public static boolean getBoolean(Map<Arg, ArgValues> m, Arg a)
692 ArgValues av = getArgValues(m, a);
693 return av == null ? false : av.getBoolean();
696 public static SubVal getSubVal(String item)
698 return new SubVal(item);
702 * A helper class to parse a string of the possible forms "content"
703 * "[index]content", "[keyName=keyValue]content" and return the integer index,
704 * the strings keyName and keyValue, and the content after the square brackets
705 * (if present). Values not set `will be -1 or null.
707 public static class SubVal
709 private static int NOTSET = -1;
711 protected int index = NOTSET;
713 protected String keyName = null;
715 protected String keyValue = null;
717 protected String content = null;
719 public SubVal(String item)
724 public void parseVal(String item)
726 if (item.indexOf('[') == 0 && item.indexOf(']') > 1)
728 int openBracket = item.indexOf('[');
729 int closeBracket = item.indexOf(']');
730 String indexString = item.substring(openBracket + 1, closeBracket);
731 this.content = item.substring(closeBracket + 1);
732 int equals = indexString.indexOf('=');
735 this.keyName = indexString.substring(0, equals);
736 this.keyValue = indexString.substring(equals + 1);
743 this.index = Integer.parseInt(indexString);
744 } catch (NumberFormatException e)
746 Console.warn("Failed to obtain subvalue or index from '" + item
747 + "'. Setting index=0 and using content='" + content
758 public boolean notSet()
760 // notSet is true if content present but nonsensical
761 return index == NOTSET && keyName == null && keyValue == null;