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