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;
65 HELP.setOptions(Opt.UNARY);
66 CALCULATION.setOptions(true, Opt.BOOLEAN); // default "true" implies only
67 // expecting "--nocalculation"
68 MENUBAR.setOptions(true, Opt.BOOLEAN);
69 STATUS.setOptions(true, Opt.BOOLEAN);
70 SHOWOVERVIEW.setOptions(Opt.UNARY, Opt.LINKED);
71 ANNOTATIONS.setOptions(Opt.STRING, Opt.LINKED);
72 COLOUR.setOptions(Opt.STRING, Opt.LINKED);
73 FEATURES.setOptions(Opt.STRING, Opt.LINKED, Opt.MULTI);
74 GROOVY.setOptions(Opt.STRING, Opt.LINKED, Opt.MULTI);
75 GROUPS.setOptions(Opt.STRING, Opt.LINKED);
76 HEADLESS.setOptions(Opt.UNARY);
77 JABAWS.setOptions(Opt.STRING);
78 ANNOTATION.setOptions(true, Opt.BOOLEAN);
79 ANNOTATION2.setOptions(true, Opt.BOOLEAN);
80 DISPLAY.setOptions(true, Opt.BOOLEAN);
81 GUI.setOptions(true, Opt.BOOLEAN);
82 NEWS.setOptions(true, Opt.BOOLEAN);
83 NOQUESTIONNAIRE.setOptions(Opt.UNARY); // unary as --questionnaire=val
84 // expects a string value
85 SORTBYTREE.setOptions(true, Opt.BOOLEAN);
86 USAGESTATS.setOptions(true, Opt.BOOLEAN);
87 OPEN.setOptions(Opt.STRING, Opt.LINKED, Opt.MULTI);
88 OPEN2.setOptions(Opt.STRING, Opt.LINKED);
89 PROPS.setOptions(Opt.STRING);
90 QUESTIONNAIRE.setOptions(Opt.STRING);
91 SETPROP.setOptions(Opt.STRING);
92 TREE.setOptions(Opt.STRING);
94 VDOC.setOptions(Opt.UNARY);
95 VSESS.setOptions(Opt.UNARY);
97 OUTPUT.setOptions(Opt.STRING, Opt.LINKED);
98 OUTPUTTYPE.setOptions(Opt.STRING, Opt.LINKED, Opt.MULTI);
100 SSANNOTATION.setOptions(Opt.BOOLEAN, Opt.LINKED);
101 NOTEMPFAC.setOptions(Opt.UNARY, Opt.LINKED);
102 TEMPFAC.setOptions(Opt.STRING, Opt.LINKED);
103 TEMPFAC_LABEL.setOptions(Opt.STRING, Opt.LINKED);
104 TEMPFAC_DESC.setOptions(Opt.STRING, Opt.LINKED);
105 TEMPFAC_SHADING.setOptions(Opt.STRING, Opt.LINKED);
106 TITLE.setOptions(Opt.STRING, Opt.LINKED);
107 PAEMATRIX.setOptions(Opt.STRING, Opt.LINKED);
108 WRAP.setOptions(Opt.BOOLEAN, Opt.LINKED);
111 private final String[] argNames;
113 private Opt[] argOptions;
115 private boolean defaultBoolValue = false;
117 public String toLongString()
119 StringBuilder sb = new StringBuilder();
120 sb.append("Arg: ").append(this.name());
121 for (String name : getNames())
123 sb.append(", '").append(name).append("'");
125 sb.append("\nOptions: ");
126 boolean first = true;
127 for (Opt o : argOptions)
133 sb.append(o.toString());
137 return sb.toString();
145 private Arg(String... names)
147 int length = (names == null || names.length == 0
148 || (names.length == 1 && names[0] == null)) ? 1
150 this.argNames = new String[length];
151 this.argNames[0] = this.getName();
153 System.arraycopy(names, 0, this.argNames, 1, names.length);
156 public String[] getNames()
161 public String getName()
163 return this.name().toLowerCase(Locale.ROOT).replace('_', '-');
167 public final String toString()
172 public boolean hasOption(Opt o)
174 if (argOptions == null)
176 for (Opt option : argOptions)
184 protected void setOptions(Opt... options)
186 setOptions(false, options);
189 protected void setOptions(boolean defaultBoolValue, Opt... options)
191 this.defaultBoolValue = defaultBoolValue;
192 argOptions = options;
195 protected boolean getDefaultBoolValue()
197 return defaultBoolValue;
201 public static class ArgValues
205 private int argCount = 0;
207 private boolean boolValue = false;
209 private boolean negated = false;
211 private List<String> argsList;
213 protected ArgValues(Arg a)
216 this.argsList = new ArrayList<String>();
217 this.boolValue = arg.getDefaultBoolValue();
225 protected int getCount()
230 protected void incrementCount()
235 protected void setNegated(boolean b)
240 protected boolean isNegated()
245 protected void setBoolean(boolean b)
250 protected boolean getBoolean()
252 return this.boolValue;
256 public String toString()
258 if (argsList == null)
260 StringBuilder sb = new StringBuilder();
261 sb.append(arg.toLongString());
262 if (arg.hasOption(Opt.BOOLEAN) || arg.hasOption(Opt.UNARY))
263 sb.append("Boolean: ").append(boolValue).append("; Default: ")
264 .append(arg.getDefaultBoolValue()).append("; Negated: ")
265 .append(negated).append("\n");
266 if (arg.hasOption(Opt.STRING))
268 sb.append("Values:");
269 boolean first = true;
270 for (String v : argsList)
275 sb.append(v).append("'");
280 sb.append("Count: ").append(argCount).append("\n");
281 return sb.toString();
284 protected void addValue()
289 protected void addValue(String val)
291 addValue(val, false);
294 protected void addValue(String val, boolean noDuplicates)
296 if ((!arg.hasOption(Opt.MULTI) && argsList.size() > 0)
297 || (noDuplicates && argsList.contains(val)))
299 if (argsList == null)
301 Console.warn("** inst");
302 argsList = new ArrayList<String>();
307 protected boolean hasValue(String val)
309 return argsList.contains(val);
312 protected String getValue()
314 if (arg.hasOption(Opt.MULTI))
315 Console.warn("Requesting single value for multi value argument");
316 return argsList.size() > 0 ? argsList.get(0) : null;
319 protected List<String> getValues()
326 private List<String> vargs = null;
328 private boolean isApplet;
330 // private AppletParams appletParams;
332 public boolean isApplet()
337 public String nextValue()
339 return vargs.remove(0);
347 public String getValue(String arg)
349 return getValue(arg, false);
352 public String getValue(String arg, boolean utf8decode)
354 int index = vargs.indexOf(arg);
355 String dc = null, ret = null;
358 ret = vargs.get(index + 1).toString();
361 if (utf8decode && ret != null)
365 dc = URLDecoder.decode(ret, "UTF-8");
367 } catch (Exception e)
369 // TODO: log failure to decode
377 public Object getAppletValue(String key, String def, boolean asString)
380 return (appletParams == null ? null
381 : (value = appletParams.get(key.toLowerCase())) == null ? def
382 : asString ? "" + value : value);
387 private static final Map<String, Arg> argMap;
389 private Map<String, HashMap<Arg, ArgValues>> linkedArgs = new HashMap<>();
391 private List<String> linkedOrder = null;
393 private List<Arg> argList;
397 argMap = new HashMap<>();
398 for (Arg a : EnumSet.allOf(Arg.class))
400 ARGNAME: for (String argName : a.getNames())
402 if (argMap.containsKey(argName))
404 Console.warn("Trying to add argument name multiple times: '"
405 + argName + "'"); // RESTORE THIS WHEN MERGED
406 if (argMap.get(argName) != a)
409 "Trying to add argument name multiple times for different Args: '"
410 + argMap.get(argName).getName() + ":" + argName
411 + "' and '" + a.getName() + ":" + argName
416 argMap.put(argName, a);
421 public ArgParser(String[] args)
424 vargs = new ArrayList<>();
425 isApplet = (args.length > 0 && args[0].startsWith("<applet"));
428 // appletParams = AppletParams.getAppletParams(args, vargs);
437 // AppletParams.getAppletParams(Platform.getAppletInfoAsMap(), vargs);
439 for (int i = 0; i < args.length; i++)
441 String arg = args[i].trim();
442 if (arg.charAt(0) == '-')
444 arg = arg.substring(1);
451 Enumeration<String> argE = Collections.enumeration(Arrays.asList(args));
452 ARG: while (argE.hasMoreElements())
454 String arg = argE.nextElement();
455 String argName = null;
457 String linkedId = null;
458 if (arg.startsWith("--"))
460 int equalPos = arg.indexOf('=');
463 argName = arg.substring(2, equalPos);
464 val = arg.substring(equalPos + 1);
468 argName = arg.substring(2);
470 int idOpen = argName.indexOf('[');
471 int idClose = argName.indexOf(']');
473 if (idOpen > -1 && idClose == argName.length() - 1)
475 linkedId = argName.substring(idOpen + 1, idClose);
476 argName = argName.substring(0, idOpen);
479 Arg a = argMap.get(argName);
480 // check for boolean prepended by "no"
481 boolean negated = false;
482 if (a == null && argName.startsWith(NEGATESTRING) && argMap
483 .containsKey(argName.substring(NEGATESTRING.length())))
485 argName = argName.substring(NEGATESTRING.length());
486 a = argMap.get(argName);
490 // check for config errors
494 Console.error("Argument '" + arg + "' not recognised. Ignoring.");
497 if (!a.hasOption(Opt.BOOLEAN) && negated)
499 // used "no" with a non-boolean option
500 Console.error("Argument '--" + NEGATESTRING + argName
501 + "' not a boolean option. Ignoring.");
504 if (!a.hasOption(Opt.STRING) && equalPos > -1)
506 // set --argname=value when arg does not accept values
507 Console.error("Argument '--" + argName
508 + "' does not expect a value (given as '" + arg
512 if (!a.hasOption(Opt.LINKED) && linkedId != null)
514 // set --argname[linkedId] when arg does not use linkedIds
515 Console.error("Argument '--" + argName
516 + "' does not expect a linked id (given as '" + arg
521 if (a.hasOption(Opt.STRING) && equalPos == -1)
523 // take next arg as value if required, and '=' was not found
524 if (!argE.hasMoreElements())
526 // no value to take for arg, which wants a value
527 Console.error("Argument '" + a.getName()
528 + "' requires a value, none given. Ignoring.");
531 val = argE.nextElement();
534 // use default linkedId for linked arguments
535 if (a.hasOption(Opt.LINKED) && linkedId == null)
536 linkedId = DEFAULTLINKEDID;
538 if (!linkedArgs.containsKey(linkedId))
539 linkedArgs.put(linkedId, new HashMap<>());
541 Map<Arg, ArgValues> valuesMap = linkedArgs.get(linkedId);
542 if (!valuesMap.containsKey(a))
543 valuesMap.put(a, new ArgValues(a));
545 ArgValues values = valuesMap.get(a);
548 values = new ArgValues(a);
550 // store appropriate value
551 if (a.hasOption(Opt.STRING))
553 values.addValue(val);
555 else if (a.hasOption(Opt.BOOLEAN))
557 values.setBoolean(!negated);
558 values.setNegated(negated);
560 else if (a.hasOption(Opt.UNARY))
562 values.setBoolean(true);
564 values.incrementCount();
566 // store in appropriate place
567 if (a.hasOption(Opt.LINKED))
569 // allow a default linked id for single usage
570 if (linkedId == null)
571 linkedId = DEFAULTLINKEDID;
572 // store the order of linkedIds
573 if (linkedOrder == null)
574 linkedOrder = new ArrayList<>();
575 if (!linkedOrder.contains(linkedId))
576 linkedOrder.add(linkedId);
578 // store the ArgValues
579 valuesMap.put(a, values);
581 // store arg in the list of args
583 argList = new ArrayList<>();
584 if (!argList.contains(a))
590 public boolean isSet(Arg a)
592 return a.hasOption(Opt.LINKED) ? isSet("", a) : isSet(null, a);
595 public boolean isSet(String linkedId, Arg a)
597 Map<Arg, ArgValues> m = linkedArgs.get(linkedId);
598 return m == null ? false : m.containsKey(a);
601 public boolean getBool(Arg a)
603 if (!a.hasOption(Opt.BOOLEAN))
605 Console.warn("Getting boolean from non boolean Arg '" + a.getName()
608 return a.hasOption(Opt.LINKED) ? getBool("", a) : getBool(null, a);
611 public boolean getBool(String linkedId, Arg a)
613 Map<Arg, ArgValues> m = linkedArgs.get(linkedId);
615 return a.getDefaultBoolValue();
616 ArgValues v = m.get(a);
617 return v == null ? a.getDefaultBoolValue() : v.getBoolean();
620 public List<String> linkedIds()
625 public HashMap<Arg, ArgValues> linkedArgs(String id)
627 return linkedArgs.get(id);
631 public String toString()
633 StringBuilder sb = new StringBuilder();
634 sb.append("UNLINKED\n");
635 sb.append(argMapToString(linkedArgs.get(null)));
636 if (linkedIds() != null)
638 sb.append("LINKED\n");
639 for (String id : linkedIds())
641 // already listed these as UNLINKED args
645 Map<Arg, ArgValues> m = linkedArgs(id);
646 sb.append("ID: '").append(id).append("'\n");
647 sb.append(argMapToString(m));
650 return sb.toString();
653 private static String argMapToString(Map<Arg, ArgValues> m)
657 StringBuilder sb = new StringBuilder();
658 for (Arg a : m.keySet())
660 ArgValues v = m.get(a);
661 sb.append(v.toString());
664 return sb.toString();