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