Clover icon

Coverage Report

  1. Project Clover database Mon Nov 11 2024 15:05:32 GMT
  2. Package jalview.bin.argparser

File ArgValuesMap.java

 

Coverage histogram

../../../img/srcFileCovDistChart7.png
29% of files have more coverage

Code metrics

146
230
48
3
723
570
158
0.69
4.79
16
3.29

Classes

Class Line # Actions
ArgValuesMap 44 221 152
0.696821569.7%
ArgValuesMap.ArgInfo 674 9 6
0.00%
ArgValuesMap.Position 719 0 0
-1.0 -
 

Contributing tests

This file is covered by 93 tests. .

Source view

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  662 toggle protected ArgValuesMap(String linkedId)
53    {
54  662 this.linkedId = linkedId;
55  662 this.newMap();
56    }
57   
 
58  0 toggle protected ArgValuesMap(String linkedId, Map<Arg, ArgValues> map)
59    {
60  0 this.linkedId = linkedId;
61  0 this.m = map;
62    }
63   
 
64  612 toggle public String getLinkedId()
65    {
66  612 return linkedId;
67    }
68   
 
69  0 toggle private Map<Arg, ArgValues> getMap()
70    {
71  0 return m;
72    }
73   
 
74  662 toggle private void newMap()
75    {
76  662 m = new HashMap<Arg, ArgValues>();
77    }
78   
 
79  1051 toggle private void newArg(Arg a)
80    {
81  1051 if (m == null)
82    {
83  0 newMap();
84    }
85  1051 if (!containsArg(a))
86    {
87  1051 m.put(a, new ArgValues(a, this));
88    }
89    }
90   
 
91  4045 toggle public ArgValues getArgValues(Arg a)
92    {
93  4045 return m == null ? null : m.get(a);
94    }
95   
 
96  1166 toggle public ArgValues getOrCreateArgValues(Arg a)
97    {
98  1166 ArgValues avs = m.get(a);
99  1166 if (avs == null)
100  1051 newArg(a);
101  1166 return getArgValues(a);
102    }
103   
 
104  1956 toggle public List<ArgValue> getArgValueList(Arg a)
105    {
106  1956 ArgValues avs = getArgValues(a);
107  1956 return avs == null ? new ArrayList<>() : avs.getArgValueList();
108    }
109   
 
110  15 toggle public List<ArgValue> getArgValueListFromSubValOrArg(ArgValue av, Arg a,
111    SubVals sv)
112    {
113  15 return getArgValueListFromSubValArgOrPrefWithSubstitutionsWithinTypes(
114    null, a, Position.AFTER, av, sv, null, null, null, true, null);
115    }
116   
 
