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