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