117  15 toggle 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  15 if (key == null)
123    {
124  15 key = a.getName();
125    }
126  15 Set<Type> types = new HashSet<>();
127  15 if (type == null)
128    {
129  15 types.addAll(Arrays.asList(av.getArg().getTypes()));
130    }
131    else
132    {
133  0 types.add(type);
134    }
135  15 List<ArgValue> avList = new ArrayList<>();
136  15 if (sv != null && sv.has(key) && sv.get(key) != null)
137    {
138  0 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  0 ArgValue svav = new ArgValue(a, null, null, value, av.getArgIndex(),
144    false, null, this.getLinkedId());
145  0 avList.add(svav);
146    }
147  15 else if (containsArg(a))
148    {
149  15 if (pos == ArgValuesMap.Position.FIRST && getValue(a) != null)
150  0 avList.add(getArgValue(a));
151  15 else if (pos == ArgValuesMap.Position.BEFORE
152    && getClosestPreviousArgValueOfArg(av, a) != null)
153    {
154  0 for (ArgValue tmpAv : getArgValues(a).getArgValueList())
155    {
156  0 if (tmpAv.getArgIndex() >= av.getArgIndex())
157    {
158  0 continue;
159    }
160  0 avList.add(tmpAv);
161    }
162    }
163  15 else if (pos == ArgValuesMap.Position.AFTER
164    && getClosestNextArgValueOfArg(av, a, withinTypes) != null)
165    {
166  15 for (ArgValue tmpAv : getArgValues(a).getArgValueList())
167    {
168  49 if (tmpAv.getArgIndex() <= av.getArgIndex())
169    {
170  15 continue;
171    }
172  34 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  15 if (withinTypes && !avList.isEmpty())
180    {
181  15 int nextPrimaryArgOfSameTypeIndex = Integer.MAX_VALUE;
182    // run through every Arg used in this ArgValuesMap
183  15 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  90 if (tmpA.hasType(types) && tmpA.hasOption(Opt.PRIMARY))
188    {
189  15 for (ArgValue tmpAv : getArgValueList(tmpA))
190    {
191  27 int tmpArgIndex = tmpAv.getArgIndex();
192  27 if (tmpArgIndex > av.getArgIndex()
193    && tmpArgIndex < nextPrimaryArgOfSameTypeIndex)
194    {
195  5 nextPrimaryArgOfSameTypeIndex = tmpArgIndex;
196    }
197    }
198    }
199    }
200  15 List<ArgValue> tmpList = new ArrayList<>();
201  15 for (ArgValue tmpAv : avList)
202    {
203  34 int tmpAvIndex = tmpAv.getArgIndex();
204  34 if (av.getArgIndex() < tmpAvIndex
205    && tmpAvIndex < nextPrimaryArgOfSameTypeIndex)
206    {
207  23 tmpList.add(tmpAv);
208    }
209    }
210  15 avList = tmpList;
211    }
212   
213  15 return avList;
214    }
215   
 
216  1209 toggle public ArgValue getArgValue(Arg a)
217    {
218  1209 List<ArgValue> vals = getArgValueList(a);
219  1209 return (vals == null || vals.size() == 0) ? null : vals.get(0);
220    }
221   
 
222  320 toggle public String getValue(Arg a)
223    {
224  320 ArgValue av = getArgValue(a);
225  320 return av == null ? null : av.getValue();
226    }
227   
 
228  0 toggle public List<String> getValues(Arg a)
229    {
230  0 return toValues(getArgValueList(a));
231    }
232   
 
233  9 toggle public static List<String> toValues(List<ArgValue> avl)
234    {
235  9 if (avl == null)
236    {
237  0 return null;
238    }
239  9 List<String> vl = new ArrayList<>();
240  9 for (ArgValue av : avl)
241    {
242  23 vl.add(av.getValue());
243    }
244  9 return vl;
245    }
246   
 
247  4931 toggle public boolean containsArg(Arg a)
248    {
249  4931 if (m == null || !m.containsKey(a))
250  4002 return false;
251  929 return a.hasOption(Opt.STRING) ? getArgValue(a) != null : true;
252    }
253   
 
254  0 toggle public boolean hasValue(Arg a, String val)
255    {
256  0 if (m == null || !m.containsKey(a))
257  0 return false;
258  0 for (ArgValue av : getArgValueList(a))
259    {
260  0 String avVal = av.getValue();
261  0 if ((val == null && avVal == null)
262    || (val != null && val.equals(avVal)))
263    {
264  0 return true;
265    }
266    }
267  0 return false;
268    }
269   
 
270  521 toggle public boolean getBoolean(Arg a)
271    {
272  521 ArgValues av = getArgValues(a);
273  521 return av == null ? false : av.getBoolean();
274    }
275   
 
276  380 toggle public Set<Arg> getArgKeys()
277    {
278  380 return m.keySet();
279    }
280   
 
281  6 toggle public ArgValue getArgValueOfArgWithSubValKey(Arg a, String svKey)
282    {
283  6 return getArgValueOfArgWithSubValKey(a, svKey, false);
284    }
285   
 
286  6 toggle public ArgValue getArgValueOfArgWithSubValKey(Arg a, String svKey,
287    boolean last)
288    {
289  6 ArgValues avs = this.getArgValues(a);
290  6 if (avs == null)
291    {
292  0 return null;
293    }
294  6 List<ArgValue> compareAvs = avs.getArgValueList();
295  12 for (int i = 0; i < compareAvs.size(); i++)
296    {
297  8 int index = last ? compareAvs.size() - 1 - i : i;
298  8 ArgValue av = compareAvs.get(index);
299  8 SubVals sv = av.getSubVals();
300  8 if (sv.has(svKey) && !sv.get(svKey).equals("false"))
301    {
302  2 return av;
303    }
304    }
305  4 return null;
306    }
307   
 
308  0 toggle public ArgValue getClosestPreviousArgValueOfArg(ArgValue thisAv, Arg a)
309    {
310  0 ArgValue closestAv = null;
311  0 int thisArgIndex = thisAv.getArgIndex();
312  0 ArgValues compareAvs = this.getArgValues(a);
313  0 int closestPreviousIndex = -1;
314  0 for (ArgValue av : compareAvs.getArgValueList())
315    {
316  0 int argIndex = av.getArgIndex();
317  0 if (argIndex < thisArgIndex && argIndex > closestPreviousIndex)
318    {
319  0 closestPreviousIndex = argIndex;
320  0 closestAv = av;
321    }
322    }
323  0 return closestAv;
324    }
325   
 
326  297 toggle 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  297 ArgValue closestAv = null;
333  297 int thisArgIndex = thisAv.getArgIndex();
334  297 if (!containsArg(a))
335  30 return null;
336  267 ArgValues compareAvs = this.getArgValues(a);
337  267 int closestNextIndex = Integer.MAX_VALUE;
338  267 for (ArgValue av : compareAvs.getArgValueList())
339    {
340  686 int argIndex = av.getArgIndex();
341  686 if (argIndex > thisArgIndex && argIndex < closestNextIndex)
342    {
343  261 closestNextIndex = argIndex;
344  261 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  267 if (withinTypes && closestAv != null)
351    {
352  261 int nextPrimaryArgOfSameTypeIndex = Integer.MAX_VALUE;
353  261 for (Arg tmpA : this.getArgKeys())
354    {
355    // interested in Opt.PRIMARY args of the same type
356  2011 if (tmpA.sharesType(a) && tmpA.hasOption(Opt.PRIMARY))
357    {
358  262 for (ArgValue tmpAv : getArgValueList(tmpA))
359    {
360  906 int tmpArgIndex = tmpAv.getArgIndex();
361  906 if (tmpArgIndex > thisArgIndex
362    && tmpArgIndex < nextPrimaryArgOfSameTypeIndex)
363    {
364  121 nextPrimaryArgOfSameTypeIndex = tmpArgIndex;
365    }
366    }
367    }
368    }
369  261 if (nextPrimaryArgOfSameTypeIndex < closestAv.getArgIndex())
370    {
371    // looks like closestAv actually belongs to a different primary Arg
372  22 return null;
373    }
374    }
375   
376  245 return closestAv;
377    }
378   
379    // TODO this is incomplete and currently unused (fortunately)
 
380  0 toggle 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  0 List<ArgValue> avList = new ArrayList<>();
386  0 Arg[] args = a == null ? (Arg[]) this.getMap().keySet().toArray()
387    : new Arg[]
388    { a };
389  0 for (Arg keyArg : args)
390    {
391  0 for (ArgValue av : this.getArgValueList(keyArg))
392    {
393   
394    }
395    }
396  0 return (ArgValue[]) avList.toArray();
397    }
398   
 
399  0 toggle public boolean hasId(Arg a, String id)
400    {
401  0 ArgValues avs = this.getArgValues(a);
402  0 return avs == null ? false : avs.hasId(id);
403    }
404   
 
405  0 toggle public ArgValue getId(Arg a, String id)
406    {
407  0 ArgValues avs = this.getArgValues(a);
408  0 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  74 toggle public String getBasename()
416    {
417  74 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  0 toggle public String getExtension()
425    {
426  0 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  86 toggle public String getDirname()
434    {
435  86 return getDirBasenameOrExtension(true, false, false);
436    }
437   
 
438  160 toggle public String getDirBasenameOrExtension(boolean dirname,
439    boolean extension, boolean absoluteDirname)
440    {
441  160 String filename = null;
442  160 String appendVal = getValue(Arg.APPEND);
443  160 String openVal = getValue(Arg.OPEN);
444  160 if (appendVal != null)
445  28 filename = appendVal;
446  160 if (filename == null && openVal != null)
447  132 filename = openVal;
448  160 if (filename == null)
449  0 return null;
450   
451  160 File file = new File(filename);
452  160 if (dirname)
453    {
454  86 return FileUtils.getDirname(file);
455    }
456  74 return extension ? FileUtils.getExtension(file)
457    : FileUtils.getBasename(file);
458    }
459   
460    /*
461    * Checks if there is an Arg with Opt
462    */
 
463  104 toggle public boolean hasArgWithOption(Opt o)
464    {
465  104 for (Arg a : getArgKeys())
466    {
467  130 if (a.hasOption(o))
468  104 return true;
469    }
470  0 return false;
471    }
472   
473    /*
474    * ArgInfo is a more straightforward list of arguments and their info
475    */
476   
 
477  0 toggle public void addArgInfo(Arg arg, String value, SubVals subVals,
478    int argIndex)
479    {
480  0 argInfoList.add(new ArgInfo(arg, value, subVals, argIndex));
481    }
482   
 
483  0 toggle public List<ArgInfo> getArgInfoList()
484    {
485  0 Collections.sort(argInfoList);
486  0 return argInfoList;
487    }
488   
489    /**
490    * get from following Arg of type a or subval of same name (lowercase)
491    */
 
492  815 toggle public String getValueFromSubValOrArg(ArgValue av, Arg a, SubVals sv)
493    {
494  815 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  1119 toggle public String getFromSubValArgOrPref(ArgValue av, Arg a, SubVals sv,
502    String key, String pref, String def)
503    {
504  1119 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  1171 toggle public String getFromSubValArgOrPref(Arg a, Position pos, ArgValue av,
513    SubVals sv, String key, String pref, String def)
514    {
515  1171 return getFromSubValArgOrPrefWithSubstitutions(null, a, pos, av, sv,
516    key, pref, def);
517    }
518   
 
519  1255 toggle public String getFromSubValArgOrPrefWithSubstitutions(ArgParser ap, Arg a,
520    Position pos, ArgValue av, SubVals sv, String key, String pref,
521    String def)
522    {
523  1255 return getFromSubValArgOrPrefWithSubstitutionsWithinTypes(ap, a, pos,
524    av, sv, key, pref, def, true);
525    }
526   
 
527  1255 toggle 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  1255 if (key == null)
532  1255 key = a.getName();
533  1255 String value = null;
534  1255 if (sv != null && sv.has(key) && sv.get(key) != null)
535    {
536  12 value = ap == null ? sv.get(key)
537    : sv.getWithSubstitutions(ap, getLinkedId(), key);
538    }
539  1243 else if (containsArg(a))
540    {
541  134 if (pos == ArgValuesMap.Position.FIRST && getValue(a) != null)
542  0 value = getValue(a);
543  134 else if (pos == ArgValuesMap.Position.BEFORE
544    && getClosestPreviousArgValueOfArg(av, a) != null)
545  0 value = getClosestPreviousArgValueOfArg(av, a).getValue();
546  134 else if (pos == ArgValuesMap.Position.AFTER
547    && getClosestNextArgValueOfArg(av, a, withinTypes) != null)
548  106 value = getClosestNextArgValueOfArg(av, a, withinTypes).getValue();
549   
550    // look for allstructures subval for Type.STRUCTURE
551  134 Arg arg = av.getArg();
552  134 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  6 ArgValue av2 = getArgValueOfArgWithSubValKey(a,
559    Arg.ALLSTRUCTURES.getName());
560  6 if (av2 != null)
561    {
562  2 value = av2.getValue();
563    }
564    }
565   
566  134 if (value == null)
567    {
568    // look for --all --a occurrences
569  26 for (ArgValue tmpAv : this.getArgValueList(a))
570    {
571  89 if (tmpAv.setByWildcardLinkedId())
572    {
573  13 value = tmpAv.getValue();
574    }
575    }
576    }
577    }
578  1255 if (value == null)
579    {
580  1122 value = pref != null ? Cache.getDefault(pref, def) : def;
581    }
582  1255 return value;
583    }
584   
 
585  152 toggle public boolean getBoolFromSubValOrArg(Arg a, SubVals sv)
586    {
587  152 return getFromSubValArgOrPref(a, sv, null, null, false);
588    }
589   
 
590  1026 toggle public boolean getFromSubValArgOrPref(Arg a, SubVals sv, String key,
591    String pref, boolean def)
592    {
593  1026 return getFromSubValArgOrPref(a, sv, key, pref, def, false);
594    }
595   
 
596  1068 toggle public boolean getFromSubValArgOrPref(Arg a, SubVals sv, String key,
597    String pref, boolean def, boolean invertPref)
598    {
599  1068 if ((key == null && a == null) || (sv == null && a == null))
600  0 return false;
601   
602  1068 boolean usingArgKey = false;
603  1068 if (key == null)
604    {
605  1068 key = a.getName();
606  1068 usingArgKey = true;
607    }
608   
609  1068 String nokey = ArgParser.NEGATESTRING + key;
610   
611    // look for key or nokey in subvals first (if using Arg check options)
612  1068 if (sv != null)
613    {
614    // check for true boolean
615  1068 if (sv.has(key) && sv.get(key) != null)
616    {
617  2 if (usingArgKey)
618    {
619  2 if (!(a.hasOption(Opt.BOOLEAN) || a.hasOption(Opt.UNARY)))
620    {
621  0 Console.debug(
622    "Looking for boolean in subval from non-boolean/non-unary Arg "
623    + a.getName());
624  0 return false;
625    }
626    }
627  2 return sv.get(key).toLowerCase(Locale.ROOT).equals("true");
628    }
629   
630    // check for negative boolean (subval "no..." will be "true")
631  1066 if (sv.has(nokey) && sv.get(nokey) != null)
632    {
633  0 if (usingArgKey)
634    {
635  0 if (!(a.hasOption(Opt.BOOLEAN)))
636    {
637  0 Console.debug(
638    "Looking for negative boolean in subval from non-boolean Arg "
639    + a.getName());
640  0 return false;
641    }
642    }
643  0 return !sv.get(nokey).toLowerCase(Locale.ROOT).equals("true");
644    }
645    }
646   
647    // check argvalues
648  1066 if (containsArg(a))
649  67 return getBoolean(a);
650   
651    // return preference or default
652  999 boolean prefVal = pref != null ? Cache.getDefault(pref, def) : false;
653  808 return pref != null ? (invertPref ? !prefVal : prefVal) : def;
654    }
655   
 
656  0 toggle @Override
657    public String toString()
658    {
659  0 StringBuilder sb = new StringBuilder();
660  0 for (Arg a : this.getArgKeys())
661    {
662  0 sb.append(a.argString());
663  0 sb.append(":\n");
664  0 for (ArgValue av : this.getArgValueList(a))
665    {
666  0 sb.append(" ");
667  0 sb.append(av.getValue());
668  0 sb.append("\n");
669    }
670    }
671  0 return sb.toString();
672    }
673   
 
674    public class ArgInfo implements Comparable<ArgInfo>
675    {
676    private Arg arg;
677   
678    private String value;
679   
680    private SubVals subVals;
681   
682    private int argIndex;
683   
 
684  0 toggle public ArgInfo(Arg arg, String value, SubVals subVals, int argIndex)
685    {
686  0 this.arg = arg;
687  0 this.value = value;
688  0 this.subVals = subVals;
689  0 this.argIndex = argIndex;
690    }
691   
 
692  0 toggle public Arg arg()
693    {
694  0 return arg;
695    }
696   
 
697  0 toggle public String value()
698    {
699  0 return value;
700    }
701   
 
702  0 toggle public SubVals subVals()
703    {
704  0 return subVals;
705    }
706   
 
707  0 toggle public int argIndex()
708    {
709  0 return argIndex;
710    }
711   
 
712  0 toggle @Override
713    public int compareTo(ArgInfo ai2)
714    {
715  0 return Integer.compare(this.argIndex(), ai2.argIndex());
716    }
717    }
718   
 
719    public static enum Position
720    {
721    FIRST, BEFORE, AFTER
722    }
723    }