8a28b8780927d0cfc10fd764a44a15bf58e9f3ac
[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 boolean containsArg(Arg a)
229   {
230     if (m == null || !m.containsKey(a))
231       return false;
232     return a.hasOption(Opt.STRING) ? getArgValue(a) != null : true;
233   }
234
235   public boolean hasValue(Arg a, String val)
236   {
237     if (m == null || !m.containsKey(a))
238       return false;
239     for (ArgValue av : getArgValueList(a))
240     {
241       String avVal = av.getValue();
242       if ((val == null && avVal == null)
243               || (val != null && val.equals(avVal)))
244       {
245         return true;
246       }
247     }
248     return false;
249   }
250
251   public boolean getBoolean(Arg a)
252   {
253     ArgValues av = getArgValues(a);
254     return av == null ? false : av.getBoolean();
255   }
256
257   public Set<Arg> getArgKeys()
258   {
259     return m.keySet();
260   }
261
262   public ArgValue getArgValueOfArgWithSubValKey(Arg a, String svKey)
263   {
264     return getArgValueOfArgWithSubValKey(a, svKey, false);
265   }
266
267   public ArgValue getArgValueOfArgWithSubValKey(Arg a, String svKey,
268           boolean last)
269   {
270     ArgValues avs = this.getArgValues(a);
271     if (avs == null)
272     {
273       return null;
274     }
275     List<ArgValue> compareAvs = avs.getArgValueList();
276     for (int i = 0; i < compareAvs.size(); i++)
277     {
278       int index = last ? compareAvs.size() - 1 - i : i;
279       ArgValue av = compareAvs.get(index);
280       SubVals sv = av.getSubVals();
281       if (sv.has(svKey) && !sv.get(svKey).equals("false"))
282       {
283         return av;
284       }
285     }
286     return null;
287   }
288
289   public ArgValue getClosestPreviousArgValueOfArg(ArgValue thisAv, Arg a)
290   {
291     ArgValue closestAv = null;
292     int thisArgIndex = thisAv.getArgIndex();
293     ArgValues compareAvs = this.getArgValues(a);
294     int closestPreviousIndex = -1;
295     for (ArgValue av : compareAvs.getArgValueList())
296     {
297       int argIndex = av.getArgIndex();
298       if (argIndex < thisArgIndex && argIndex > closestPreviousIndex)
299       {
300         closestPreviousIndex = argIndex;
301         closestAv = av;
302       }
303     }
304     return closestAv;
305   }
306
307   public ArgValue getClosestNextArgValueOfArg(ArgValue thisAv, Arg a,
308           boolean withinTypes)
309   {
310     // this looks for the *next* arg that *might* be referring back to
311     // a thisAv. Such an arg would have no subValues (if it does it should
312     // specify an id in the subValues so wouldn't need to be guessed).
313     ArgValue closestAv = null;
314     int thisArgIndex = thisAv.getArgIndex();
315     if (!containsArg(a))
316       return null;
317     ArgValues compareAvs = this.getArgValues(a);
318     int closestNextIndex = Integer.MAX_VALUE;
319     for (ArgValue av : compareAvs.getArgValueList())
320     {
321       int argIndex = av.getArgIndex();
322       if (argIndex > thisArgIndex && argIndex < closestNextIndex)
323       {
324         closestNextIndex = argIndex;
325         closestAv = av;
326       }
327     }
328
329     // check if withinType this closestAv doesn't belong to the next primary arg
330     // of this type. Checking for *any* shared type.
331     if (withinTypes && closestAv != null)
332     {
333       int nextPrimaryArgOfSameTypeIndex = Integer.MAX_VALUE;
334       for (Arg tmpA : this.getArgKeys())
335       {
336         // interested in Opt.PRIMARY args of the same type
337         if (tmpA.sharesType(a) && tmpA.hasOption(Opt.PRIMARY))
338         {
339           for (ArgValue tmpAv : getArgValueList(tmpA))
340           {
341             int tmpArgIndex = tmpAv.getArgIndex();
342             if (tmpArgIndex > thisArgIndex
343                     && tmpArgIndex < nextPrimaryArgOfSameTypeIndex)
344             {
345               nextPrimaryArgOfSameTypeIndex = tmpArgIndex;
346             }
347           }
348         }
349       }
350       if (nextPrimaryArgOfSameTypeIndex < closestAv.getArgIndex())
351       {
352         // looks like closestAv actually belongs to a different primary Arg
353         return null;
354       }
355     }
356
357     return closestAv;
358   }
359
360   // TODO this is incomplete and currently unused (fortunately)
361   public ArgValue[] getArgValuesReferringTo(String key, String value, Arg a)
362   {
363     // this looks for the *next* arg that *might* be referring back to
364     // a thisAv. Such an arg would have no subValues (if it does it should
365     // specify an id in the subValues so wouldn't need to be guessed).
366     List<ArgValue> avList = new ArrayList<>();
367     Arg[] args = a == null ? (Arg[]) this.getMap().keySet().toArray()
368             : new Arg[]
369             { a };
370     for (Arg keyArg : args)
371     {
372       for (ArgValue av : this.getArgValueList(keyArg))
373       {
374
375       }
376     }
377     return (ArgValue[]) avList.toArray();
378   }
379
380   public boolean hasId(Arg a, String id)
381   {
382     ArgValues avs = this.getArgValues(a);
383     return avs == null ? false : avs.hasId(id);
384   }
385
386   public ArgValue getId(Arg a, String id)
387   {
388     ArgValues avs = this.getArgValues(a);
389     return avs == null ? null : avs.getId(id);
390   }
391
392   /*
393    * This method returns the basename of the first --append or --open value. 
394    * Used primarily for substitutions in output filenames.
395    */
396   public String getBasename()
397   {
398     return getDirBasenameOrExtension(false, false, false);
399   }
400
401   /*
402    * This method returns the basename of the first --append or --open value. 
403    * Used primarily for substitutions in output filenames.
404    */
405   public String getExtension()
406   {
407     return getDirBasenameOrExtension(false, true, false);
408   }
409
410   /*
411    * This method returns the dirname of the first --append or --open value. 
412    * Used primarily for substitutions in output filenames.
413    */
414   public String getDirname()
415   {
416     return getDirBasenameOrExtension(true, false, false);
417   }
418
419   public String getDirBasenameOrExtension(boolean dirname,
420           boolean extension, boolean absoluteDirname)
421   {
422     String filename = null;
423     String appendVal = getValue(Arg.APPEND);
424     String openVal = getValue(Arg.OPEN);
425     if (appendVal != null)
426       filename = appendVal;
427     if (filename == null && openVal != null)
428       filename = openVal;
429     if (filename == null)
430       return null;
431
432     File file = new File(filename);
433     if (dirname)
434     {
435       return FileUtils.getDirname(file);
436     }
437     return extension ? FileUtils.getExtension(file)
438             : FileUtils.getBasename(file);
439   }
440
441   /*
442    * Checks if there is an Arg with Opt
443    */
444   public boolean hasArgWithOption(Opt o)
445   {
446     for (Arg a : getArgKeys())
447     {
448       if (a.hasOption(o))
449         return true;
450     }
451     return false;
452   }
453
454   /*
455    * ArgInfo is a more straightforward list of arguments and their info
456    */
457
458   public void addArgInfo(Arg arg, String value, SubVals subVals,
459           int argIndex)
460   {
461     argInfoList.add(new ArgInfo(arg, value, subVals, argIndex));
462   }
463
464   public List<ArgInfo> getArgInfoList()
465   {
466     Collections.sort(argInfoList);
467     return argInfoList;
468   }
469
470   /**
471    * get from following Arg of type a or subval of same name (lowercase)
472    */
473   public String getValueFromSubValOrArg(ArgValue av, Arg a, SubVals sv)
474   {
475     return getFromSubValArgOrPref(av, a, sv, null, null, null);
476   }
477
478   /**
479    * get from following Arg of type a or subval key or preference pref or
480    * default def
481    */
482   public String getFromSubValArgOrPref(ArgValue av, Arg a, SubVals sv,
483           String key, String pref, String def)
484   {
485     return getFromSubValArgOrPref(a, Position.AFTER, av, sv, key, pref,
486             def);
487   }
488
489   /**
490    * get from following(AFTER), first occurence of (FIRST) or previous (BEFORE)
491    * Arg of type a or subval key or preference pref or default def
492    */
493   public String getFromSubValArgOrPref(Arg a, Position pos, ArgValue av,
494           SubVals sv, String key, String pref, String def)
495   {
496     return getFromSubValArgOrPrefWithSubstitutions(null, a, pos, av, sv,
497             key, pref, def);
498   }
499
500   public String getFromSubValArgOrPrefWithSubstitutions(ArgParser ap, Arg a,
501           Position pos, ArgValue av, SubVals sv, String key, String pref,
502           String def)
503   {
504     return getFromSubValArgOrPrefWithSubstitutionsWithinTypes(ap, a, pos,
505             av, sv, key, pref, def, true);
506   }
507
508   public String getFromSubValArgOrPrefWithSubstitutionsWithinTypes(
509           ArgParser ap, Arg a, Position pos, ArgValue av, SubVals sv,
510           String key, String pref, String def, boolean withinTypes)
511   {
512     if (key == null)
513       key = a.getName();
514     String value = null;
515     if (sv != null && sv.has(key) && sv.get(key) != null)
516     {
517       value = ap == null ? sv.get(key)
518               : sv.getWithSubstitutions(ap, getLinkedId(), key);
519     }
520     else if (containsArg(a))
521     {
522       if (pos == ArgValuesMap.Position.FIRST && getValue(a) != null)
523         value = getValue(a);
524       else if (pos == ArgValuesMap.Position.BEFORE
525               && getClosestPreviousArgValueOfArg(av, a) != null)
526         value = getClosestPreviousArgValueOfArg(av, a).getValue();
527       else if (pos == ArgValuesMap.Position.AFTER
528               && getClosestNextArgValueOfArg(av, a, withinTypes) != null)
529         value = getClosestNextArgValueOfArg(av, a, withinTypes).getValue();
530
531       // look for allstructures subval for Type.STRUCTURE
532       Arg arg = av.getArg();
533       if (value == null && arg.hasOption(Opt.PRIMARY)
534               && arg.hasType(Type.STRUCTURE) && !a.hasOption(Opt.PRIMARY)
535               && (a.getFirstType() == Type.STRUCTURE
536               // || a.getType() == Type.STRUCTUREIMAGE))
537               ))
538       {
539         ArgValue av2 = getArgValueOfArgWithSubValKey(a,
540                 Arg.ALLSTRUCTURES.getName());
541         if (av2 != null)
542         {
543           value = av2.getValue();
544         }
545       }
546
547       if (value == null)
548       {
549         // look for --all --a occurrences
550         for (ArgValue tmpAv : this.getArgValueList(a))
551         {
552           if (tmpAv.setByWildcardLinkedId())
553           {
554             value = tmpAv.getValue();
555           }
556         }
557       }
558     }
559     if (value == null)
560     {
561       value = pref != null ? Cache.getDefault(pref, def) : def;
562     }
563     return value;
564   }
565
566   public boolean getBoolFromSubValOrArg(Arg a, SubVals sv)
567   {
568     return getFromSubValArgOrPref(a, sv, null, null, false);
569   }
570
571   public boolean getFromSubValArgOrPref(Arg a, SubVals sv, String key,
572           String pref, boolean def)
573   {
574     return getFromSubValArgOrPref(a, sv, key, pref, def, false);
575   }
576
577   public boolean getFromSubValArgOrPref(Arg a, SubVals sv, String key,
578           String pref, boolean def, boolean invertPref)
579   {
580     if ((key == null && a == null) || (sv == null && a == null))
581       return false;
582
583     boolean usingArgKey = false;
584     if (key == null)
585     {
586       key = a.getName();
587       usingArgKey = true;
588     }
589
590     String nokey = ArgParser.NEGATESTRING + key;
591
592     // look for key or nokey in subvals first (if using Arg check options)
593     if (sv != null)
594     {
595       // check for true boolean
596       if (sv.has(key) && sv.get(key) != null)
597       {
598         if (usingArgKey)
599         {
600           if (!(a.hasOption(Opt.BOOLEAN) || a.hasOption(Opt.UNARY)))
601           {
602             Console.debug(
603                     "Looking for boolean in subval from non-boolean/non-unary Arg "
604                             + a.getName());
605             return false;
606           }
607         }
608         return sv.get(key).toLowerCase(Locale.ROOT).equals("true");
609       }
610
611       // check for negative boolean (subval "no..." will be "true")
612       if (sv.has(nokey) && sv.get(nokey) != null)
613       {
614         if (usingArgKey)
615         {
616           if (!(a.hasOption(Opt.BOOLEAN)))
617           {
618             Console.debug(
619                     "Looking for negative boolean in subval from non-boolean Arg "
620                             + a.getName());
621             return false;
622           }
623         }
624         return !sv.get(nokey).toLowerCase(Locale.ROOT).equals("true");
625       }
626     }
627
628     // check argvalues
629     if (containsArg(a))
630       return getBoolean(a);
631
632     // return preference or default
633     boolean prefVal = pref != null ? Cache.getDefault(pref, def) : false;
634     return pref != null ? (invertPref ? !prefVal : prefVal) : def;
635   }
636
637   public class ArgInfo implements Comparable<ArgInfo>
638   {
639     private Arg arg;
640
641     private String value;
642
643     private SubVals subVals;
644
645     private int argIndex;
646
647     public ArgInfo(Arg arg, String value, SubVals subVals, int argIndex)
648     {
649       this.arg = arg;
650       this.value = value;
651       this.subVals = subVals;
652       this.argIndex = argIndex;
653     }
654
655     public Arg arg()
656     {
657       return arg;
658     }
659
660     public String value()
661     {
662       return value;
663     }
664
665     public SubVals subVals()
666     {
667       return subVals;
668     }
669
670     public int argIndex()
671     {
672       return argIndex;
673     }
674
675     @Override
676     public int compareTo(ArgInfo ai2)
677     {
678       return Integer.compare(this.argIndex(), ai2.argIndex());
679     }
680   }
681
682   public static enum Position
683   {
684     FIRST, BEFORE, AFTER
685   }
686 }