JAL-629 fixes to typed args
[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   public static BootstrapArgs getBootstrapArgs(String[] args)
27   {
28     List<String> argList = new ArrayList<>(Arrays.asList(args));
29     return new BootstrapArgs(argList);
30   }
31
32   public static BootstrapArgs getBootstrapArgs(List<String> args)
33   {
34     return new BootstrapArgs(args);
35   }
36
37   private BootstrapArgs(List<String> args)
38   {
39     parse(args, null);
40   }
41
42   private void parse(List<String> args, File inArgFile)
43   {
44     if (args == null)
45       return;
46     // avoid looping argFiles
47     if (inArgFile != null)
48     {
49       if (argFiles.contains(inArgFile))
50       {
51         System.err.println(
52                 "Looped argfiles detected: '" + inArgFile.getPath() + "'");
53         return;
54       }
55       argFiles.add(inArgFile);
56     }
57
58     for (int i = 0; i < args.size(); i++)
59     {
60       String arg = args.get(i);
61       // look for double-dash, e.g. --arg
62       if (arg.startsWith(ArgParser.DOUBLEDASH))
63       {
64         String argName = null;
65         String val = null;
66         Type type = null;
67         // remove "--"
68         argName = arg.substring(ArgParser.DOUBLEDASH.length());
69
70         // look for equals e.g. --arg=value
71         int equalPos = argName.indexOf(ArgParser.EQUALS);
72         if (equalPos > -1)
73         {
74           val = argName.substring(equalPos + 1);
75           argName = argName.substring(0, equalPos);
76         }
77
78         // check for boolean prepended by "no"
79         if (argName.startsWith(ArgParser.NEGATESTRING)
80                 && ArgParser.argMap.containsKey(
81                         argName.substring(ArgParser.NEGATESTRING.length())))
82         {
83           val = "false";
84           argName = argName.substring(ArgParser.NEGATESTRING.length());
85         }
86
87         // look for type modification e.g. --help-opening
88         int dashPos = argName.indexOf(ArgParser.SINGLEDASH);
89         if (dashPos > -1)
90         {
91           String potentialArgName = argName.substring(0, dashPos);
92           Arg potentialArg = ArgParser.argMap.get(potentialArgName);
93           if (potentialArg != null && potentialArg.hasOption(Opt.HASTYPE))
94           {
95             String typeName = argName.substring(dashPos + 1);
96             try
97             {
98               type = Type.valueOf(typeName.toUpperCase(Locale.ROOT));
99             } catch (IllegalArgumentException e)
100             {
101               type = Type.INVALID;
102             }
103             argName = argName.substring(0, dashPos);
104           }
105         }
106
107         if (ArgParser.argMap.containsKey(argName) && val == null)
108         {
109           val = "true";
110         }
111
112         Arg a = ArgParser.argMap.get(argName);
113
114         if (a == null || !a.hasOption(Opt.BOOTSTRAP))
115         {
116           // not a valid bootstrap arg
117           continue;
118         }
119
120         if (a.hasOption(Opt.STRING))
121         {
122           List<String> vals = null;
123           if (equalPos == -1)
124           {
125             vals = ArgParser.getShellGlobbedFilenameValues(a, args, i + 1);
126           }
127           else
128           {
129             if (a.hasOption(Opt.GLOB))
130             {
131               vals = FileUtils.getFilenamesFromGlob(val);
132             }
133             else
134             {
135               vals = new ArrayList<>();
136               vals.add(val);
137             }
138           }
139           addAll(a, type, vals);
140
141           if (a == Arg.ARGFILE)
142           {
143             for (String filename : vals)
144             {
145               File argFile = new File(filename);
146               parse(ArgParser.readArgFile(argFile), argFile);
147             }
148           }
149         }
150         else
151         {
152           add(a, type, val);
153         }
154       }
155     }
156   }
157
158   public boolean contains(Arg a)
159   {
160     return bootstrapArgMap.containsKey(a);
161   }
162
163   public boolean containsType(Type t)
164   {
165     for (List<Map.Entry<Type, String>> l : bootstrapArgMap.values())
166     {
167       for (Map.Entry<Type, String> e : l)
168       {
169         if (e.getKey() == t)
170           return true;
171       }
172     }
173     return false;
174   }
175
176   public List<Arg> getArgsOfType(Type t)
177   {
178     return getArgsOfType(t, new Opt[] {});
179   }
180
181   public List<Arg> getArgsOfType(Type t, Opt... opts)
182   {
183     List<Arg> args = new ArrayList<>();
184     for (Arg a : bootstrapArgMap.keySet())
185     {
186       if (!a.hasAllOptions(opts))
187         continue;
188
189       List<Map.Entry<Type, String>> l = bootstrapArgMap.get(a);
190       if (l.stream().anyMatch(e -> e.getKey() == t))
191       {
192         args.add(a);
193       }
194     }
195     return args;
196   }
197
198   public List<Map.Entry<Type, String>> getList(Arg a)
199   {
200     return bootstrapArgMap.get(a);
201   }
202
203   public List<String> getValueList(Arg a)
204   {
205     return bootstrapArgMap.get(a).stream().map(e -> e.getValue())
206             .collect(Collectors.toList());
207   }
208
209   private List<Map.Entry<Type, String>> getOrCreateList(Arg a)
210   {
211     List<Map.Entry<Type, String>> l = getList(a);
212     if (l == null)
213     {
214       l = new ArrayList<>();
215       putList(a, l);
216     }
217     return l;
218   }
219
220   private void putList(Arg a, List<Map.Entry<Type, String>> l)
221   {
222     bootstrapArgMap.put(a, l);
223   }
224
225   /*
226    * Creates a new list if not used before,
227    * adds the value unless the existing list is non-empty
228    * and the arg is not MULTI (so first expressed value is
229    * retained).
230    */
231   private void add(Arg a, Type t, String s)
232   {
233     List<Map.Entry<Type, String>> l = getOrCreateList(a);
234     if (a.hasOption(Opt.MULTI) || l.size() == 0)
235     {
236       l.add(entry(t, s));
237     }
238   }
239
240   private void addAll(Arg a, Type t, List<String> al)
241   {
242     List<Map.Entry<Type, String>> l = getOrCreateList(a);
243     if (a.hasOption(Opt.MULTI))
244     {
245       for (String s : al)
246       {
247         l.add(entry(t, s));
248       }
249     }
250     else if (l.size() == 0 && al.size() > 0)
251     {
252       l.add(entry(t, al.get(0)));
253     }
254   }
255
256   private static Map.Entry<Type, String> entry(Type t, String s)
257   {
258     return new AbstractMap.SimpleEntry<Type, String>(t, s);
259   }
260
261   /*
262    * Retrieves the first value even if MULTI.
263    * A convenience for non-MULTI args.
264    */
265   public String get(Arg a)
266   {
267     if (!bootstrapArgMap.containsKey(a))
268       return null;
269     List<Map.Entry<Type, String>> aL = bootstrapArgMap.get(a);
270     return (aL == null || aL.size() == 0) ? null : aL.get(0).getValue();
271   }
272
273   public boolean getBoolean(Arg a, boolean d)
274   {
275     if (!bootstrapArgMap.containsKey(a))
276       return d;
277     return Boolean.parseBoolean(get(a));
278   }
279
280   public boolean getBoolean(Arg a)
281   {
282     if (!(a.hasOption(Opt.BOOLEAN) || a.hasOption(Opt.UNARY)))
283     {
284       return false;
285     }
286     if (bootstrapArgMap.containsKey(a))
287       return Boolean.parseBoolean(get(a));
288     else
289       return a.getDefaultBoolValue();
290   }
291 }