219983f7dfe9c323b40565a9d04b174c98e7c396
[jalview.git] / src / jalview / bin / argparser / ArgValuesMap.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.ArrayList;
25 import java.util.Arrays;
26 import java.util.Collections;
27 import java.util.HashMap;
28 import java.util.HashSet;
29 import java.util.List;
30 import java.util.Locale;
31 import java.util.Map;
32 import java.util.Set;
33
34 import jalview.bin.Cache;
35 import jalview.bin.Console;
36 import jalview.bin.argparser.Arg.Opt;
37 import jalview.bin.argparser.Arg.Type;
38 import jalview.util.FileUtils;
39
40 /**
41  * Helper class to allow easy extraction of information about specific argument
42  * values (without having to check for null etc all the time)
43  */
44 public class ArgValuesMap
45 {
46   private List<ArgInfo> argInfoList = new ArrayList<>();
47
48   protected Map<Arg, ArgValues> m;
49
50   private String linkedId;
51
52   protected ArgValuesMap(String linkedId)
53   {
54     this.linkedId = linkedId;
55     this.newMap();
56   }
57
58   protected ArgValuesMap(String linkedId, Map<Arg, ArgValues> map)
59   {
60     this.linkedId = linkedId;
61     this.m = map;
62   }
63
64   public String getLinkedId()
65   {
66     return linkedId;
67   }
68
69   private Map<Arg, ArgValues> getMap()
70   {
71     return m;
72   }
73
74   private void newMap()
75   {
76     m = new HashMap<Arg, ArgValues>();
77   }
78
79   private void newArg(Arg a)
80   {
81     if (m == null)
82     {
83       newMap();
84     }
85     if (!containsArg(a))
86     {
87       m.put(a, new ArgValues(a, this));
88     }
89   }
90
91   public ArgValues getArgValues(Arg a)
92   {
93     return m == null ? null : m.get(a);
94   }
95
96   public ArgValues getOrCreateArgValues(Arg a)
97   {
98     ArgValues avs = m.get(a);
99     if (avs == null)
100       newArg(a);
101     return getArgValues(a);
102   }
103
104   public List<ArgValue> getArgValueList(Arg a)
105   {
106     ArgValues avs = getArgValues(a);
107     return avs == null ? new ArrayList<>() : avs.getArgValueList();
108   }
109
110   public List<ArgValue> getArgValueListFromSubValOrArg(ArgValue av, Arg a,
111           SubVals sv)
112   {
113     return getArgValueListFromSubValArgOrPrefWithSubstitutionsWithinTypes(
114             null, a, Position.AFTER, av, sv, null, null, null, true, null);
115   }
116
117   public List<ArgValue> getArgValueListFromSubValArgOrPrefWithSubstitutionsWithinTypes(
118           ArgParser ap, Arg a, ArgValuesMap.Position pos, ArgValue av,
119           SubVals sv, String key, String pref, String def,
120           boolean withinTypes, Type type)
121   {
122     if (key == null)
123     {
124       key = a.getName();
125     }
126     Set<Type> types = new HashSet<>();
127     if (type == null)
128     {
129       types.addAll(Arrays.asList(av.getArg().getTypes()));
130     }
131     else
132     {
133       types.add(type);
134     }
135     List<ArgValue> avList = new ArrayList<>();
136     if (sv != null && sv.has(key) && sv.get(key) != null)
137     {
138       String value = ap == null ? sv.get(key)
139               : sv.getWithSubstitutions(ap, getLinkedId(), key);
140       // protected ArgValue(Arg a, SubVals sv, Type type, String content, int
141       // argIndex)
142
143       ArgValue svav = new ArgValue(a, null, null, value, av.getArgIndex(),
144               false, null, this.getLinkedId());
145       avList.add(svav);
146     }
147     else if (containsArg(a))
148     {
149       if (pos == ArgValuesMap.Position.FIRST && getValue(a) != null)
150         avList.add(getArgValue(a));
151       else if (pos == ArgValuesMap.Position.BEFORE
152               && getClosestPreviousArgValueOfArg(av, a) != null)
153       {
154         for (ArgValue tmpAv : getArgValues(a).getArgValueList())
155         {
156           if (tmpAv.getArgIndex() >= av.getArgIndex())
157           {
158             continue;
159           }
160           avList.add(tmpAv);
161         }
162       }
163       else if (pos == ArgValuesMap.Position.AFTER
164               && getClosestNextArgValueOfArg(av, a, withinTypes) != null)
165       {
166         for (ArgValue tmpAv : getArgValues(a).getArgValueList())
167         {
168           if (tmpAv.getArgIndex() <= av.getArgIndex())
169           {
170             continue;
171           }
172           avList.add(tmpAv);
173         }
174       }
175     }
176
177     // check if withinType the avs don't belong to the next primary arg
178     // of this type. Checking for *any* shared type.
179     if (withinTypes && !avList.isEmpty())
180     {
181       int nextPrimaryArgOfSameTypeIndex = Integer.MAX_VALUE;
182       // run through every Arg used in this ArgValuesMap
183       for (Arg tmpA : this.getArgKeys())
184       {
185         // only interested in looking up to next Opt.PRIMARY args of the same
186         // type as av (or provided type)
187         if (tmpA.hasType(types) && tmpA.hasOption(Opt.PRIMARY))
188         {
189           for (ArgValue tmpAv : getArgValueList(tmpA))
190           {
191             int tmpArgIndex = tmpAv.getArgIndex();
192             if (tmpArgIndex > av.getArgIndex()
193                     && tmpArgIndex < nextPrimaryArgOfSameTypeIndex)
194             {
195               nextPrimaryArgOfSameTypeIndex = tmpArgIndex;
196             }
197           }
198         }
199       }
200       List<ArgValue> tmpList = new ArrayList<>();
201       for (ArgValue tmpAv : avList)
202       {
203         int tmpAvIndex = tmpAv.getArgIndex();
204         if (av.getArgIndex() < tmpAvIndex
205                 && tmpAvIndex < nextPrimaryArgOfSameTypeIndex)
206         {
207           tmpList.add(tmpAv);
208         }
209       }
210       avList = tmpList;
211     }
212
213     return avList;
214   }
215
216   public ArgValue getArgValue(Arg a)
217   {
218     List<ArgValue> vals = getArgValueList(a);
219     return (vals == null || vals.size() == 0) ? null : vals.get(0);
220   }
221
222   public String getValue(Arg a)
223   {
224     ArgValue av = getArgValue(a);
225     return av == null ? null : av.getValue();
226   }
227
228   public List<String> getValues(Arg a)
229   {
230     return toValues(getArgValueList(a));
231   }
232
233   public static List<String> toValues(List<ArgValue> avl)
234   {
235     if (avl == null)
236     {
237       return null;
238     }
239     List<String> vl = new ArrayList<>();
240     for (ArgValue av : avl)
241     {
242       vl.add(av.getValue());
243     }
244     return vl;
245   }
246
247   public boolean containsArg(Arg a)
248   {
249     if (m == null || !m.containsKey(a))
250       return false;
251     return a.hasOption(Opt.STRING) ? getArgValue(a) != null : true;
252   }
253
254   public boolean hasValue(Arg a, String val)
255   {
256     if (m == null || !m.containsKey(a))
257       return false;
258     for (ArgValue av : getArgValueList(a))
259     {
260       String avVal = av.getValue();
261       if ((val == null && avVal == null)
262               || (val != null && val.equals(avVal)))
263       {
264         return true;
265       }
266     }
267     return false;
268   }
269
270   public boolean getBoolean(Arg a)
271   {
272     ArgValues av = getArgValues(a);
273     return av == null ? false : av.getBoolean();
274   }
275
276   public Set<Arg> getArgKeys()
277   {
278     return m.keySet();
279   }
280
281   public ArgValue getArgValueOfArgWithSubValKey(Arg a, String svKey)
282   {
283     return getArgValueOfArgWithSubValKey(a, svKey, false);
284   }
285
286   public ArgValue getArgValueOfArgWithSubValKey(Arg a, String svKey,
287           boolean last)
288   {
289     ArgValues avs = this.getArgValues(a);
290     if (avs == null)
291     {
292       return null;
293     }
294     List<ArgValue> compareAvs = avs.getArgValueList();
295     for (int i = 0; i < compareAvs.size(); i++)
296     {
297       int index = last ? compareAvs.size() - 1 - i : i;
298       ArgValue av = compareAvs.get(index);
299       SubVals sv = av.getSubVals();
300       if (sv.has(svKey) && !sv.get(svKey).equals("false"))
301       {
302         return av;
303       }
304     }
305     return null;
306   }
307
308   public ArgValue getClosestPreviousArgValueOfArg(ArgValue thisAv, Arg a)
309   {
310     ArgValue closestAv = null;
311     int thisArgIndex = thisAv.getArgIndex();
312     ArgValues compareAvs = this.getArgValues(a);
313     int closestPreviousIndex = -1;
314     for (ArgValue av : compareAvs.getArgValueList())
315     {
316       int argIndex = av.getArgIndex();
317       if (argIndex < thisArgIndex && argIndex > closestPreviousIndex)
318       {
319         closestPreviousIndex = argIndex;
320         closestAv = av;
321       }
322     }
323     return closestAv;
324   }
325
326   public ArgValue getClosestNextArgValueOfArg(ArgValue thisAv, Arg a,
327           boolean withinTypes)
328   {
329     // this looks for the *next* arg that *might* be referring back to
330     // a thisAv. Such an arg would have no subValues (if it does it should
331     // specify an id in the subValues so wouldn't need to be guessed).
332     ArgValue closestAv = null;
333     int thisArgIndex = thisAv.getArgIndex();
334     if (!containsArg(a))
335       return null;
336     ArgValues compareAvs = this.getArgValues(a);
337     int closestNextIndex = Integer.MAX_VALUE;
338     for (ArgValue av : compareAvs.getArgValueList())
339     {
340       int argIndex = av.getArgIndex();
341       if (argIndex > thisArgIndex && argIndex < closestNextIndex)
342       {
343         closestNextIndex = argIndex;
344         closestAv = av;
345       }
346     }
347
348     // check if withinType this closestAv doesn't belong to the next primary arg
349     // of this type. Checking for *any* shared type.
350     if (withinTypes && closestAv != null)
351     {
352       int nextPrimaryArgOfSameTypeIndex = Integer.MAX_VALUE;
353       for (Arg tmpA : this.getArgKeys())
354       {
355         // interested in Opt.PRIMARY args of the same type
356         if (tmpA.sharesType(a) && tmpA.hasOption(Opt.PRIMARY))
357         {
358           for (ArgValue tmpAv : getArgValueList(tmpA))
359           {
360             int tmpArgIndex = tmpAv.getArgIndex();
361             if (tmpArgIndex > thisArgIndex
362                     && tmpArgIndex < nextPrimaryArgOfSameTypeIndex)
363             {
364               nextPrimaryArgOfSameTypeIndex = tmpArgIndex;
365             }
366           }
367         }
368       }
369       if (nextPrimaryArgOfSameTypeIndex < closestAv.getArgIndex())
370       {
371         // looks like closestAv actually belongs to a different primary Arg
372         return null;
373       }
374     }
375
376     return closestAv;
377   }
378
379   // TODO this is incomplete and currently unused (fortunately)
380   public ArgValue[] getArgValuesReferringTo(String key, String value, Arg a)
381   {
382     // this looks for the *next* arg that *might* be referring back to
383     // a thisAv. Such an arg would have no subValues (if it does it should
384     // specify an id in the subValues so wouldn't need to be guessed).
385     List<ArgValue> avList = new ArrayList<>();
386     Arg[] args = a == null ? (Arg[]) this.getMap().keySet().toArray()
387             : new Arg[]
388             { a };
389     for (Arg keyArg : args)
390     {
391       for (ArgValue av : this.getArgValueList(keyArg))
392       {
393
394       }
395     }
396     return (ArgValue[]) avList.toArray();
397   }
398
399   public boolean hasId(Arg a, String id)
400   {
401     ArgValues avs = this.getArgValues(a);
402     return avs == null ? false : avs.hasId(id);
403   }
404
405   public ArgValue getId(Arg a, String id)
406   {
407     ArgValues avs = this.getArgValues(a);
408     return avs == null ? null : avs.getId(id);
409   }
410
411   /*
412    * This method returns the basename of the first --append or --open value. 
413    * Used primarily for substitutions in output filenames.
414    */
415   public String getBasename()
416   {
417     return getDirBasenameOrExtension(false, false, false);
418   }
419
420   /*
421    * This method returns the basename of the first --append or --open value. 
422    * Used primarily for substitutions in output filenames.
423    */
424   public String getExtension()
425   {
426     return getDirBasenameOrExtension(false, true, false);
427   }
428
429   /*
430    * This method returns the dirname of the first --append or --open value. 
431    * Used primarily for substitutions in output filenames.
432    */
433   public String getDirname()
434   {
435     return getDirBasenameOrExtension(true, false, false);
436   }
437
438   public String getDirBasenameOrExtension(boolean dirname,
439           boolean extension, boolean absoluteDirname)
440   {
441     String filename = null;
442     String appendVal = getValue(Arg.APPEND);
443     String openVal = getValue(Arg.OPEN);
444     if (appendVal != null)
445       filename = appendVal;
446     if (filename == null && openVal != null)
447       filename = openVal;
448     if (filename == null)
449       return null;
450
451     File file = new File(filename);
452     if (dirname)
453     {
454       return FileUtils.getDirname(file);
455     }
456     return extension ? FileUtils.getExtension(file)
457             : FileUtils.getBasename(file);
458   }
459
460   /*
461    * Checks if there is an Arg with Opt
462    */
463   public boolean hasArgWithOption(Opt o)
464   {
465     for (Arg a : getArgKeys())
466     {
467       if (a.hasOption(o))
468         return true;
469     }
470     return false;
471   }
472
473   /*
474    * ArgInfo is a more straightforward list of arguments and their info
475    */
476
477   public void addArgInfo(Arg arg, String value, SubVals subVals,
478           int argIndex)
479   {
480     argInfoList.add(new ArgInfo(arg, value, subVals, argIndex));
481   }
482
483   public List<ArgInfo> getArgInfoList()
484   {
485     Collections.sort(argInfoList);
486     return argInfoList;
487   }
488
489   /**
490    * get from following Arg of type a or subval of same name (lowercase)
491    */
492   public String getValueFromSubValOrArg(ArgValue av, Arg a, SubVals sv)
493   {
494     return getFromSubValArgOrPref(av, a, sv, null, null, null);
495   }
496
497   /**
498    * get from following Arg of type a or subval key or preference pref or
499    * default def
500    */
501   public String getFromSubValArgOrPref(ArgValue av, Arg a, SubVals sv,
502           String key, String pref, String def)
503   {
504     return getFromSubValArgOrPref(a, Position.AFTER, av, sv, key, pref,
505             def);
506   }
507
508   /**
509    * get from following(AFTER), first occurence of (FIRST) or previous (BEFORE)
510    * Arg of type a or subval key or preference pref or default def
511    */
512   public String getFromSubValArgOrPref(Arg a, Position pos, ArgValue av,
513           SubVals sv, String key, String pref, String def)
514   {
515     return getFromSubValArgOrPrefWithSubstitutions(null, a, pos, av, sv,
516             key, pref, def);
517   }
518
519   public String getFromSubValArgOrPrefWithSubstitutions(ArgParser ap, Arg a,
520           Position pos, ArgValue av, SubVals sv, String key, String pref,
521           String def)
522   {
523     return getFromSubValArgOrPrefWithSubstitutionsWithinTypes(ap, a, pos,
524             av, sv, key, pref, def, true);
525   }
526
527   public String getFromSubValArgOrPrefWithSubstitutionsWithinTypes(
528           ArgParser ap, Arg a, Position pos, ArgValue av, SubVals sv,
529           String key, String pref, String def, boolean withinTypes)
530   {
531     if (key == null)
532       key = a.getName();
533     String value = null;
534     if (sv != null && sv.has(key) && sv.get(key) != null)
535     {
536       value = ap == null ? sv.get(key)
537               : sv.getWithSubstitutions(ap, getLinkedId(), key);
538     }
539     else if (containsArg(a))
540     {
541       if (pos == ArgValuesMap.Position.FIRST && getValue(a) != null)
542         value = getValue(a);
543       else if (pos == ArgValuesMap.Position.BEFORE
544               && getClosestPreviousArgValueOfArg(av, a) != null)
545         value = getClosestPreviousArgValueOfArg(av, a).getValue();
546       else if (pos == ArgValuesMap.Position.AFTER
547               && getClosestNextArgValueOfArg(av, a, withinTypes) != null)
548         value = getClosestNextArgValueOfArg(av, a, withinTypes).getValue();
549
550       // look for allstructures subval for Type.STRUCTURE
551       Arg arg = av.getArg();
552       if (value == null && arg.hasOption(Opt.PRIMARY)
553               && arg.hasType(Type.STRUCTURE) && !a.hasOption(Opt.PRIMARY)
554               && (a.getFirstType() == Type.STRUCTURE
555               // || a.getType() == Type.STRUCTUREIMAGE))
556               ))
557       {
558         ArgValue av2 = getArgValueOfArgWithSubValKey(a,
559                 Arg.ALLSTRUCTURES.getName());
560         if (av2 != null)
561         {
562           value = av2.getValue();
563         }
564       }
565
566       if (value == null)
567       {
568         // look for --all --a occurrences
569         for (ArgValue tmpAv : this.getArgValueList(a))
570         {
571           if (tmpAv.setByWildcardLinkedId())
572           {
573             value = tmpAv.getValue();
574           }
575         }
576       }
577     }
578     if (value == null)
579     {
580       value = pref != null ? Cache.getDefault(pref, def) : def;
581     }
582     return value;
583   }
584
585   public boolean getBoolFromSubValOrArg(Arg a, SubVals sv)
586   {
587     return getFromSubValArgOrPref(a, sv, null, null, false);
588   }
589
590   public boolean getFromSubValArgOrPref(Arg a, SubVals sv, String key,
591           String pref, boolean def)
592   {
593     return getFromSubValArgOrPref(a, sv, key, pref, def, false);
594   }
595
596   public boolean getFromSubValArgOrPref(Arg a, SubVals sv, String key,
597           String pref, boolean def, boolean invertPref)
598   {
599     if ((key == null && a == null) || (sv == null && a == null))
600       return false;
601
602     boolean usingArgKey = false;
603     if (key == null)
604     {
605       key = a.getName();
606       usingArgKey = true;
607     }
608
609     String nokey = ArgParser.NEGATESTRING + key;
610
611     // look for key or nokey in subvals first (if using Arg check options)
612     if (sv != null)
613     {
614       // check for true boolean
615       if (sv.has(key) && sv.get(key) != null)
616       {
617         if (usingArgKey)
618         {
619           if (!(a.hasOption(Opt.BOOLEAN) || a.hasOption(Opt.UNARY)))
620           {
621             Console.debug(
622                     "Looking for boolean in subval from non-boolean/non-unary Arg "
623                             + a.getName());
624             return false;
625           }
626         }
627         return sv.get(key).toLowerCase(Locale.ROOT).equals("true");
628       }
629
630       // check for negative boolean (subval "no..." will be "true")
631       if (sv.has(nokey) && sv.get(nokey) != null)
632       {
633         if (usingArgKey)
634         {
635           if (!(a.hasOption(Opt.BOOLEAN)))
636           {
637             Console.debug(
638                     "Looking for negative boolean in subval from non-boolean Arg "
639                             + a.getName());
640             return false;
641           }
642         }
643         return !sv.get(nokey).toLowerCase(Locale.ROOT).equals("true");
644       }
645     }
646
647     // check argvalues
648     if (containsArg(a))
649       return getBoolean(a);
650
651     // return preference or default
652     boolean prefVal = pref != null ? Cache.getDefault(pref, def) : false;
653     return pref != null ? (invertPref ? !prefVal : prefVal) : def;
654   }
655
656   public class ArgInfo implements Comparable<ArgInfo>
657   {
658     private Arg arg;
659
660     private String value;
661
662     private SubVals subVals;
663
664     private int argIndex;
665
666     public ArgInfo(Arg arg, String value, SubVals subVals, int argIndex)
667     {
668       this.arg = arg;
669       this.value = value;
670       this.subVals = subVals;
671       this.argIndex = argIndex;
672     }
673
674     public Arg arg()
675     {
676       return arg;
677     }
678
679     public String value()
680     {
681       return value;
682     }
683
684     public SubVals subVals()
685     {
686       return subVals;
687     }
688
689     public int argIndex()
690     {
691       return argIndex;
692     }
693
694     @Override
695     public int compareTo(ArgInfo ai2)
696     {
697       return Integer.compare(this.argIndex(), ai2.argIndex());
698     }
699   }
700
701   public static enum Position
702   {
703     FIRST, BEFORE, AFTER
704   }
705 }