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