5c21d03e5768be034f119e2add7669b9e1b0c39e
[jalview.git] / src / jalview / bin / argparser / BootstrapArgs.java
1 package jalview.bin.argparser;
2
3 import java.io.File;
4 import java.util.AbstractMap;
5 import java.util.ArrayList;
6 import java.util.Arrays;
7 import java.util.EnumSet;
8 import java.util.HashMap;
9 import java.util.HashSet;
10 import java.util.List;
11 import java.util.Locale;
12 import java.util.Map;
13 import java.util.Set;
14 import java.util.stream.Collectors;
15
16 import jalview.bin.argparser.Arg.Opt;
17 import jalview.bin.argparser.Arg.Type;
18 import jalview.util.FileUtils;
19
20 public class BootstrapArgs
21 {
22   // only need one
23   private Map<Arg, List<Map.Entry<Type, String>>> bootstrapArgMap = new HashMap<>();
24
25   private Set<File> argFiles = new HashSet<>();
26
27   private Set<Opt> argsOptions = new HashSet<>();
28
29   private Set<Type> argsTypes = new HashSet<>();
30
31   private boolean outputToStdout = false;
32
33   public static BootstrapArgs getBootstrapArgs(String[] args)
34   {
35     List<String> argList = new ArrayList<>(Arrays.asList(args));
36     return new BootstrapArgs(argList);
37   }
38
39   public static BootstrapArgs getBootstrapArgs(List<String> args)
40   {
41     return new BootstrapArgs(args);
42   }
43
44   private BootstrapArgs(List<String> args)
45   {
46     parse(args, null);
47   }
48
49   private void parse(List<String> args, File inArgFile)
50   {
51     if (args == null)
52       return;
53     // avoid looping argFiles
54     if (inArgFile != null)
55     {
56       if (argFiles.contains(inArgFile))
57       {
58         jalview.bin.Console.errPrintln(
59                 "Looped argfiles detected: '" + inArgFile.getPath() + "'");
60         return;
61       }
62       argFiles.add(inArgFile);
63     }
64
65     for (int i = 0; i < args.size(); i++)
66     {
67       String arg = args.get(i);
68       // look for double-dash, e.g. --arg
69       if (arg.startsWith(ArgParser.DOUBLEDASH))
70       {
71         String argName = null;
72         String val = null;
73         Type type = null;
74         // remove "--"
75         argName = arg.substring(ArgParser.DOUBLEDASH.length());
76
77         // look for equals e.g. --arg=value
78         int equalPos = argName.indexOf(ArgParser.EQUALS);
79         if (equalPos > -1)
80         {
81           val = argName.substring(equalPos + 1);
82           argName = argName.substring(0, equalPos);
83         }
84
85         // check for boolean prepended by "no"
86         if (argName.startsWith(ArgParser.NEGATESTRING)
87                 && ArgParser.argMap.containsKey(
88                         argName.substring(ArgParser.NEGATESTRING.length())))
89         {
90           val = "false";
91           argName = argName.substring(ArgParser.NEGATESTRING.length());
92         }
93
94         // look for type modification e.g. --help-opening
95         int dashPos = argName.indexOf(ArgParser.SINGLEDASH);
96         if (dashPos > -1)
97         {
98           String potentialArgName = argName.substring(0, dashPos);
99           Arg potentialArg = ArgParser.argMap.get(potentialArgName);
100           if (potentialArg != null && potentialArg.hasOption(Opt.HASTYPE))
101           {
102             String typeName = argName.substring(dashPos + 1);
103             try
104             {
105               type = Type.valueOf(typeName.toUpperCase(Locale.ROOT));
106             } catch (IllegalArgumentException e)
107             {
108               type = Type.INVALID;
109             }
110             argName = argName.substring(0, dashPos);
111           }
112         }
113
114         // after all other args, look for Opt.PREFIX args if still not found
115         if (!ArgParser.argMap.containsKey(argName))
116         {
117           for (Arg potentialArg : EnumSet.allOf(Arg.class))
118           {
119             if (potentialArg.hasOption(Opt.PREFIXKEV) && argName != null
120                     && argName.startsWith(potentialArg.getName())
121                     && val != null)
122             {
123               val = argName.substring(potentialArg.getName().length())
124                       + ArgParser.EQUALS + val;
125               argName = argName.substring(0,
126                       potentialArg.getName().length());
127               break;
128             }
129           }
130         }
131
132         Arg a = ArgParser.argMap.get(argName);
133
134         if (a != null)
135         {
136           for (Opt opt : a.getOptions())
137           {
138             if (!argsOptions.contains(opt))
139             {
140               argsOptions.add(opt);
141             }
142           }
143           Type t = a.getType();
144           if (!argsTypes.contains(t))
145           {
146             argsTypes.add(t);
147           }
148         }
149
150         if (a == null || !a.hasOption(Opt.BOOTSTRAP))
151         {
152           // not a bootstrap arg
153
154           // make a check for an output going to stdout
155           if (a != null && a.hasOption(Opt.OUTPUTFILE)
156                   && a.hasOption(Opt.STDOUT))
157           {
158             if (val == null && i + 1 < args.size())
159             {
160               val = args.get(i + 1);
161             }
162             if (val.startsWith("[") && val.indexOf(']') > 0)
163             {
164               val = val.substring(val.indexOf(']') + 1);
165             }
166
167             if (ArgParser.STDOUTFILENAME.equals(val))
168             {
169               this.outputToStdout = true;
170             }
171           }
172
173           continue;
174         }
175
176         if (a.hasOption(Opt.STRING))
177         {
178           List<String> vals = null;
179           if (equalPos == -1)
180           {
181             vals = ArgParser.getShellGlobbedFilenameValues(a, args, i + 1);
182           }
183           else
184           {
185             if (a.hasOption(Opt.GLOB))
186             {
187               vals = FileUtils.getFilenamesFromGlob(val);
188             }
189             else
190             {
191               vals = new ArrayList<>();
192               vals.add(val);
193             }
194           }
195           addAll(a, type, vals);
196
197           if (a == Arg.ARGFILE)
198           {
199             for (String filename : vals)
200             {
201               File argFile = new File(filename);
202               parse(ArgParser.readArgFile(argFile), argFile);
203             }
204           }
205         }
206         else
207         {
208           if (val == null)
209           {
210             val = "true";
211           }
212
213           add(a, type, val);
214         }
215       }
216     }
217
218     // if in an argfile, remove it from the hashset so it can be re-used in
219     // another argfile
220     if (inArgFile != null)
221     {
222       argFiles.remove(inArgFile);
223     }
224   }
225
226   public boolean contains(Arg a)
227   {
228     return bootstrapArgMap.containsKey(a);
229   }
230
231   public boolean containsType(Type t)
232   {
233     for (List<Map.Entry<Type, String>> l : bootstrapArgMap.values())
234     {
235       for (Map.Entry<Type, String> e : l)
236       {
237         if (e.getKey() == t)
238           return true;
239       }
240     }
241     return false;
242   }
243
244   public List<Arg> getArgsOfType(Type t)
245   {
246     return getArgsOfType(t, new Opt[] {});
247   }
248
249   public List<Arg> getArgsOfType(Type t, Opt... opts)
250   {
251     List<Arg> args = new ArrayList<>();
252     for (Arg a : bootstrapArgMap.keySet())
253     {
254       if (!a.hasAllOptions(opts))
255         continue;
256
257       List<Map.Entry<Type, String>> l = bootstrapArgMap.get(a);
258       if (l.stream().anyMatch(e -> e.getKey() == t))
259       {
260         args.add(a);
261       }
262     }
263     return args;
264   }
265
266   public List<Map.Entry<Type, String>> getList(Arg a)
267   {
268     return bootstrapArgMap.get(a);
269   }
270
271   public List<String> getValueList(Arg a)
272   {
273     return bootstrapArgMap.get(a).stream().map(e -> e.getValue())
274             .collect(Collectors.toList());
275   }
276
277   private List<Map.Entry<Type, String>> getOrCreateList(Arg a)
278   {
279     List<Map.Entry<Type, String>> l = getList(a);
280     if (l == null)
281     {
282       l = new ArrayList<>();
283       putList(a, l);
284     }
285     return l;
286   }
287
288   private void putList(Arg a, List<Map.Entry<Type, String>> l)
289   {
290     bootstrapArgMap.put(a, l);
291   }
292
293   /*
294    * Creates a new list if not used before,
295    * adds the value unless the existing list is non-empty
296    * and the arg is not MULTI (so first expressed value is
297    * retained).
298    */
299   private void add(Arg a, Type t, String s)
300   {
301     List<Map.Entry<Type, String>> l = getOrCreateList(a);
302     if (a.hasOption(Opt.MULTIVALUE) || l.size() == 0)
303     {
304       l.add(entry(t, s));
305     }
306   }
307
308   private void addAll(Arg a, Type t, List<String> al)
309   {
310     List<Map.Entry<Type, String>> l = getOrCreateList(a);
311     if (a.hasOption(Opt.MULTIVALUE))
312     {
313       for (String s : al)
314       {
315         l.add(entry(t, s));
316       }
317     }
318     else if (l.size() == 0 && al.size() > 0)
319     {
320       l.add(entry(t, al.get(0)));
321     }
322   }
323
324   private static Map.Entry<Type, String> entry(Type t, String s)
325   {
326     return new AbstractMap.SimpleEntry<Type, String>(t, s);
327   }
328
329   /*
330    * Retrieves the first value even if MULTI.
331    * A convenience for non-MULTI args.
332    */
333   public String getValue(Arg a)
334   {
335     if (!bootstrapArgMap.containsKey(a))
336       return null;
337     List<Map.Entry<Type, String>> aL = bootstrapArgMap.get(a);
338     return (aL == null || aL.size() == 0) ? null : aL.get(0).getValue();
339   }
340
341   public boolean getBoolean(Arg a, boolean d)
342   {
343     if (!bootstrapArgMap.containsKey(a))
344       return d;
345     return Boolean.parseBoolean(getValue(a));
346   }
347
348   public boolean getBoolean(Arg a)
349   {
350     if (!(a.hasOption(Opt.BOOLEAN) || a.hasOption(Opt.UNARY)))
351     {
352       return false;
353     }
354     if (bootstrapArgMap.containsKey(a))
355     {
356       return Boolean.parseBoolean(getValue(a));
357     }
358     else
359     {
360       return a.getDefaultBoolValue();
361     }
362   }
363
364   public boolean argsHaveOption(Opt opt)
365   {
366     return argsOptions.contains(opt);
367   }
368
369   public boolean argsHaveType(Type type)
370   {
371     return argsTypes.contains(type);
372   }
373
374   public boolean isHeadless()
375   {
376     boolean isHeadless = false;
377     if (this.argsHaveType(Type.HELP))
378     {
379       // --help, --help-all, ... specified => headless
380       isHeadless = true;
381     }
382     else if (this.contains(Arg.VERSION))
383     {
384       // --version specified => headless
385       isHeadless = true;
386     }
387     else if (this.contains(Arg.GUI))
388     {
389       // --gui specified => forced NOT headless
390       isHeadless = !this.getBoolean(Arg.GUI);
391     }
392     else if (this.contains(Arg.HEADLESS))
393     {
394       // --headless has been specified on the command line => headless
395       isHeadless = this.getBoolean(Arg.HEADLESS);
396     }
397     else if (this.argsHaveOption(Opt.OUTPUTFILE))
398     {
399       // --output file.fa, --image pic.png, --structureimage struct.png =>
400       // assume headless unless above has been already specified
401       isHeadless = true;
402     }
403     return isHeadless;
404   }
405
406   public boolean outputToStdout()
407   {
408     return this.outputToStdout;
409   }
410 }