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