JAL-4269 Now using --width, --height, --scale for both --image and --structureimage...
[jalview.git] / src / jalview / bin / argparser / ArgValuesMap.java
1 package jalview.bin.argparser;
2
3 import java.io.File;
4 import java.util.ArrayList;
5 import java.util.Collections;
6 import java.util.HashMap;
7 import java.util.List;
8 import java.util.Locale;
9 import java.util.Map;
10 import java.util.Set;
11
12 import jalview.bin.Cache;
13 import jalview.bin.Console;
14 import jalview.bin.argparser.Arg.Opt;
15 import jalview.bin.argparser.Arg.Type;
16 import jalview.util.FileUtils;
17
18 /**
19  * Helper class to allow easy extraction of information about specific argument
20  * values (without having to check for null etc all the time)
21  */
22 public class ArgValuesMap
23 {
24   private List<ArgInfo> argInfoList = new ArrayList<>();
25
26   protected Map<Arg, ArgValues> m;
27
28   private String linkedId;
29
30   protected ArgValuesMap(String linkedId)
31   {
32     this.linkedId = linkedId;
33     this.newMap();
34   }
35
36   protected ArgValuesMap(String linkedId, Map<Arg, ArgValues> map)
37   {
38     this.linkedId = linkedId;
39     this.m = map;
40   }
41
42   public String getLinkedId()
43   {
44     return linkedId;
45   }
46
47   private Map<Arg, ArgValues> getMap()
48   {
49     return m;
50   }
51
52   private void newMap()
53   {
54     m = new HashMap<Arg, ArgValues>();
55   }
56
57   private void newArg(Arg a)
58   {
59     if (m == null)
60       newMap();
61     if (!containsArg(a))
62       m.put(a, new ArgValues(a));
63   }
64
65   public ArgValues getArgValues(Arg a)
66   {
67     return m == null ? null : m.get(a);
68   }
69
70   public ArgValues getOrCreateArgValues(Arg a)
71   {
72     ArgValues avs = m.get(a);
73     if (avs == null)
74       newArg(a);
75     return getArgValues(a);
76   }
77
78   public List<ArgValue> getArgValueList(Arg a)
79   {
80     ArgValues avs = getArgValues(a);
81     return avs == null ? new ArrayList<>() : avs.getArgValueList();
82   }
83
84   public ArgValue getArgValue(Arg a)
85   {
86     List<ArgValue> vals = getArgValueList(a);
87     return (vals == null || vals.size() == 0) ? null : vals.get(0);
88   }
89
90   public String getValue(Arg a)
91   {
92     ArgValue av = getArgValue(a);
93     return av == null ? null : av.getValue();
94   }
95
96   public boolean containsArg(Arg a)
97   {
98     if (m == null || !m.containsKey(a))
99       return false;
100     return a.hasOption(Opt.STRING) ? getArgValue(a) != null : true;
101   }
102
103   public boolean hasValue(Arg a, String val)
104   {
105     if (m == null || !m.containsKey(a))
106       return false;
107     for (ArgValue av : getArgValueList(a))
108     {
109       String avVal = av.getValue();
110       if ((val == null && avVal == null)
111               || (val != null && val.equals(avVal)))
112       {
113         return true;
114       }
115     }
116     return false;
117   }
118
119   public boolean getBoolean(Arg a)
120   {
121     ArgValues av = getArgValues(a);
122     return av == null ? false : av.getBoolean();
123   }
124
125   public Set<Arg> getArgKeys()
126   {
127     return m.keySet();
128   }
129
130   public ArgValue getArgValueOfArgWithSubValKey(Arg a, String svKey)
131   {
132     return getArgValueOfArgWithSubValKey(a, svKey, false);
133   }
134
135   public ArgValue getArgValueOfArgWithSubValKey(Arg a, String svKey,
136           boolean last)
137   {
138     ArgValues avs = this.getArgValues(a);
139     if (avs == null)
140     {
141       return null;
142     }
143     List<ArgValue> compareAvs = avs.getArgValueList();
144     for (int i = 0; i < compareAvs.size(); i++)
145     {
146       int index = last ? compareAvs.size() - 1 - i : i;
147       ArgValue av = compareAvs.get(index);
148       SubVals sv = av.getSubVals();
149       if (sv.has(svKey) && !sv.get(svKey).equals("false"))
150       {
151         return av;
152       }
153     }
154     return null;
155   }
156
157   public ArgValue getClosestPreviousArgValueOfArg(ArgValue thisAv, Arg a)
158   {
159     ArgValue closestAv = null;
160     int thisArgIndex = thisAv.getArgIndex();
161     ArgValues compareAvs = this.getArgValues(a);
162     int closestPreviousIndex = -1;
163     for (ArgValue av : compareAvs.getArgValueList())
164     {
165       int argIndex = av.getArgIndex();
166       if (argIndex < thisArgIndex && argIndex > closestPreviousIndex)
167       {
168         closestPreviousIndex = argIndex;
169         closestAv = av;
170       }
171     }
172     return closestAv;
173   }
174
175   public ArgValue getClosestNextArgValueOfArg(ArgValue thisAv, Arg a,
176           boolean withinType)
177   {
178     // this looks for the *next* arg that *might* be referring back to
179     // a thisAv. Such an arg would have no subValues (if it does it should
180     // specify an id in the subValues so wouldn't need to be guessed).
181     ArgValue closestAv = null;
182     int thisArgIndex = thisAv.getArgIndex();
183     if (!containsArg(a))
184       return null;
185     ArgValues compareAvs = this.getArgValues(a);
186     int closestNextIndex = Integer.MAX_VALUE;
187     for (ArgValue av : compareAvs.getArgValueList())
188     {
189       int argIndex = av.getArgIndex();
190       if (argIndex > thisArgIndex && argIndex < closestNextIndex)
191       {
192         closestNextIndex = argIndex;
193         closestAv = av;
194       }
195     }
196
197     // check if withinType this closestAv doesn't belong to the next primary arg
198     // of this type
199     if (withinType && closestAv != null)
200     {
201       int nextPrimaryArgOfSameTypeIndex = Integer.MAX_VALUE;
202       for (Arg tmpA : this.getArgKeys())
203       {
204         // interested in Opt.PRIMARY args of the same type
205         if (tmpA.getType() == a.getType() && tmpA.hasOption(Opt.PRIMARY))
206         {
207           for (ArgValue tmpAv : getArgValueList(tmpA))
208           {
209             int tmpArgIndex = tmpAv.getArgIndex();
210             if (tmpArgIndex > thisArgIndex
211                     && tmpArgIndex < nextPrimaryArgOfSameTypeIndex)
212             {
213               nextPrimaryArgOfSameTypeIndex = tmpArgIndex;
214             }
215           }
216         }
217       }
218       if (nextPrimaryArgOfSameTypeIndex < closestAv.getArgIndex())
219       {
220         // looks licke closestAv actually belongs to a different primary Arg
221         return null;
222       }
223     }
224
225     return closestAv;
226   }
227
228   // TODO this is incomplete and currently unused (fortunately)
229   public ArgValue[] getArgValuesReferringTo(String key, String value, Arg a)
230   {
231     // this looks for the *next* arg that *might* be referring back to
232     // a thisAv. Such an arg would have no subValues (if it does it should
233     // specify an id in the subValues so wouldn't need to be guessed).
234     List<ArgValue> avList = new ArrayList<>();
235     Arg[] args = a == null ? (Arg[]) this.getMap().keySet().toArray()
236             : new Arg[]
237             { a };
238     for (Arg keyArg : args)
239     {
240       for (ArgValue av : this.getArgValueList(keyArg))
241       {
242
243       }
244     }
245     return (ArgValue[]) avList.toArray();
246   }
247
248   public boolean hasId(Arg a, String id)
249   {
250     ArgValues avs = this.getArgValues(a);
251     return avs == null ? false : avs.hasId(id);
252   }
253
254   public ArgValue getId(Arg a, String id)
255   {
256     ArgValues avs = this.getArgValues(a);
257     return avs == null ? null : avs.getId(id);
258   }
259
260   /*
261    * This method returns the basename of the first --append or --open value. 
262    * Used primarily for substitutions in output filenames.
263    */
264   public String getBasename()
265   {
266     return getDirBasenameOrExtension(false, false, false);
267   }
268
269   /*
270    * This method returns the basename of the first --append or --open value. 
271    * Used primarily for substitutions in output filenames.
272    */
273   public String getExtension()
274   {
275     return getDirBasenameOrExtension(false, true, false);
276   }
277
278   /*
279    * This method returns the dirname of the first --append or --open value. 
280    * Used primarily for substitutions in output filenames.
281    */
282   public String getDirname()
283   {
284     return getDirBasenameOrExtension(true, false, false);
285   }
286
287   public String getDirBasenameOrExtension(boolean dirname,
288           boolean extension, boolean absoluteDirname)
289   {
290     String filename = null;
291     String appendVal = getValue(Arg.APPEND);
292     String openVal = getValue(Arg.OPEN);
293     if (appendVal != null)
294       filename = appendVal;
295     if (filename == null && openVal != null)
296       filename = openVal;
297     if (filename == null)
298       return null;
299
300     File file = new File(filename);
301     if (dirname)
302     {
303       return FileUtils.getDirname(file);
304     }
305     return extension ? FileUtils.getExtension(file)
306             : FileUtils.getBasename(file);
307   }
308
309   /*
310    * Checks if there is an Arg with Opt
311    */
312   public boolean hasArgWithOption(Opt o)
313   {
314     for (Arg a : getArgKeys())
315     {
316       if (a.hasOption(o))
317         return true;
318     }
319     return false;
320   }
321
322   /*
323    * ArgInfo is a more straightforward list of arguments and their info
324    */
325
326   public void addArgInfo(Arg arg, String value, SubVals subVals,
327           int argIndex)
328   {
329     argInfoList.add(new ArgInfo(arg, value, subVals, argIndex));
330   }
331
332   public List<ArgInfo> getArgInfoList()
333   {
334     Collections.sort(argInfoList);
335     return argInfoList;
336   }
337
338   /**
339    * get from following Arg of type a or subval of same name (lowercase)
340    */
341   public String getValueFromSubValOrArg(ArgValue av, Arg a, SubVals sv)
342   {
343     return getFromSubValArgOrPref(av, a, sv, null, null, null);
344   }
345
346   /**
347    * get from following Arg of type a or subval key or preference pref or
348    * default def
349    */
350   public String getFromSubValArgOrPref(ArgValue av, Arg a, SubVals sv,
351           String key, String pref, String def)
352   {
353     return getFromSubValArgOrPref(a, ArgValuesMap.Position.AFTER, av, sv,
354             key, pref, def);
355   }
356
357   /**
358    * get from following(AFTER), first occurence of (FIRST) or previous (BEFORE)
359    * Arg of type a or subval key or preference pref or default def
360    */
361   public String getFromSubValArgOrPref(Arg a, ArgValuesMap.Position pos,
362           ArgValue av, SubVals sv, String key, String pref, String def)
363   {
364     return getFromSubValArgOrPrefWithSubstitutions(null, a, pos, av, sv,
365             key, pref, def);
366   }
367
368   public String getFromSubValArgOrPrefWithSubstitutions(ArgParser ap, Arg a,
369           ArgValuesMap.Position pos, ArgValue av, SubVals sv, String key,
370           String pref, String def)
371   {
372     return getFromSubValArgOrPrefWithSubstitutionsWithinType(ap, a, pos, av,
373             sv, key, pref, def, true);
374   }
375
376   public String getFromSubValArgOrPrefWithSubstitutionsWithinType(
377           ArgParser ap, Arg a, ArgValuesMap.Position pos, ArgValue av,
378           SubVals sv, String key, String pref, String def,
379           boolean withinType)
380   {
381     if (key == null)
382       key = a.getName();
383     String value = null;
384     if (sv != null && sv.has(key) && sv.get(key) != null)
385     {
386       value = ap == null ? sv.get(key)
387               : sv.getWithSubstitutions(ap, getLinkedId(), key);
388     }
389     else if (containsArg(a))
390     {
391       if (pos == ArgValuesMap.Position.FIRST && getValue(a) != null)
392         value = getValue(a);
393       else if (pos == ArgValuesMap.Position.BEFORE
394               && getClosestPreviousArgValueOfArg(av, a) != null)
395         value = getClosestPreviousArgValueOfArg(av, a).getValue();
396       else if (pos == ArgValuesMap.Position.AFTER
397               && getClosestNextArgValueOfArg(av, a, withinType) != null)
398         value = getClosestNextArgValueOfArg(av, a, withinType).getValue();
399
400       // look for allstructures subval for Type.STRUCTURE
401       Arg arg = av.getArg();
402       if (value == null && arg.hasOption(Opt.PRIMARY)
403               && arg.getType() == Type.STRUCTURE
404               && !a.hasOption(Opt.PRIMARY) && (a.getType() == Type.STRUCTURE
405               // || a.getType() == Type.STRUCTUREIMAGE))
406               ))
407       {
408         ArgValue av2 = getArgValueOfArgWithSubValKey(a,
409                 Arg.ALLSTRUCTURES.getName());
410         if (av2 != null)
411         {
412           value = av2.getValue();
413         }
414       }
415     }
416     if (value == null)
417     {
418       value = pref != null ? Cache.getDefault(pref, def) : def;
419     }
420     return value;
421   }
422
423   public boolean getBoolFromSubValOrArg(Arg a, SubVals sv)
424   {
425     return getFromSubValArgOrPref(a, sv, null, null, false);
426   }
427
428   public boolean getFromSubValArgOrPref(Arg a, SubVals sv, String key,
429           String pref, boolean def)
430   {
431     return getFromSubValArgOrPref(a, sv, key, pref, def, false);
432   }
433
434   public boolean getFromSubValArgOrPref(Arg a, SubVals sv, String key,
435           String pref, boolean def, boolean invertPref)
436   {
437     if ((key == null && a == null) || (sv == null && a == null))
438       return false;
439
440     boolean usingArgKey = false;
441     if (key == null)
442     {
443       key = a.getName();
444       usingArgKey = true;
445     }
446
447     String nokey = ArgParser.NEGATESTRING + key;
448
449     // look for key or nokey in subvals first (if using Arg check options)
450     if (sv != null)
451     {
452       // check for true boolean
453       if (sv.has(key) && sv.get(key) != null)
454       {
455         if (usingArgKey)
456         {
457           if (!(a.hasOption(Opt.BOOLEAN) || a.hasOption(Opt.UNARY)))
458           {
459             Console.debug(
460                     "Looking for boolean in subval from non-boolean/non-unary Arg "
461                             + a.getName());
462             return false;
463           }
464         }
465         return sv.get(key).toLowerCase(Locale.ROOT).equals("true");
466       }
467
468       // check for negative boolean (subval "no..." will be "true")
469       if (sv.has(nokey) && sv.get(nokey) != null)
470       {
471         if (usingArgKey)
472         {
473           if (!(a.hasOption(Opt.BOOLEAN)))
474           {
475             Console.debug(
476                     "Looking for negative boolean in subval from non-boolean Arg "
477                             + a.getName());
478             return false;
479           }
480         }
481         return !sv.get(nokey).toLowerCase(Locale.ROOT).equals("true");
482       }
483     }
484
485     // check argvalues
486     if (containsArg(a))
487       return getBoolean(a);
488
489     // return preference or default
490     boolean prefVal = pref != null ? Cache.getDefault(pref, def) : false;
491     return pref != null ? (invertPref ? !prefVal : prefVal) : def;
492   }
493
494   public class ArgInfo implements Comparable<ArgInfo>
495   {
496     private Arg arg;
497
498     private String value;
499
500     private SubVals subVals;
501
502     private int argIndex;
503
504     public ArgInfo(Arg arg, String value, SubVals subVals, int argIndex)
505     {
506       this.arg = arg;
507       this.value = value;
508       this.subVals = subVals;
509       this.argIndex = argIndex;
510     }
511
512     public Arg arg()
513     {
514       return arg;
515     }
516
517     public String value()
518     {
519       return value;
520     }
521
522     public SubVals subVals()
523     {
524       return subVals;
525     }
526
527     public int argIndex()
528     {
529       return argIndex;
530     }
531
532     @Override
533     public int compareTo(ArgInfo ai2)
534     {
535       return Integer.compare(this.argIndex(), ai2.argIndex());
536     }
537   }
538
539   public static enum Position
540   {
541     FIRST, BEFORE, AFTER
542   }
543 }