Merge branch 'releases/Release_2_11_3_Branch'
[jalview.git] / src / jalview / datamodel / AlignmentView.java
index b61420c..6ab71c7 100644 (file)
@@ -1,6 +1,6 @@
 /*
- * Jalview - A Sequence Alignment Editor and Viewer (Version 2.8.2)
- * Copyright (C) 2014 The Jalview Authors
+ * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
+ * Copyright (C) $$Year-Rel$$ The Jalview Authors
  * 
  * This file is part of Jalview.
  * 
  */
 package jalview.datamodel;
 
+import jalview.util.MessageManager;
 import jalview.util.ShiftList;
 
 import java.io.PrintStream;
-import java.util.Enumeration;
+import java.util.ArrayList;
 import java.util.List;
-import java.util.Vector;
 
 /**
  * Transient object compactly representing a 'view' of an alignment - with
@@ -46,11 +46,13 @@ public class AlignmentView
    * one or more ScGroup objects, which are referenced by each seqCigar's group
    * membership
    */
-  private Vector scGroups;
+  private List<ScGroup> scGroups = null;
+
+  private boolean isNa = false;
 
-  private boolean isNa=false;
   /**
    * false if the view concerns peptides
+   * 
    * @return
    */
   public boolean isNa()
@@ -66,13 +68,51 @@ public class AlignmentView
    */
   private class ScGroup
   {
-    public Vector seqs;
+    public List<SeqCigar> seqs;
 
     public SequenceGroup sg;
 
     ScGroup()
     {
-      seqs = new Vector();
+      seqs = new ArrayList<>();
+    }
+
+    /**
+     * @param seq
+     * @return true if seq was not a member before and was added to group
+     */
+    public boolean add(SeqCigar seq)
+    {
+      if (!seq.isMemberOf(this))
+      {
+        seqs.add(seq);
+        seq.setGroupMembership(this);
+        return true;
+      }
+      else
+      {
+        return false;
+      }
+    }
+
+    /**
+     * 
+     * @param seq
+     * @return true if seq was a member and was removed from group
+     */
+    public boolean remove(SeqCigar seq)
+    {
+      if (seq.removeGroupMembership(this))
+      {
+        seqs.remove(seq);
+        return true;
+      }
+      return false;
+    }
+
+    public int size()
+    {
+      return seqs.size();
     }
   }
 
@@ -80,7 +120,7 @@ public class AlignmentView
    * vector of selected seqCigars. This vector is also referenced by each
    * seqCigar contained in it.
    */
-  private Vector selected;
+  private ScGroup selected;
 
   /**
    * Construct an alignmentView from a live jalview alignment view. Note -
@@ -100,17 +140,17 @@ public class AlignmentView
    *          - when set, any groups on the given alignment will be marked on
    *          the view
    */
-  public AlignmentView(AlignmentI alignment,
-          ColumnSelection columnSelection, SequenceGroup selection,
-          boolean hasHiddenColumns, boolean selectedRegionOnly,
-          boolean recordGroups)
+  public AlignmentView(AlignmentI alignment, HiddenColumns hidden,
+          SequenceGroup selection, boolean hasHiddenColumns,
+          boolean selectedRegionOnly, boolean recordGroups)
   {
     // refactored from AlignViewport.getAlignmentView(selectedOnly);
     this(new jalview.datamodel.CigarArray(alignment,
-            (hasHiddenColumns ? columnSelection : null),
+            (hasHiddenColumns ? hidden : null),
             (selectedRegionOnly ? selection : null)),
-            (selectedRegionOnly && selection != null) ? selection
-                    .getStartRes() : 0);
+            (selectedRegionOnly && selection != null)
+                    ? selection.getStartRes()
+                    : 0);
     isNa = alignment.isNucleotide();
     // walk down SeqCigar array and Alignment Array - optionally restricted by
     // selected region.
@@ -120,37 +160,33 @@ public class AlignmentView
     SequenceI[] selseqs;
     if (selection != null && selection.getSize() > 0)
     {
-      List<SequenceI> sel = selection.getSequences(null);
-      this.selected = new Vector();
-      selseqs = selection
-              .getSequencesInOrder(alignment, selectedRegionOnly);
+      this.selected = new ScGroup();
+      selseqs = selection.getSequencesInOrder(alignment,
+              selectedRegionOnly);
     }
     else
     {
       selseqs = alignment.getSequencesArray();
     }
 
