32eb91a905c62781a337a8b112ef731c5bbe904a
[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           {
157             if ((val == null && i + 1 < args.size()
158                     && ArgParser.STDOUTFILENAME.equals(args.get(i + 1)))
159                     || ArgParser.STDOUTFILENAME.equals(val))
160             {
161               this.outputToStdout = true;
162             }
163           }
164
165           continue;
166         }
167
168         if (a.hasOption(Opt.STRING))
169         {
170           List<String> vals = null;
171           if (equalPos == -1)
172           {
173             vals = ArgParser.getShellGlobbedFilenameValues(a, args, i + 1);
174           }
175           else
176           {
177             if (a.hasOption(Opt.GLOB))
178             {
179               vals = FileUtils.getFilenamesFromGlob(val);
180             }
181             else
182             {
183               vals = new ArrayList<>();
184               vals.add(val);
185             }
186           }
187           addAll(a, type, vals);
188
189           if (a == Arg.ARGFILE)
190           {
191             for (String filename : vals)
192             {
193               File argFile = new File(filename);
194               parse(ArgParser.readArgFile(argFile), argFile);
195             }
196           }
197         }
198         else
199         {
200           if (val == null)
201           {
202             val = "true";
203           }
204
205           add(a, type, val);
206         }
207       }
208     }
209   }
210
211   public boolean contains(Arg a)
212   {
213     return bootstrapArgMap.containsKey(a);
214   }
215
216   public boolean containsType(Type t)
217   {
218     for (List<Map.Entry<Type, String>> l : bootstrapArgMap.values())
219     {
220       for (Map.Entry<Type, String> e : l)
221       {
222         if (e.getKey() == t)
223           return true;
224       }
225     }
226     return false;
227   }
228
229   public List<Arg> getArgsOfType(Type t)
230   {
231     return getArgsOfType(t, new Opt[] {});
232   }
233
234   public List<Arg> getArgsOfType(Type t, Opt... opts)
235   {
236     List<Arg> args = new ArrayList<>();
237     for (Arg a : bootstrapArgMap.keySet())
238     {
239       if (!a.hasAllOptions(opts))
240         continue;
241
242       List<Map.Entry<Type, String>> l = bootstrapArgMap.get(a);
243       if (l.stream().anyMatch(e -> e.getKey() == t))
244       {
245         args.add(a);
246       }
247     }
248     return args;
249   }
250
251   public List<Map.Entry<Type, String>> getList(Arg a)
252   {
253     return bootstrapArgMap.get(a);
254   }
255
256   public List<String> getValueList(Arg a)
257   {
258     return bootstrapArgMap.get(a).stream().map(e -> e.getValue())
259             .collect(Collectors.toList());
260   }
261
262   private List<Map.Entry<Type, String>> getOrCreateList(Arg a)
263   {
264     List<Map.Entry<Type, String>> l = getList(a);
265     if (l == null)
266     {
267       l = new ArrayList<>();
268       putList(a, l);
269     }
270     return l;
271   }
272
273   private void putList(Arg a, List<Map.Entry<Type, String>> l)
274   {
275     bootstrapArgMap.put(a, l);
276   }
277
278   /*
279    * Creates a new list if not used before,
280    * adds the value unless the existing list is non-empty
281    * and the arg is not MULTI (so first expressed value is
282    * retained).
283    */
284   private void add(Arg a, Type t, String s)
285   {
286     List<Map.Entry<Type, String>> l = getOrCreateList(a);
287     if (a.hasOption(Opt.MULTI) || l.size() == 0)
288     {
289       l.add(entry(t, s));
290     }
291   }
292
293   private void addAll(Arg a, Type t, List<String> al)
294   {
295     List<Map.Entry<Type, String>> l = getOrCreateList(a);
296     if (a.hasOption(Opt.MULTI))
297     {
298       for (String s : al)
299       {
300         l.add(entry(t, s));
301       }
302     }
303     else if (l.size() == 0 && al.size() > 0)
304     {
305       l.add(entry(t, al.get(0)));
306     }
307   }
308
309   private static Map.Entry<Type, String> entry(Type t, String s)
310   {
311     return new AbstractMap.SimpleEntry<Type, String>(t, s);
312   }
313
314   /*
315    * Retrieves the first value even if MULTI.
316    * A convenience for non-MULTI args.
317    */
318   public String getValue(Arg a)
319   {
320     if (!bootstrapArgMap.containsKey(a))
321       return null;
322     List<Map.Entry<Type, String>> aL = bootstrapArgMap.get(a);
323     return (aL == null || aL.size() == 0) ? null : aL.get(0).getValue();
324   }
325
326   public boolean getBoolean(Arg a, boolean d)
327   {
328     if (!bootstrapArgMap.containsKey(a))
329       return d;
330     return Boolean.parseBoolean(getValue(a));
331   }
332
333   public boolean getBoolean(Arg a)
334   {
335     if (!(a.hasOption(Opt.BOOLEAN) || a.hasOption(Opt.UNARY)))
336     {
337       return false;
338     }
339     if (bootstrapArgMap.containsKey(a))
340     {
341       return Boolean.parseBoolean(getValue(a));
342     }
343     else
344     {
345       return a.getDefaultBoolValue();
346     }
347   }
348
349   public boolean argsHaveOption(Opt opt)
350   {
351     return argsOptions.contains(opt);
352   }
353
354   public boolean argsHaveType(Type type)
355   {
356     return argsTypes.contains(type);
357   }
358
359   public boolean isHeadless()
360   {
361     boolean isHeadless = false;
362     if (this.argsHaveType(Type.HELP))
363     {
364       // --help, --help-all, ... specified => headless
365       isHeadless = true;
366     }
367     else if (this.contains(Arg.VERSION))
368     {
369       // --version specified => headless
370       isHeadless = true;
371     }
372     else if (this.contains(Arg.GUI))
373     {
374       // --gui specified => forced NOT headless
375       isHeadless = !this.getBoolean(Arg.GUI);
376     }
377     else if (this.contains(Arg.HEADLESS))
378     {
379       // --headless, --noheadless specified => use value
380       isHeadless = this.getBoolean(Arg.HEADLESS);
381     }
382     else if (this.argsHaveOption(Opt.OUTPUTFILE))
383     {
384       // --output file.fa, --image pic.png, --structureimage struct.png =>
385       // assume headless unless above has been already specified
386       isHeadless = true;
387     }
388     return isHeadless;
389   }
390
391   public boolean outputToStdout()
392   {
393     return this.outputToStdout;
394   }
395 }