+    List<List<SequenceI>> seqsets = new ArrayList<>();
     // get the alignment's group list and make a copy
-    Vector grps = new Vector();
+    List<SequenceGroup> grps = new ArrayList<>();
     List<SequenceGroup> gg = alignment.getGroups();
     grps.addAll(gg);
     ScGroup[] sgrps = null;
     boolean addedgps[] = null;
     if (grps != null)
     {
-      SequenceGroup sg;
       if (selection != null && selectedRegionOnly)
       {
         // trim annotation to the region being stored.
         // strip out any groups that do not actually intersect with the
         // visible and selected region
         int ssel = selection.getStartRes(), esel = selection.getEndRes();
-        Vector isg = new Vector();
-        Enumeration en = grps.elements();
-        while (en.hasMoreElements())
+        List<SequenceGroup> isg = new ArrayList<>();
+        for (SequenceGroup sg : grps)
         {
-          sg = (SequenceGroup) en.nextElement();
-
           if (!(sg.getStartRes() > esel || sg.getEndRes() < ssel))
           {
             // adjust bounds of new group, if necessary.
@@ -165,7 +201,7 @@ public class AlignmentView
             sg.setStartRes(sg.getStartRes() - ssel + 1);
             sg.setEndRes(sg.getEndRes() - ssel + 1);
 
-            isg.addElement(sg);
+            isg.add(sg);
           }
         }
         grps = isg;
@@ -175,13 +211,15 @@ public class AlignmentView
       addedgps = new boolean[grps.size()];
       for (int g = 0; g < sgrps.length; g++)
       {
-        sg = (SequenceGroup) grps.elementAt(g);
+        SequenceGroup sg = grps.get(g);
         sgrps[g] = new ScGroup();
         sgrps[g].sg = new SequenceGroup(sg);
         addedgps[g] = false;
-        grps.setElementAt(sg.getSequences(null), g);
+        // can't set entry 0 in an empty list
+        // seqsets.set(g, sg.getSequences(null));
+        seqsets.add(sg.getSequences());
       }
-      // grps now contains vectors (should be sets) for each group, so we can
+      // seqsets now contains vectors (should be sets) for each group, so we can
       // track when we've done with the group
     }
     int csi = 0;
@@ -192,26 +230,24 @@ public class AlignmentView
         if (selection != null && selection.getSize() > 0
                 && !selectedRegionOnly)
         {
-          sequences[csi].setGroupMembership(selected);
-          selected.addElement(sequences[csi]);
+          selected.add(sequences[csi]);
         }
-        if (grps != null)
+        if (seqsets != null)
         {
           for (int sg = 0; sg < sgrps.length; sg++)
           {
-            if (((Vector) grps.elementAt(sg)).contains(selseqs[i]))
+            if ((seqsets.get(sg)).contains(selseqs[i]))
             {
-              sequences[csi].setGroupMembership(sgrps[sg]);
               sgrps[sg].sg.deleteSequence(selseqs[i], false);
-              sgrps[sg].seqs.addElement(sequences[csi]);
+              sgrps[sg].add(sequences[csi]);
               if (!addedgps[sg])
               {
                 if (scGroups == null)
                 {
-                  scGroups = new Vector();
+                  scGroups = new ArrayList<>();
                 }
                 addedgps[sg] = true;
-                scGroups.addElement(sgrps[sg]);
+                scGroups.add(sgrps[sg]);
               }
             }
           }
@@ -293,14 +329,13 @@ public class AlignmentView
    *          char
    * @return Object[] { SequenceI[], ColumnSelection}
    */
-  public Object[] getAlignmentAndColumnSelection(char gapCharacter)
+  public Object[] getAlignmentAndHiddenColumns(char gapCharacter)
   {
-    ColumnSelection colsel = new ColumnSelection();
+    HiddenColumns hidden = new HiddenColumns();
 
-    return new Object[]
-    {
-        SeqCigar.createAlignmentSequences(sequences, gapCharacter, colsel,
-                contigs), colsel };
+    return new Object[] { SeqCigar.createAlignmentSequences(sequences,
+            gapCharacter, hidden, contigs),
+        hidden };
   }
 
   /**
@@ -333,8 +368,8 @@ public class AlignmentView
    *          - true if vcal is alignment of the visible regions of the view
    *          (e.g. as returned from getVisibleAlignment)
    */
-  private void addPrunedGroupsInOrder(AlignmentI vcal, int gstart,
-          int gend, boolean viscontigs)
+  private void addPrunedGroupsInOrder(AlignmentI vcal, int gstart, int gend,
+          boolean viscontigs)
   {
     boolean r = false;
     if (gstart > -1 && gstart <= gend)
@@ -354,7 +389,7 @@ public class AlignmentView
           SequenceGroup[] nsg = new SequenceGroup[nvg];
           for (int g = 0; g < nvg; g++)
           {
-            SequenceGroup sg = ((ScGroup) scGroups.elementAt(g)).sg;
+            SequenceGroup sg = scGroups.get(g).sg;
             if (r)
             {
               if (sg.getStartRes() > gend || sg.getEndRes() < gstart)
@@ -404,8 +439,8 @@ public class AlignmentView
               for (int h = 0; h < contigs.length; h += 3)
               {
                 {
-                  prune.addShift(p + contigs[h + 1], contigs[h + 2]
-                          - contigs[h + 1]);
+                  prune.addShift(p + contigs[h + 1],
+                          contigs[h + 2] - contigs[h + 1]);
                 }
                 p = contigs[h + 1] + contigs[h + 2];
               }
@@ -440,7 +475,7 @@ public class AlignmentView
             for (int g = 0; g < nvg; g++)
             {
               if (nsg[g] != null
-                      && sequences[nsq].isMemberOf(scGroups.elementAt(g)))
+                      && sequences[nsq].isMemberOf(scGroups.get(g)))
               {
                 nsg[g].addSequence(aln[nsq], false);
               }
@@ -464,6 +499,7 @@ public class AlignmentView
    * alignment.
    * 
    * @param c
+   *          gap character to use to recreate the alignment
    * @return
    */
   private SequenceI[] getVisibleSeqs(char c)
@@ -471,13 +507,9 @@ public class AlignmentView
     SequenceI[] aln = new SequenceI[sequences.length];
     for (int i = 0, j = sequences.length; i < j; i++)
     {
-      aln[i] = sequences[i].getSeq('-');
-    }
-    // Remove hidden regions from sequence objects.
-    String seqs[] = getSequenceStrings('-');
-    for (int i = 0, j = aln.length; i < j; i++)
-    {
-      aln[i].setSequence(seqs[i]);
+      aln[i] = sequences[i].getSeq(c);
+      // Remove hidden regions from sequence
+      aln[i].setSequence(getASequenceString(c, i));
     }
     return aln;
   }
@@ -513,8 +545,38 @@ public class AlignmentView
   }
 
   /**
+   * build a string excluding hidden regions from a particular sequence in the
+   * view
+   * 
+   * @param c
+   * @param n
+   * @return
+   */
+  private String getASequenceString(char c, int n)
+  {
+    String sqn;
+    String fullseq = sequences[n].getSequenceString(c);
+    if (contigs != null)
+    {
+      sqn = "";
+      int p = 0;
+      for (int h = 0; h < contigs.length; h += 3)
+      {
+        sqn += fullseq.substring(p, contigs[h + 1]);
+        p = contigs[h + 1] + contigs[h + 2];
+      }
+      sqn += fullseq.substring(p);
+    }
+    else
+    {
+      sqn = fullseq;
+    }
+    return sqn;
+  }
+
+  /**
    * get an array of visible sequence strings for a view on an alignment using
-   * the given gap character
+   * the given gap character uses getASequenceString
    * 
    * @param c
    *          char
@@ -525,22 +587,7 @@ public class AlignmentView
     String[] seqs = new String[sequences.length];
     for (int n = 0; n < sequences.length; n++)
     {
-      String fullseq = sequences[n].getSequenceString(c);
-      if (contigs != null)
-      {
-        seqs[n] = "";
-        int p = 0;
-        for (int h = 0; h < contigs.length; h += 3)
-        {
-          seqs[n] += fullseq.substring(p, contigs[h + 1]);
-          p = contigs[h + 1] + contigs[h + 2];
-        }
-        seqs[n] += fullseq.substring(p);
-      }
-      else
-      {
-        seqs[n] = fullseq;
-      }
+      seqs[n] = getASequenceString(c, n);
     }
     return seqs;
   }
@@ -603,8 +650,8 @@ public class AlignmentView
           SequenceI mseq[] = new SequenceI[sequences.length];
           for (int s = 0; s < mseq.length; s++)
           {
-            mseq[s] = sequences[s].getSeq(gapCharacter).getSubSequence(
-                    start, contigs[contig + 1]);
+            mseq[s] = sequences[s].getSeq(gapCharacter)
+                    .getSubSequence(start, contigs[contig + 1]);
           }
           smsa[j] = mseq;
           j++;
@@ -650,7 +697,8 @@ public class AlignmentView
   {
     if (sequences == null || width <= 0)
     {
-      throw new Error("empty view cannot be updated.");
+      throw new Error(MessageManager
+              .getString("error.empty_view_cannot_be_updated"));
     }
     if (nvismsa == null)
     {
@@ -660,7 +708,8 @@ public class AlignmentView
     if (contigs != null && contigs.length > 0)
     {
       SequenceI[] alignment = new SequenceI[sequences.length];
-      ColumnSelection columnselection = new ColumnSelection();
+      // ColumnSelection columnselection = new ColumnSelection();
+      HiddenColumns hidden = new HiddenColumns();
       if (contigs != null && contigs.length > 0)
       {
         int start = 0;
@@ -680,11 +729,13 @@ public class AlignmentView
               j++;
               if (mseq.length != sequences.length)
               {
-                throw new Error(
-                        "Mismatch between number of sequences in block "
-                                + j + " (" + mseq.length
-                                + ") and the original view ("
-                                + sequences.length + ")");
+                throw new Error(MessageManager.formatMessage(
+                        "error.mismatch_between_number_of_sequences_in_block",
+                        new String[]
+                        { Integer.valueOf(j).toString(),
+                            Integer.valueOf(mseq.length).toString(),
+                            Integer.valueOf(sequences.length)
+                                    .toString() }));
               }
               swidth = mseq[0].getLength(); // JBPNote: could ensure padded
               // here.
@@ -696,9 +747,9 @@ public class AlignmentView
                 }
                 else
                 {
-                  alignment[s].setSequence(alignment[s]
-                          .getSequenceAsString()
-                          + mseq[s].getSequenceAsString());
+                  alignment[s]
+                          .setSequence(alignment[s].getSequenceAsString()
+                                  + mseq[s].getSequenceAsString());
                   if (mseq[s].getStart() <= mseq[s].getEnd())
                   {
                     alignment[s].setEnd(mseq[s].getEnd());
@@ -730,9 +781,9 @@ public class AlignmentView
                   }
                   else
                   {
-                    alignment[s].setSequence(alignment[s]
-                            .getSequenceAsString()
-                            + oseq.getSequenceAsString());
+                    alignment[s]
+                            .setSequence(alignment[s].getSequenceAsString()
+                                    + oseq.getSequenceAsString());
                     if (oseq.getEnd() >= oseq.getStart())
                     {
                       alignment[s].setEnd(oseq.getEnd());
@@ -767,8 +818,7 @@ public class AlignmentView
             }
           }
           // mark hidden segment as hidden in the new alignment
-          columnselection.hideColumns(nwidth, nwidth + contigs[contig + 2]
-                  - 1);
+          hidden.hideColumns(nwidth, nwidth + contigs[contig + 2] - 1);
           nwidth += contigs[contig + 2];
         }
         // Do final segment - if it exists
@@ -823,9 +873,9 @@ public class AlignmentView
                   }
                   else
                   {
-                    alignment[s].setSequence(alignment[s]
-                            .getSequenceAsString()
-                            + oseq.getSequenceAsString());
+                    alignment[s]
+                            .setSequence(alignment[s].getSequenceAsString()
+                                    + oseq.getSequenceAsString());
                     if (oseq.getEnd() >= oseq.getStart())
                     {
                       alignment[s].setEnd(oseq.getEnd());
@@ -837,31 +887,31 @@ public class AlignmentView
               else
               {
                 // place gaps.
-                throw new Error("Padding not yet implemented.");
+                throw new Error(MessageManager
+                        .getString("error.padding_not_yet_implemented"));
               }
             }
           }
         }
       }
-      return new Object[]
-      { alignment, columnselection };
+      return new Object[] { alignment, hidden };
     }
     else
     {
       if (nvismsa.length != 1)
       {
-        throw new Error(
-                "Mismatch between visible blocks to update and number of contigs in view (contigs=0,blocks="
-                        + nvismsa.length);
+        throw new Error(MessageManager.formatMessage(
+                "error.mismatch_between_visible_blocks_to_update_and_number_of_contigs_in_view",
+                new String[]
+                { Integer.valueOf(nvismsa.length).toString() }));
       }
       if (nvismsa[0] != null)
       {
-        return new Object[]
-        { nvismsa[0], new ColumnSelection() };
+        return new Object[] { nvismsa[0], new HiddenColumns() };
       }
       else
       {
-        return getAlignmentAndColumnSelection(gapCharacter);
+        return getAlignmentAndHiddenColumns(gapCharacter);
       }
     }
   }
@@ -911,15 +961,14 @@ public class AlignmentView
       if (start < fwidth)
       {
         viscontigs[nvis] = start;
-        viscontigs[nvis + 1] = fwidth; // end is inclusive
+        viscontigs[nvis + 1] = fwidth - 1; // end is inclusive
         nvis += 2;
       }
       return viscontigs;
     }
     else
     {
-      return new int[]
-      { 0, width };
+      return new int[] { 0, width - 1 };
     }
   }
 
@@ -1042,17 +1091,17 @@ public class AlignmentView
               + " groups defined on the view.");
       for (int g = 0; g < view.scGroups.size(); g++)
       {
-        ScGroup sgr = (ScGroup) view.scGroups.elementAt(g);
+        ScGroup sgr = view.scGroups.get(g);
         os.println("Group " + g + ": Name = " + sgr.sg.getName()
                 + " Contains " + sgr.seqs.size() + " Seqs.");
         os.println("This group runs from " + sgr.sg.getStartRes() + " to "
                 + sgr.sg.getEndRes());
         for (int s = 0; s < sgr.seqs.size(); s++)
         {
-          if (!((SeqCigar) sgr.seqs.elementAt(s)).isMemberOf(sgr))
+          // JBPnote this should be a unit test for ScGroup
+          if (!sgr.seqs.get(s).isMemberOf(sgr))
           {
-            os.println("** WARNING: sequence "
-                    + ((SeqCigar) sgr.seqs.elementAt(s)).toString()
+            os.println("** WARNING: sequence " + sgr.seqs.get(s).toString()
                     + " is not marked as member of group.");
           }
         }
@@ -1077,113 +1126,114 @@ public class AlignmentView
   }
 
   public static void testSelectionViews(AlignmentI alignment,
-          ColumnSelection csel, SequenceGroup selection)
+          HiddenColumns hidden, SequenceGroup selection)
   {
-    System.out.println("Testing standard view creation:\n");
+    jalview.bin.Console.outPrintln("Testing standard view creation:\n");
     AlignmentView view = null;
     try
     {
-      System.out
-              .println("View with no hidden columns, no limit to selection, no groups to be collected:");
-      view = new AlignmentView(alignment, csel, selection, false, false,
+      jalview.bin.Console.outPrintln(
+              "View with no hidden columns, no limit to selection, no groups to be collected:");
+      view = new AlignmentView(alignment, hidden, selection, false, false,
               false);
       summariseAlignmentView(view, System.out);
 
     } catch (Exception e)
     {
       e.printStackTrace();
-      System.err
-              .println("Failed to generate alignment with selection but no groups marked.");
+      jalview.bin.Console.errPrintln(
+              "Failed to generate alignment with selection but no groups marked.");
     }
     try
     {
-      System.out
-              .println("View with no hidden columns, no limit to selection, and all groups to be collected:");
-      view = new AlignmentView(alignment, csel, selection, false, false,
+      jalview.bin.Console.outPrintln(
+              "View with no hidden columns, no limit to selection, and all groups to be collected:");
+      view = new AlignmentView(alignment, hidden, selection, false, false,
               true);
       summariseAlignmentView(view, System.out);
     } catch (Exception e)
     {
       e.printStackTrace();
-      System.err
-              .println("Failed to generate alignment with selection marked but no groups marked.");
+      jalview.bin.Console.errPrintln(
+              "Failed to generate alignment with selection marked but no groups marked.");
     }
     try
     {
-      System.out
-              .println("View with no hidden columns, limited to selection and no groups to be collected:");
-      view = new AlignmentView(alignment, csel, selection, false, true,
+      jalview.bin.Console.outPrintln(
+              "View with no hidden columns, limited to selection and no groups to be collected:");
+      view = new AlignmentView(alignment, hidden, selection, false, true,
               false);
       summariseAlignmentView(view, System.out);
     } catch (Exception e)
     {
       e.printStackTrace();
-      System.err
-              .println("Failed to generate alignment with selection restricted but no groups marked.");
+      jalview.bin.Console.errPrintln(
+              "Failed to generate alignment with selection restricted but no groups marked.");
     }
     try
     {
-      System.out
-              .println("View with no hidden columns, limited to selection, and all groups to be collected:");
-      view = new AlignmentView(alignment, csel, selection, false, true,
+      jalview.bin.Console.outPrintln(
+              "View with no hidden columns, limited to selection, and all groups to be collected:");
+      view = new AlignmentView(alignment, hidden, selection, false, true,
               true);
       summariseAlignmentView(view, System.out);
     } catch (Exception e)
     {
       e.printStackTrace();
-      System.err
-              .println("Failed to generate alignment with selection restricted and groups marked.");
+      jalview.bin.Console.errPrintln(
+              "Failed to generate alignment with selection restricted and groups marked.");
     }
     try
     {
-      System.out
-              .println("View *with* hidden columns, no limit to selection, no groups to be collected:");
-      view = new AlignmentView(alignment, csel, selection, true, false,
+      jalview.bin.Console.outPrintln(
+              "View *with* hidden columns, no limit to selection, no groups to be collected:");
+      view = new AlignmentView(alignment, hidden, selection, true, false,
               false);
       summariseAlignmentView(view, System.out);
     } catch (Exception e)
     {
       e.printStackTrace();
-      System.err
-              .println("Failed to generate alignment with selection but no groups marked.");
+      jalview.bin.Console.errPrintln(
+              "Failed to generate alignment with selection but no groups marked.");
     }
     try
     {
-      System.out
-              .println("View *with* hidden columns, no limit to selection, and all groups to be collected:");
-      view = new AlignmentView(alignment, csel, selection, true, false,
+      jalview.bin.Console.outPrintln(
+              "View *with* hidden columns, no limit to selection, and all groups to be collected:");
+      view = new AlignmentView(alignment, hidden, selection, true, false,
               true);
       summariseAlignmentView(view, System.out);
     } catch (Exception e)
     {
       e.printStackTrace();
-      System.err
-              .println("Failed to generate alignment with selection marked but no groups marked.");
+      jalview.bin.Console.errPrintln(
+              "Failed to generate alignment with selection marked but no groups marked.");
     }
     try
     {
-      System.out
-              .println("View *with* hidden columns, limited to selection and no groups to be collected:");
-      view = new AlignmentView(alignment, csel, selection, true, true,
+      jalview.bin.Console.outPrintln(
+              "View *with* hidden columns, limited to selection and no groups to be collected:");
+      view = new AlignmentView(alignment, hidden, selection, true, true,
               false);
       summariseAlignmentView(view, System.out);
     } catch (Exception e)
     {
       e.printStackTrace();
-      System.err
-              .println("Failed to generate alignment with selection restricted but no groups marked.");
+      jalview.bin.Console.errPrintln(
+              "Failed to generate alignment with selection restricted but no groups marked.");
     }
     try
     {
-      System.out
-              .println("View *with* hidden columns, limited to selection, and all groups to be collected:");
-      view = new AlignmentView(alignment, csel, selection, true, true, true);
+      jalview.bin.Console.outPrintln(
+              "View *with* hidden columns, limited to selection, and all groups to be collected:");
+      view = new AlignmentView(alignment, hidden, selection, true, true,
+              true);
       summariseAlignmentView(view, System.out);
     } catch (Exception e)
     {
       e.printStackTrace();
-      System.err
-              .println("Failed to generate alignment with selection restricted and groups marked.");
+      jalview.bin.Console.errPrintln(
+              "Failed to generate alignment with selection restricted and groups marked.");
     }
 
   }