Merge branch 'develop' into features/JAL-2446NCList features/JAL-2446NCList
authorgmungoc <g.m.carstairs@dundee.ac.uk>
Mon, 21 Aug 2017 13:06:46 +0000 (14:06 +0100)
committergmungoc <g.m.carstairs@dundee.ac.uk>
Mon, 21 Aug 2017 13:06:46 +0000 (14:06 +0100)
Conflicts:
src/MCview/PDBChain.java
src/jalview/analysis/AlignmentSorter.java
src/jalview/analysis/AlignmentUtils.java
src/jalview/analysis/Dna.java
src/jalview/appletgui/AlignFrame.java
src/jalview/appletgui/FeatureRenderer.java
src/jalview/appletgui/Finder.java
src/jalview/appletgui/SeqPanel.java
src/jalview/commands/EditCommand.java
src/jalview/datamodel/AlignedCodonFrame.java
src/jalview/datamodel/Sequence.java
src/jalview/datamodel/SequenceFeature.java
src/jalview/datamodel/xdb/embl/EmblEntry.java
src/jalview/ext/ensembl/EnsemblGene.java
src/jalview/gui/AlignmentPanel.java
src/jalview/gui/AnnotationExporter.java
src/jalview/gui/CutAndPasteTransfer.java
src/jalview/gui/FeatureRenderer.java
src/jalview/gui/Finder.java
src/jalview/gui/PopupMenu.java
src/jalview/gui/SeqCanvas.java
src/jalview/gui/SeqPanel.java
src/jalview/io/FeaturesFile.java
src/jalview/io/JSONFile.java
src/jalview/io/gff/InterProScanHelper.java
src/jalview/io/vamsas/Datasetsequence.java
src/jalview/renderer/seqfeatures/FeatureRenderer.java
src/jalview/util/RangeComparator.java
src/jalview/viewmodel/seqfeatures/FeatureRendererModel.java
src/jalview/ws/jws2/AADisorderClient.java

81 files changed:
1  2 
src/MCview/PDBChain.java
src/jalview/analysis/AAFrequency.java
src/jalview/analysis/AlignmentSorter.java
src/jalview/analysis/AlignmentUtils.java
src/jalview/analysis/Conservation.java
src/jalview/analysis/CrossRef.java
src/jalview/analysis/Dna.java
src/jalview/analysis/Rna.java
src/jalview/analysis/SeqsetUtils.java
src/jalview/analysis/scoremodels/FeatureDistanceModel.java
src/jalview/appletgui/APopupMenu.java
src/jalview/appletgui/AlignFrame.java
src/jalview/appletgui/FeatureColourChooser.java
src/jalview/appletgui/FeatureRenderer.java
src/jalview/appletgui/FeatureSettings.java
src/jalview/appletgui/Finder.java
src/jalview/appletgui/IdPanel.java
src/jalview/appletgui/SeqCanvas.java
src/jalview/appletgui/SeqPanel.java
src/jalview/commands/EditCommand.java
src/jalview/controller/AlignViewController.java
src/jalview/datamodel/AlignedCodonFrame.java
src/jalview/datamodel/Alignment.java
src/jalview/datamodel/AlignmentAnnotation.java
src/jalview/datamodel/BinarySequence.java
src/jalview/datamodel/Mapping.java
src/jalview/datamodel/SearchResults.java
src/jalview/datamodel/Sequence.java
src/jalview/datamodel/SequenceFeature.java
src/jalview/datamodel/xdb/embl/EmblEntry.java
src/jalview/ext/ensembl/EnsemblGene.java
src/jalview/ext/ensembl/EnsemblSeqProxy.java
src/jalview/ext/jmol/JmolParser.java
src/jalview/ext/rbvi/chimera/AtomSpecModel.java
src/jalview/ext/rbvi/chimera/ChimeraCommands.java
src/jalview/gui/AlignFrame.java
src/jalview/gui/AlignmentPanel.java
src/jalview/gui/AnnotationExporter.java
src/jalview/gui/AppVarna.java
src/jalview/gui/CutAndPasteTransfer.java
src/jalview/gui/FeatureColourChooser.java
src/jalview/gui/FeatureRenderer.java
src/jalview/gui/FeatureSettings.java
src/jalview/gui/Finder.java
src/jalview/gui/IdPanel.java
src/jalview/gui/Jalview2XML.java
src/jalview/gui/Jalview2XML_V1.java
src/jalview/gui/PopupMenu.java
src/jalview/gui/SeqCanvas.java
src/jalview/gui/SeqPanel.java
src/jalview/gui/SequenceFetcher.java
src/jalview/gui/TreePanel.java
src/jalview/io/ClustalFile.java
src/jalview/io/FeaturesFile.java
src/jalview/io/IdentifyFile.java
src/jalview/io/JSONFile.java
src/jalview/io/JnetAnnotationMaker.java
src/jalview/io/MSFfile.java
src/jalview/io/PfamFile.java
src/jalview/io/PhylipFile.java
src/jalview/io/PileUpfile.java
src/jalview/io/SequenceAnnotationReport.java
src/jalview/io/StockholmFile.java
src/jalview/io/StructureFile.java
src/jalview/io/gff/ExonerateHelper.java
src/jalview/io/gff/Gff3Helper.java
src/jalview/io/gff/GffHelperBase.java
src/jalview/io/gff/InterProScanHelper.java
src/jalview/io/vamsas/Datasetsequence.java
src/jalview/io/vamsas/Sequencefeature.java
src/jalview/renderer/seqfeatures/FeatureColourFinder.java
src/jalview/renderer/seqfeatures/FeatureRenderer.java
src/jalview/schemes/ClustalxColourScheme.java
src/jalview/schemes/FeatureColour.java
src/jalview/util/Comparison.java
src/jalview/viewmodel/ViewportRanges.java
src/jalview/viewmodel/seqfeatures/FeatureRendererModel.java
src/jalview/ws/DBRefFetcher.java
src/jalview/ws/dbsources/Uniprot.java
src/jalview/ws/jws2/AADisorderClient.java
src/jalview/ws/rest/params/SeqVector.java

diff --combined src/MCview/PDBChain.java
@@@ -41,11 -41,11 +41,6 @@@ public class PDBChai
  {
    public static final String RESNUM_FEATURE = "RESNUM";
  
--  /**
--   * SequenceFeature group for PDB File features added to sequences
--   */
--  private static final String PDBFILEFEATURE = "PDBFile";
--
    private static final String IEASTATUS = "IEA:jalview";
  
    public String id;
  
    public String pdbid = "";
  
--  public PDBChain(String pdbid, String id)
++  public PDBChain(String thePdbid, String theId)
    {
--    this.pdbid = pdbid == null ? pdbid : pdbid.toLowerCase();
--    this.id = id;
++    this.pdbid = thePdbid == null ? thePdbid : thePdbid.toLowerCase();
++    this.id = theId;
    }
  
    /**
    }
  
    /**
 -   * copy over the RESNUM seqfeatures from the internal chain sequence to the
 +   * Copies over the RESNUM seqfeatures from the internal chain sequence to the
     * mapped sequence
     * 
     * @param seq
     * @param status
     *          The Status of the transferred annotation
 -   * @return the features added to sq (or its dataset)
     */
 -  public SequenceFeature[] transferRESNUMFeatures(SequenceI seq,
 +  public void transferRESNUMFeatures(SequenceI seq,
            String status)
    {
      SequenceI sq = seq;
        sq = sq.getDatasetSequence();
        if (sq == sequence)
        {
 -        return null;
 +        return;
        }
      }
 -    /**
 +
 +    /*
       * Remove any existing features for this chain if they exist ?
       * SequenceFeature[] seqsfeatures=seq.getSequenceFeatures(); int
       * totfeat=seqsfeatures.length; // Remove any features for this exact chain
      {
        status = PDBChain.IEASTATUS;
      }
 -    SequenceFeature[] features = sequence.getSequenceFeatures();
 -    if (features == null)
 -    {
 -      return null;
 -    }
 -    for (int i = 0; i < features.length; i++)
 +
 +    List<SequenceFeature> features = sequence.getSequenceFeatures();
 +    for (SequenceFeature feature : features)
      {
 -      if (features[i].getFeatureGroup() != null
 -              && features[i].getFeatureGroup().equals(pdbid))
 +      if (feature.getFeatureGroup() != null
 +              && feature.getFeatureGroup().equals(pdbid))
        {
 -        SequenceFeature tx = new SequenceFeature(features[i]);
 -        tx.setBegin(1 + residues.elementAt(tx.getBegin() - offset).atoms
 -                .elementAt(0).alignmentMapping);
 -        tx.setEnd(1 + residues.elementAt(tx.getEnd() - offset).atoms
 -                .elementAt(0).alignmentMapping);
 +        int newBegin = 1 + residues.elementAt(feature.getBegin() - offset).atoms
 +                .elementAt(0).alignmentMapping;
 +        int newEnd = 1 + residues.elementAt(feature.getEnd() - offset).atoms
 +                .elementAt(0).alignmentMapping;
 +        SequenceFeature tx = new SequenceFeature(feature, newBegin, newEnd,
 +                feature.getFeatureGroup(), feature.getScore());
          tx.setStatus(status
-                 + ((tx.getStatus() == null || tx.getStatus().length() == 0) ? ""
+                 + ((tx.getStatus() == null || tx.getStatus().length() == 0)
+                         ? ""
                          : ":" + tx.getStatus()));
          if (tx.begin != 0 && tx.end != 0)
          {
          }
        }
      }
 -    return features;
    }
  
    /**
  
        // Add inserted residues as features to the base residue
        Atom currAtom = resAtoms.get(0);
-       if (currAtom.insCode != ' '
-               && !residues.isEmpty()
-               && residues.lastElement().atoms.get(0).resNumber == currAtom.resNumber)
+       if (currAtom.insCode != ' ' && !residues.isEmpty()
+               && residues.lastElement().atoms
+                       .get(0).resNumber == currAtom.resNumber)
        {
 -        SequenceFeature sf = new SequenceFeature("INSERTION",
 -                currAtom.resName + ":" + currAtom.resNumIns + " " + pdbid
 -                        + id,
 -                "", offset + count - 1, offset + count - 1, "PDB_INS");
 +        String desc = currAtom.resName + ":" + currAtom.resNumIns + " "
 +                + pdbid + id;
 +        SequenceFeature sf = new SequenceFeature("INSERTION", desc, offset
 +                + count - 1, offset + count - 1, "PDB_INS");
          resFeatures.addElement(sf);
          residues.lastElement().atoms.addAll(resAtoms);
        }
        else
        {
 -
          // Make a new Residue object with the new atoms vector
          residues.addElement(new Residue(resAtoms, resNumber - 1, count));
  
          Residue tmpres = residues.lastElement();
          Atom tmpat = tmpres.atoms.get(0);
          // Make A new SequenceFeature for the current residue numbering
 -        SequenceFeature sf = new SequenceFeature(RESNUM_FEATURE,
 -                tmpat.resName + ":" + tmpat.resNumIns + " " + pdbid + id,
 -                "", offset + count, offset + count, pdbid);
 +        String desc = tmpat.resName
 +                + ":" + tmpat.resNumIns + " " + pdbid + id;
 +        SequenceFeature sf = new SequenceFeature(RESNUM_FEATURE, desc,
 +                offset + count, offset + count, pdbid);
          resFeatures.addElement(sf);
          resAnnotation.addElement(new Annotation(tmpat.tfactor));
          // Keep totting up the sequence
  
-         if ((symbol = ResidueProperties.getAA3Hash().get(tmpat.resName)) == null)
+         if ((symbol = ResidueProperties.getAA3Hash()
+                 .get(tmpat.resName)) == null)
          {
            String nucname = tmpat.resName.trim();
            // use the aaIndex rather than call 'toLower' - which would take a bit
            // more time.
            deoxyn = nucname.length() == 2
-                   && ResidueProperties.aaIndex[nucname.charAt(0)] == ResidueProperties.aaIndex['D'];
+                   && ResidueProperties.aaIndex[nucname
+                           .charAt(0)] == ResidueProperties.aaIndex['D'];
            if (tmpat.name.equalsIgnoreCase("CA")
                    || ResidueProperties.nucleotideIndex[nucname
                            .charAt((deoxyn ? 1 : 0))] == -1)
            {
-             char r = ResidueProperties
-                     .getSingleCharacterCode(ResidueProperties
-                             .getCanonicalAminoAcid(tmpat.resName));
+             char r = ResidueProperties.getSingleCharacterCode(
+                     ResidueProperties.getCanonicalAminoAcid(tmpat.resName));
              seq.append(r == '0' ? 'X' : r);
              // System.err.println("PDBReader:Null aa3Hash for " +
              // tmpat.resName);
          {
            if (nucleotide)
            {
-             System.err
-                     .println("Warning: mixed nucleotide and amino acid chain.. its gonna do bad things to you!");
+             System.err.println(
+                     "Warning: mixed nucleotide and amino acid chain.. its gonna do bad things to you!");
            }
            seq.append(ResidueProperties.aa[((Integer) symbol).intValue()]);
          }
        try
        {
          index = ResidueProperties.aa3Hash.get(b.at1.resName).intValue();
-         b.startCol = cs.findColour(ResidueProperties.aa[index].charAt(0),
-                 0, null, null, 0f);
+         b.startCol = cs.findColour(ResidueProperties.aa[index].charAt(0), 0,
+                 null, null, 0f);
  
          index = ResidueProperties.aa3Hash.get(b.at2.resName).intValue();
          b.endCol = cs.findColour(ResidueProperties.aa[index].charAt(0), 0,
  
          for (AlignmentAnnotation ana : shadow.getAnnotation())
          {
-           List<AlignmentAnnotation> transfer = sq.getAlignmentAnnotations(
-                   ana.getCalcId(), ana.label);
+           List<AlignmentAnnotation> transfer = sq
+                   .getAlignmentAnnotations(ana.getCalcId(), ana.label);
            if (transfer == null || transfer.size() == 0)
            {
              ana = new AlignmentAnnotation(ana);
          // Useful for debugging mappings - adds annotation for mapped position
          float min = -1, max = 0;
          Annotation[] an = new Annotation[sq.getEnd() - sq.getStart() + 1];
-         for (int i = sq.getStart(), j = sq.getEnd(), k = 0; i <= j; i++, k++)
+         for (int i = sq.getStart(), j = sq
+                 .getEnd(), k = 0; i <= j; i++, k++)
          {
            int prn = mapping.getPDBResNum(k + 1);
  
@@@ -147,13 -147,14 +147,13 @@@ public class AAFrequenc
        {
          if (sequences[row] == null)
          {
-           System.err
-                   .println("WARNING: Consensus skipping null sequence - possible race condition.");
+           System.err.println(
+                   "WARNING: Consensus skipping null sequence - possible race condition.");
            continue;
          }
 -        char[] seq = sequences[row].getSequence();
 -        if (seq.length > column)
 +        if (sequences[row].getLength() > column)
          {
 -          char c = seq[column];
 +          char c = sequences[row].getCharAt(column);
            residueCounts.add(c);
            if (Comparison.isNucleotide(c))
            {
      // always set ranges again
      gaprow.graphMax = nseq;
      gaprow.graphMin = 0;
-     double scale = 0.8/nseq;
+     double scale = 0.8 / nseq;
      for (int i = startCol; i < endCol; i++)
      {
        ProfileI profile = profiles.get(i);
  
        String description = "" + gapped;
  
-       gaprow.annotations[i] = new Annotation("", description,
-               '\0', gapped, jalview.util.ColorUtils.bleachColour(
-                       Color.DARK_GRAY, (float) scale * gapped));
+       gaprow.annotations[i] = new Annotation("", description, '\0', gapped,
+               jalview.util.ColorUtils.bleachColour(Color.DARK_GRAY,
+                       (float) scale * gapped));
      }
    }
  
     * <ul>
     * <li>the full profile (percentages of all residues present), if
     * showSequenceLogo is true, or</li>
-    * <li>just the modal (most common) residue(s), if showSequenceLogo is false</li>
+    * <li>just the modal (most common) residue(s), if showSequenceLogo is
+    * false</li>
     * </ul>
     * Percentages are as a fraction of all sequence, or only ungapped sequences
     * if ignoreGaps is true.
      String description = null;
      if (counts != null && showSequenceLogo)
      {
-       int normaliseBy = ignoreGaps ? profile.getNonGapped() : profile
-               .getHeight();
+       int normaliseBy = ignoreGaps ? profile.getNonGapped()
+               : profile.getHeight();
        description = counts.getTooltip(normaliseBy, dp);
      }
      else
      QuickSort.sort(values, symbols);
      int nextArrayPos = 2;
      int totalPercentage = 0;
-     final int divisor = ignoreGaps ? profile.getNonGapped() : profile
-             .getHeight();
+     final int divisor = ignoreGaps ? profile.getNonGapped()
+             : profile.getHeight();
  
      /*
       * traverse the arrays in reverse order (highest counts first)
          {
            continue;
          }
-         List<char[]> codons = MappingUtils
-                 .findCodonsFor(seq, col, mappings);
+         List<char[]> codons = MappingUtils.findCodonsFor(seq, col,
+                 mappings);
          for (char[] codon : codons)
          {
            int codonEncoded = CodingUtils.encodeCodon(codon);
  
        int modalCodonEncoded = codons[codons.length - 1];
        int modalCodonCount = sortedCodonCounts[codons.length - 1];
-       String modalCodon = String.valueOf(CodingUtils
-               .decodeCodon(modalCodonEncoded));
-       if (sortedCodonCounts.length > 1
-               && sortedCodonCounts[codons.length - 2] == sortedCodonCounts[codons.length - 1])
+       String modalCodon = String
+               .valueOf(CodingUtils.decodeCodon(modalCodonEncoded));
+       if (sortedCodonCounts.length > 1 && sortedCodonCounts[codons.length
+               - 2] == sortedCodonCounts[codons.length - 1])
        {
          /*
           * two or more codons share the modal count
            {
              if (samePercent.length() > 0)
              {
-               mouseOver.append(samePercent).append(": ")
-                       .append(lastPercent).append("% ");
+               mouseOver.append(samePercent).append(": ").append(lastPercent)
+                       .append("% ");
              }
              samePercent.setLength(0);
              samePercent.append(codon);
@@@ -29,11 -29,11 +29,11 @@@ import jalview.datamodel.SequenceFeatur
  import jalview.datamodel.SequenceGroup;
  import jalview.datamodel.SequenceI;
  import jalview.datamodel.SequenceNode;
  import jalview.util.QuickSort;
  
  import java.util.ArrayList;
 -import java.util.Arrays;
 +import java.util.Collections;
 +import java.util.Iterator;
  import java.util.List;
  
  /**
@@@ -53,7 -53,7 +53,7 @@@
   */
  public class AlignmentSorter
  {
 -  /**
 +  /*
     * todo: refactor searches to follow a basic pattern: (search property, last
     * search state, current sort direction)
     */
  
    static boolean sortTreeAscending = true;
  
 -  /**
 -   * last Annotation Label used by sortByScore
 +  /*
 +   * last Annotation Label used for sort by Annotation score
     */
 -  private static String lastSortByScore;
 -
 -  private static boolean sortByScoreAscending = true;
 +  private static String lastSortByAnnotation;
  
 -  /**
 -   * compact representation of last arguments to SortByFeatureScore
 +  /*
 +   * string hash of last arguments to sortByFeature
 +   * (sort order toggles if this is unchanged between sorts)
     */
 -  private static String lastSortByFeatureScore;
 +  private static String sortByFeatureCriteria;
  
 -  private static boolean sortByFeatureScoreAscending = true;
 +  private static boolean sortByFeatureAscending = true;
  
    private static boolean sortLengthAscending;
  
              true);
      for (int i = 0; i < nSeq; i++)
      {
-       scores[i] = (float) PIDModel.computePID(align.getSequenceAt(i)
-               .getSequenceAsString(), refSeq, pidParams);
+       scores[i] = (float) PIDModel.computePID(
+               align.getSequenceAt(i).getSequenceAsString(), refSeq,
+               pidParams);
        seqs[i] = align.getSequenceAt(i);
      }
  
      }
      else
      {
-       setReverseOrder(align, vectorSubsetToArray(tmp, align.getSequences()));
+       setReverseOrder(align,
+               vectorSubsetToArray(tmp, align.getSequences()));
      }
    }
  
  
        if (tmp.size() != nSeq)
        {
-         System.err
-                 .println("WARNING: tmp.size()="
-                         + tmp.size()
-                         + " != nseq="
-                         + nSeq
-                         + " in getOrderByTree - tree contains sequences not in alignment");
+         System.err.println("WARNING: tmp.size()=" + tmp.size() + " != nseq="
+                 + nSeq
+                 + " in getOrderByTree - tree contains sequences not in alignment");
        }
      }
  
      }
      else
      {
-       setReverseOrder(align, vectorSubsetToArray(tmp, align.getSequences()));
+       setReverseOrder(align,
+               vectorSubsetToArray(tmp, align.getSequences()));
      }
    }
  
      }
  
      jalview.util.QuickSort.sort(scores, seqs);
 -    if (lastSortByScore != scoreLabel)
 +    if (lastSortByAnnotation != scoreLabel)
      {
 -      lastSortByScore = scoreLabel;
 +      lastSortByAnnotation = scoreLabel;
        setOrder(alignment, seqs);
      }
      else
  
    public static String FEATURE_DENSITY = "density";
  
-   private static boolean containsIgnoreCase(final String lab,
-           final List<String> labs)
-   {
-     if (labs == null)
-     {
-       return true;
-     }
-     if (lab == null)
-     {
-       return false;
-     }
-     for (String label : labs)
-     {
-       if (lab.equalsIgnoreCase(label))
-       {
-         return true;
-       }
-     }
-     return false;
-   }
    /**
 -   * sort the alignment using the features on each sequence found between start
 -   * and stop with the given featureLabel (and optional group qualifier)
 +   * Sort sequences by feature score or density, optionally restricted by
 +   * feature types, feature groups, or alignment start/end positions.
 +   * <p>
 +   * If the sort is repeated for the same combination of types and groups, sort
 +   * order is reversed.
     * 
 -   * @param featureLabel
 -   *          (may not be null)
 -   * @param groupLabel
 -   *          (may be null)
 -   * @param start
 -   *          (-1 to include non-positional features)
 -   * @param stop
 -   *          (-1 to only sort on non-positional features)
 +   * @param featureTypes
 +   *          a list of feature types to include (or null for all)
 +   * @param groups
 +   *          a list of feature groups to include (or null for all)
 +   * @param startCol
 +   *          start column position to include (base zero)
 +   * @param endCol
 +   *          end column position to include (base zero)
     * @param alignment
 -   *          - aligned sequences containing features
 +   *          the alignment to be sorted
     * @param method
 -   *          - one of the string constants FEATURE_SCORE, FEATURE_LABEL,
 -   *          FEATURE_DENSITY
 +   *          either "average_score" or "density" ("text" not yet implemented)
     */
 -  public static void sortByFeature(String featureLabel, String groupLabel,
 -          int start, int stop, AlignmentI alignment, String method)
 -  {
 -    sortByFeature(
 -            featureLabel == null ? null : Arrays.asList(new String[]
 -            { featureLabel }),
 -            groupLabel == null ? null : Arrays.asList(new String[]
 -            { groupLabel }), start, stop, alignment, method);
 -  }
 -
 -  private static boolean containsIgnoreCase(final String lab,
 -          final List<String> labs)
 -  {
 -    if (labs == null)
 -    {
 -      return true;
 -    }
 -    if (lab == null)
 -    {
 -      return false;
 -    }
 -    for (String label : labs)
 -    {
 -      if (lab.equalsIgnoreCase(label))
 -      {
 -        return true;
 -      }
 -    }
 -    return false;
 -  }
 -
 -  public static void sortByFeature(List<String> featureLabels,
 -          List<String> groupLabels, int start, int stop,
 +  public static void sortByFeature(List<String> featureTypes,
 +          List<String> groups, final int startCol, final int endCol,
            AlignmentI alignment, String method)
    {
      if (method != FEATURE_SCORE && method != FEATURE_LABEL
              && method != FEATURE_DENSITY)
      {
 -      throw new Error(MessageManager
 -              .getString("error.implementation_error_sortbyfeature"));
 +      String msg = String
 +              .format("Implementation Error - sortByFeature method must be either '%s' or '%s'",
 +                      FEATURE_SCORE, FEATURE_DENSITY);
 +      System.err.println(msg);
 +      return;
      }
  
 -    boolean ignoreScore = method != FEATURE_SCORE;
 -    StringBuffer scoreLabel = new StringBuffer();
 -    scoreLabel.append(start + stop + method);
 -    // This doesn't quite work yet - we'd like to have a canonical ordering that
 -    // can be preserved from call to call
 -    if (featureLabels != null)
 -    {
 -      for (String label : featureLabels)
 -      {
 -        scoreLabel.append(label);
 -      }
 -    }
 -    if (groupLabels != null)
 -    {
 -      for (String label : groupLabels)
 -      {
 -        scoreLabel.append(label);
 -      }
 -    }
 -
 -    /*
 -     * if resorting the same feature, toggle sort order
 -     */
 -    if (lastSortByFeatureScore == null
 -            || !scoreLabel.toString().equals(lastSortByFeatureScore))
 -    {
 -      sortByFeatureScoreAscending = true;
 -    }
 -    else
 -    {
 -      sortByFeatureScoreAscending = !sortByFeatureScoreAscending;
 -    }
 -    lastSortByFeatureScore = scoreLabel.toString();
 +    flipFeatureSortIfUnchanged(method, featureTypes, groups, startCol, endCol);
  
      SequenceI[] seqs = alignment.getSequencesArray();
  
      int hasScores = 0; // number of scores present on set
      double[] scores = new double[seqs.length];
      int[] seqScores = new int[seqs.length];
 -    Object[] feats = new Object[seqs.length];
 -    double min = 0, max = 0;
 +    Object[][] feats = new Object[seqs.length][];
 +    double min = 0d;
 +    double max = 0d;
 +
      for (int i = 0; i < seqs.length; i++)
      {
 -      SequenceFeature[] sf = seqs[i].getSequenceFeatures();
 -      if (sf == null)
 -      {
 -        sf = new SequenceFeature[0];
 -      }
 -      else
 -      {
 -        SequenceFeature[] tmp = new SequenceFeature[sf.length];
 -        for (int s = 0; s < tmp.length; s++)
 -        {
 -          tmp[s] = sf[s];
 -        }
 -        sf = tmp;
 -      }
 -      int sstart = (start == -1) ? start : seqs[i].findPosition(start);
 -      int sstop = (stop == -1) ? stop : seqs[i].findPosition(stop);
 +      /*
 +       * get sequence residues overlapping column region
 +       * and features for residue positions and specified types
 +       */
 +      String[] types = featureTypes == null ? null : featureTypes
 +              .toArray(new String[featureTypes.size()]);
 +      List<SequenceFeature> sfs = seqs[i].findFeatures(startCol + 1,
 +              endCol + 1, types);
 +
        seqScores[i] = 0;
        scores[i] = 0.0;
 -      int n = sf.length;
 -      for (int f = 0; f < sf.length; f++)
 +
 +      Iterator<SequenceFeature> it = sfs.listIterator();
 +      while (it.hasNext())
        {
 -        // filter for selection criteria
 -        SequenceFeature feature = sf[f];
 +        SequenceFeature sf = it.next();
  
          /*
 -         * double-check feature overlaps columns (JAL-2544)
 -         * (could avoid this with a findPositions(fromCol, toCol) method)
 -         * findIndex returns base 1 column values, startCol/endCol are base 0
 +         * accept all features with null or empty group, otherwise
 +         * check group is one of the currently visible groups
           */
 -        boolean noOverlap = seqs[i].findIndex(feature.getBegin()) > stop + 1
 -                || seqs[i].findIndex(feature.getEnd()) < start + 1;
 -        boolean skipFeatureType = featureLabels != null && !AlignmentSorter
 -                .containsIgnoreCase(feature.type, featureLabels);
 -        boolean skipFeatureGroup = groupLabels != null
 -                && (feature.getFeatureGroup() != null
 -                        && !AlignmentSorter.containsIgnoreCase(
 -                                feature.getFeatureGroup(), groupLabels));
 -        if (noOverlap || skipFeatureType || skipFeatureGroup)
 +        String featureGroup = sf.getFeatureGroup();
 +        if (groups != null && featureGroup != null
 +                && !"".equals(featureGroup)
 +                && !groups.contains(featureGroup))
          {
 -          // forget about this feature
 -          sf[f] = null;
 -          n--;
 +          it.remove();
          }
          else
          {
 -          // or, also take a look at the scores if necessary.
 -          if (!ignoreScore && !Float.isNaN(feature.getScore()))
 +          float score = sf.getScore();
 +          if (FEATURE_SCORE.equals(method) && !Float.isNaN(score))
            {
              if (seqScores[i] == 0)
              {
              }
              seqScores[i]++;
              hasScore[i] = true;
 -            scores[i] += feature.getScore(); // take the first instance of this
 -            // score.
 +            scores[i] += score;
 +            // take the first instance of this score // ??
            }
          }
        }
 -      SequenceFeature[] fs;
 -      feats[i] = fs = new SequenceFeature[n];
 -      if (n > 0)
 +
 +      feats[i] = sfs.toArray(new SequenceFeature[sfs.size()]);
 +      if (!sfs.isEmpty())
        {
 -        n = 0;
 -        for (int f = 0; f < sf.length; f++)
 -        {
 -          if (sf[f] != null)
 -          {
 -            ((SequenceFeature[]) feats[i])[n++] = sf[f];
 -          }
 -        }
          if (method == FEATURE_LABEL)
          {
 -          // order the labels by alphabet
 -          String[] labs = new String[fs.length];
 -          for (int l = 0; l < labs.length; l++)
 +          // order the labels by alphabet (not yet implemented)
 +          String[] labs = new String[sfs.size()];
 +          for (int l = 0; l < sfs.size(); l++)
            {
 -            labs[l] = (fs[l].getDescription() != null
 -                    ? fs[l].getDescription()
 -                    : fs[l].getType());
 +            SequenceFeature sf = sfs.get(l);
 +            String description = sf.getDescription();
 +            labs[l] = (description != null ? description : sf.getType());
            }
 -          QuickSort.sort(labs, ((Object[]) feats[i]));
 +          QuickSort.sort(labs, feats[i]);
          }
        }
        if (hasScore[i])
          // update the score bounds.
          if (hasScores == 1)
          {
 -          max = min = scores[i];
 +          min = scores[i];
 +          max = min;
          }
          else
          {
 -          if (max < scores[i])
 -          {
 -            max = scores[i];
 -          }
 -          if (min > scores[i])
 -          {
 -            min = scores[i];
 -          }
 +          max = Math.max(max, scores[i]);
 +          min = Math.min(min, scores[i]);
          }
        }
      }
  
 -    if (method == FEATURE_SCORE)
 +    if (FEATURE_SCORE.equals(method))
      {
        if (hasScores == 0)
        {
            }
          }
        }
 -      QuickSort.sortByDouble(scores, seqs, sortByFeatureScoreAscending);
 +      QuickSort.sortByDouble(scores, seqs, sortByFeatureAscending);
      }
 -    else if (method == FEATURE_DENSITY)
 +    else if (FEATURE_DENSITY.equals(method))
      {
        for (int i = 0; i < seqs.length; i++)
        {
          // System.err.println("Sorting on Density: seq "+seqs[i].getName()+
          // " Feats: "+featureCount+" Score : "+scores[i]);
        }
 -      QuickSort.sortByDouble(scores, seqs, sortByFeatureScoreAscending);
 +      QuickSort.sortByDouble(scores, seqs, sortByFeatureAscending);
      }
 -    else
 +
 +    setOrder(alignment, seqs);
 +  }
 +
 +  /**
 +   * Builds a string hash of criteria for sorting, and if unchanged from last
 +   * time, reverse the sort order
 +   * 
 +   * @param method
 +   * @param featureTypes
 +   * @param groups
 +   * @param startCol
 +   * @param endCol
 +   */
 +  protected static void flipFeatureSortIfUnchanged(String method,
 +          List<String> featureTypes, List<String> groups,
 +          final int startCol, final int endCol)
 +  {
 +    StringBuilder sb = new StringBuilder(64);
 +    sb.append(startCol).append(method).append(endCol);
 +    if (featureTypes != null)
      {
 -      if (method == FEATURE_LABEL)
 -      {
 -        throw new Error(
 -                MessageManager.getString("error.not_yet_implemented"));
 -      }
 +      Collections.sort(featureTypes);
 +      sb.append(featureTypes.toString());
      }
 +    if (groups != null)
 +    {
 +      Collections.sort(groups);
 +      sb.append(groups.toString());
 +    }
 +    String scoreCriteria = sb.toString();
  
 -    setOrder(alignment, seqs);
 +    /*
 +     * if resorting on the same criteria, toggle sort order
 +     */
 +    if (sortByFeatureCriteria == null
 +            || !scoreCriteria.equals(sortByFeatureCriteria))
 +    {
 +      sortByFeatureAscending = true;
 +    }
 +    else
 +    {
 +      sortByFeatureAscending = !sortByFeatureAscending;
 +    }
 +    sortByFeatureCriteria = scoreCriteria;
    }
  
  }
@@@ -35,14 -35,14 +35,14 @@@ import jalview.datamodel.Sequence
  import jalview.datamodel.SequenceFeature;
  import jalview.datamodel.SequenceGroup;
  import jalview.datamodel.SequenceI;
 -import jalview.io.gff.SequenceOntologyFactory;
 +import jalview.datamodel.features.SequenceFeatures;
  import jalview.io.gff.SequenceOntologyI;
  import jalview.schemes.ResidueProperties;
  import jalview.util.Comparison;
  import jalview.util.DBRefUtils;
 +import jalview.util.IntRangeComparator;
  import jalview.util.MapList;
  import jalview.util.MappingUtils;
 -import jalview.util.RangeComparator;
  import jalview.util.StringUtils;
  
  import java.io.UnsupportedEncodingException;
@@@ -51,6 -51,7 +51,6 @@@ import java.util.ArrayList
  import java.util.Arrays;
  import java.util.Collection;
  import java.util.Collections;
 -import java.util.Comparator;
  import java.util.HashMap;
  import java.util.HashSet;
  import java.util.Iterator;
@@@ -170,10 -171,12 +170,12 @@@ public class AlignmentUtil
          }
        }
        // TODO use Character.toLowerCase to avoid creating String objects?
-       char[] upstream = new String(ds.getSequence(s.getStart() - 1
-               - ustream_ds, s.getStart() - 1)).toLowerCase().toCharArray();
-       char[] downstream = new String(ds.getSequence(s_end - 1, s_end
-               + dstream_ds)).toLowerCase().toCharArray();
+       char[] upstream = new String(ds
+               .getSequence(s.getStart() - 1 - ustream_ds, s.getStart() - 1))
+                       .toLowerCase().toCharArray();
+       char[] downstream = new String(
+               ds.getSequence(s_end - 1, s_end + dstream_ds)).toLowerCase()
+                       .toCharArray();
        char[] coreseq = s.getSequence();
        char[] nseq = new char[offset + upstream.length + downstream.length
                + coreseq.length];
        System.arraycopy(upstream, 0, nseq, p, upstream.length);
        System.arraycopy(coreseq, 0, nseq, p + upstream.length,
                coreseq.length);
-       System.arraycopy(downstream, 0, nseq, p + coreseq.length
-               + upstream.length, downstream.length);
+       System.arraycopy(downstream, 0, nseq,
+               p + coreseq.length + upstream.length, downstream.length);
        s.setSequence(new String(nseq));
        s.setStart(s.getStart() - ustream_ds);
        s.setEnd(s_end + downstream.length);
     * @return
     */
    protected static boolean mapProteinToCdna(
-           final AlignmentI proteinAlignment,
-           final AlignmentI cdnaAlignment, Set<SequenceI> mappedDna,
-           Set<SequenceI> mappedProtein, boolean xrefsOnly)
+           final AlignmentI proteinAlignment, final AlignmentI cdnaAlignment,
+           Set<SequenceI> mappedDna, Set<SequenceI> mappedProtein,
+           boolean xrefsOnly)
    {
      boolean mappingExistsOrAdded = false;
      List<SequenceI> thisSeqs = proteinAlignment.getSequences();
           * Don't map non-xrefd sequences more than once each. This heuristic
           * allows us to pair up similar sequences in ordered alignments.
           */
-         if (!xrefsOnly
-                 && (mappedProtein.contains(aaSeq) || mappedDna
-                         .contains(cdnaSeq)))
+         if (!xrefsOnly && (mappedProtein.contains(aaSeq)
+                 || mappedDna.contains(cdnaSeq)))
          {
            continue;
          }
    /**
     * Builds a mapping (if possible) of a cDNA to a protein sequence.
     * <ul>
-    * <li>first checks if the cdna translates exactly to the protein sequence</li>
+    * <li>first checks if the cdna translates exactly to the protein
+    * sequence</li>
     * <li>else checks for translation after removing a STOP codon</li>
     * <li>else checks for translation after removing a START codon</li>
     * <li>if that fails, inspect CDS features on the cDNA sequence</li>
       * String objects.
       */
      final SequenceI proteinDataset = proteinSeq.getDatasetSequence();
-     char[] aaSeqChars = proteinDataset != null ? proteinDataset
-             .getSequence() : proteinSeq.getSequence();
+     char[] aaSeqChars = proteinDataset != null
+             ? proteinDataset.getSequence()
+             : proteinSeq.getSequence();
      final SequenceI cdnaDataset = cdnaSeq.getDatasetSequence();
      char[] cdnaSeqChars = cdnaDataset != null ? cdnaDataset.getSequence()
              : cdnaSeq.getSequence();
       * If lengths still don't match, try ignoring start codon.
       */
      int startOffset = 0;
-     if (cdnaLength != mappedLength
-             && cdnaLength > 2
+     if (cdnaLength != mappedLength && cdnaLength > 2
              && String.valueOf(cdnaSeqChars, 0, CODON_LENGTH).toUpperCase()
                      .equals(ResidueProperties.START))
      {
        /*
         * protein is translation of dna (+/- start/stop codons)
         */
-       MapList map = new MapList(new int[] { cdnaStart, cdnaEnd }, new int[]
-       { proteinStart, proteinEnd }, CODON_LENGTH, 1);
+       MapList map = new MapList(new int[] { cdnaStart, cdnaEnd },
+               new int[]
+               { proteinStart, proteinEnd }, CODON_LENGTH, 1);
        return map;
      }
  
  
      int aaPos = 0;
      int dnaPos = cdnaStart;
-     for (; dnaPos < cdnaSeqChars.length - 2 && aaPos < aaSeqChars.length; dnaPos += CODON_LENGTH, aaPos++)
+     for (; dnaPos < cdnaSeqChars.length - 2
+             && aaPos < aaSeqChars.length; dnaPos += CODON_LENGTH, aaPos++)
      {
        String codon = String.valueOf(cdnaSeqChars, dnaPos, CODON_LENGTH);
        final String translated = ResidueProperties.codonTranslate(codon);
     * @param preserveUnmappedGaps
     * @param preserveMappedGaps
     */
-   public static void alignSequenceAs(SequenceI alignTo,
-           SequenceI alignFrom, AlignedCodonFrame mapping, String myGap,
-           char sourceGap, boolean preserveMappedGaps,
-           boolean preserveUnmappedGaps)
+   public static void alignSequenceAs(SequenceI alignTo, SequenceI alignFrom,
+           AlignedCodonFrame mapping, String myGap, char sourceGap,
+           boolean preserveMappedGaps, boolean preserveUnmappedGaps)
    {
      // TODO generalise to work for Protein-Protein, dna-dna, dna-protein
  
      int toOffset = alignTo.getStart() - 1;
      int sourceGapMappedLength = 0;
      boolean inExon = false;
 -    final char[] thisSeq = alignTo.getSequence();
 -    final char[] thatAligned = alignFrom.getSequence();
 -    StringBuilder thisAligned = new StringBuilder(2 * thisSeq.length);
 +    final int toLength = alignTo.getLength();
 +    final int fromLength = alignFrom.getLength();
 +    StringBuilder thisAligned = new StringBuilder(2 * toLength);
  
      /*
       * Traverse the 'model' aligned sequence
       */
 -    for (char sourceChar : thatAligned)
 +    for (int i = 0; i < fromLength; i++)
      {
 +      char sourceChar = alignFrom.getCharAt(i);
        if (sourceChar == sourceGap)
        {
          sourceGapMappedLength += ratio;
         */
        int intronLength = 0;
        while (basesWritten + toOffset < mappedCodonEnd
 -              && thisSeqPos < thisSeq.length)
 +              && thisSeqPos < toLength)
        {
 -        final char c = thisSeq[thisSeqPos++];
 +        final char c = alignTo.getCharAt(thisSeqPos++);
          if (c != myGapChar)
          {
            basesWritten++;
              int gapsToAdd = calculateGapsToInsert(preserveMappedGaps,
                      preserveUnmappedGaps, sourceGapMappedLength, inExon,
                      trailingCopiedGap.length(), intronLength, startOfCodon);
 -            for (int i = 0; i < gapsToAdd; i++)
 +            for (int k = 0; k < gapsToAdd; k++)
              {
                thisAligned.append(myGapChar);
              }
       * At end of model aligned sequence. Copy any remaining target sequence, optionally
       * including (intron) gaps.
       */
 -    while (thisSeqPos < thisSeq.length)
 +    while (thisSeqPos < toLength)
      {
 -      final char c = thisSeq[thisSeqPos++];
 +      final char c = alignTo.getCharAt(thisSeqPos++);
        if (c != myGapChar || preserveUnmappedGaps)
        {
          thisAligned.append(c);
          }
          else
          {
-           gapsToAdd = Math.min(intronLength + trailingGapLength
-                   - sourceGapMappedLength, trailingGapLength);
+           gapsToAdd = Math.min(
+                   intronLength + trailingGapLength - sourceGapMappedLength,
+                   trailingGapLength);
          }
        }
      }
     * @return
     */
    static boolean alignCdsSequenceAsProtein(SequenceI cdsSeq,
-           AlignmentI protein, List<AlignedCodonFrame> mappings, char gapChar)
+           AlignmentI protein, List<AlignedCodonFrame> mappings,
+           char gapChar)
    {
      SequenceI cdsDss = cdsSeq.getDatasetSequence();
      if (cdsDss == null)
        SequenceI peptide = mapping.findAlignedSequence(cdsSeq, protein);
        if (peptide != null)
        {
 -        int peptideLength = peptide.getLength();
 +        final int peptideLength = peptide.getLength();
          Mapping map = mapping.getMappingBetween(cdsSeq, peptide);
          if (map != null)
          {
            {
              mapList = mapList.getInverse();
            }
 -          int cdsLength = cdsDss.getLength();
 -          int mappedFromLength = MappingUtils
 -                  .getLength(mapList.getFromRanges());
 +          final int cdsLength = cdsDss.getLength();
 +          int mappedFromLength = MappingUtils.getLength(mapList
 +                  .getFromRanges());
            int mappedToLength = MappingUtils
                    .getLength(mapList.getToRanges());
            boolean addStopCodon = (cdsLength == mappedFromLength
                    * CODON_LENGTH + CODON_LENGTH)
-                   || (peptide.getDatasetSequence().getLength() == mappedFromLength - 1);
+                   || (peptide.getDatasetSequence()
+                           .getLength() == mappedFromLength - 1);
            if (cdsLength != mappedToLength && !addStopCodon)
            {
-             System.err
-                     .println(String
-                             .format("Can't align cds as protein (length mismatch %d/%d): %s",
-                                     cdsLength, mappedToLength,
-                                     cdsSeq.getName()));
+             System.err.println(String.format(
+                     "Can't align cds as protein (length mismatch %d/%d): %s",
+                     cdsLength, mappedToLength, cdsSeq.getName()));
            }
  
            /*
             * walk over the aligned peptide sequence and insert mapped 
             * codons for residues in the aligned cds sequence 
             */
 -          char[] alignedPeptide = peptide.getSequence();
 -          char[] nucleotides = cdsDss.getSequence();
            int copiedBases = 0;
            int cdsStart = cdsDss.getStart();
            int proteinPos = peptide.getStart() - 1;
            int cdsCol = 0;
 -          for (char residue : alignedPeptide)
 +
 +          for (int col = 0; col < peptideLength; col++)
            {
 +            char residue = peptide.getCharAt(col);
 +
              if (Comparison.isGap(residue))
              {
                cdsCol += CODON_LENGTH;
                {
                  for (int j = codon[0]; j <= codon[1]; j++)
                  {
 -                  char mappedBase = nucleotides[j - cdsStart];
 +                  char mappedBase = cdsDss.getCharAt(j - cdsStart);
                    alignedCds[cdsCol++] = mappedBase;
                    copiedBases++;
                  }
             * append stop codon if not mapped from protein,
             * closing it up to the end of the mapped sequence
             */
 -          if (copiedBases == nucleotides.length - CODON_LENGTH)
 +          if (copiedBases == cdsLength - CODON_LENGTH)
            {
              for (int i = alignedCds.length - 1; i >= 0; i--)
              {
                  break;
                }
              }
 -            for (int i = nucleotides.length
 -                    - CODON_LENGTH; i < nucleotides.length; i++)
 +            for (int i = cdsLength - CODON_LENGTH; i < cdsLength; i++)
              {
 -              alignedCds[cdsCol++] = nucleotides[i];
 +              alignedCds[cdsCol++] = cdsDss.getCharAt(i);
              }
            }
            cdsSeq.setSequence(new String(alignedCds));
          if (prot != null)
          {
            Mapping seqMap = mapping.getMappingForSequence(dnaSeq);
-           addCodonPositions(dnaSeq, prot, protein.getGapCharacter(),
-                   seqMap, alignedCodons);
+           addCodonPositions(dnaSeq, prot, protein.getGapCharacter(), seqMap,
+                   alignedCodons);
            unmappedProtein.remove(prot);
          }
        }
          AlignedCodon codon = sequenceCodon.getValue();
          if (codon.peptideCol > 1)
          {
-           System.err
-                   .println("Problem mapping protein with >1 unmapped start positions: "
+           System.err.println(
+                   "Problem mapping protein with >1 unmapped start positions: "
                            + seq.getName());
          }
          else if (codon.peptideCol == 1)
            if (lastCodon != null)
            {
              AlignedCodon firstPeptide = new AlignedCodon(lastCodon.pos1,
-                     lastCodon.pos2, lastCodon.pos3, String.valueOf(seq
-                             .getCharAt(0)), 0);
+                     lastCodon.pos2, lastCodon.pos3,
+                     String.valueOf(seq.getCharAt(0)), 0);
              toAdd.put(seq, firstPeptide);
            }
            else
            List<SequenceI> unmappedProtein)
    {
      /*
 -     * Prefill aligned sequences with gaps before inserting aligned protein
 -     * residues.
 +     * prefill peptide sequences with gaps 
       */
      int alignedWidth = alignedCodons.size();
      char[] gaps = new char[alignedWidth];
      Arrays.fill(gaps, protein.getGapCharacter());
 -    String allGaps = String.valueOf(gaps);
 +    Map<SequenceI, char[]> peptides = new HashMap<>();
      for (SequenceI seq : protein.getSequences())
      {
        if (!unmappedProtein.contains(seq))
        {
 -        seq.setSequence(allGaps);
 +        peptides.put(seq, Arrays.copyOf(gaps, gaps.length));
        }
      }
  
 +    /*
 +     * Traverse the codons left to right (as defined by CodonComparator)
 +     * and insert peptides in each column where the sequence is mapped.
 +     * This gives a peptide 'alignment' where residues are aligned if their
 +     * corresponding codons occupy the same columns in the cdna alignment.
 +     */
      int column = 0;
      for (AlignedCodon codon : alignedCodons.keySet())
      {
                .get(codon);
        for (Entry<SequenceI, AlignedCodon> entry : columnResidues.entrySet())
        {
 -        // place translated codon at its column position in sequence
 -        entry.getKey().getSequence()[column] = entry.getValue().product
 -                .charAt(0);
 +        char residue = entry.getValue().product.charAt(0);
 +        peptides.get(entry.getKey())[column] = residue;
        }
        column++;
      }
 +
 +    /*
 +     * and finally set the constructed sequences
 +     */
 +    for (Entry<SequenceI, char[]> entry : peptides.entrySet())
 +    {
 +      entry.getKey().setSequence(new String(entry.getValue()));
 +    }
 +
      return 0;
    }
  
     * <ul>
     * <li>One alignment must be nucleotide, and the other protein</li>
     * <li>At least one pair of sequences must be already mapped, or mappable</li>
-    * <li>Mappable means the nucleotide translation matches the protein sequence</li>
+    * <li>Mappable means the nucleotide translation matches the protein
+    * sequence</li>
     * <li>The translation may ignore start and stop codons if present in the
     * nucleotide</li>
     * </ul>
        return false;
      }
  
-     SequenceI dnaDs = dnaSeq.getDatasetSequence() == null ? dnaSeq : dnaSeq
-             .getDatasetSequence();
-     SequenceI proteinDs = proteinSeq.getDatasetSequence() == null ? proteinSeq
+     SequenceI dnaDs = dnaSeq.getDatasetSequence() == null ? dnaSeq
+             : dnaSeq.getDatasetSequence();
+     SequenceI proteinDs = proteinSeq.getDatasetSequence() == null
+             ? proteinSeq
              : proteinSeq.getDatasetSequence();
  
      for (AlignedCodonFrame mapping : mappings)
     *          the alignment to check for presence of annotations
     */
    public static void findAddableReferenceAnnotations(
-           List<SequenceI> sequenceScope,
-           Map<String, String> labelForCalcId,
+           List<SequenceI> sequenceScope, Map<String, String> labelForCalcId,
            final Map<SequenceI, List<AlignmentAnnotation>> candidates,
            AlignmentI al)
    {
  
    /**
     * Set visibility of alignment annotations of specified types (labels), for
-    * specified sequences. This supports controls like
-    * "Show all secondary structure", "Hide all Temp factor", etc.
+    * specified sequences. This supports controls like "Show all secondary
+    * structure", "Hide all Temp factor", etc.
     * 
     * @al the alignment to scan for annotations
     * @param types
        {
          if (anyType || types.contains(aa.label))
          {
-           if ((aa.sequenceRef != null)
-                   && (forSequences == null || forSequences
-                           .contains(aa.sequenceRef)))
+           if ((aa.sequenceRef != null) && (forSequences == null
+                   || forSequences.contains(aa.sequenceRef)))
            {
              aa.visible = doShow;
            }
        productSeqs = new HashSet<SequenceI>();
        for (SequenceI seq : products)
        {
-         productSeqs.add(seq.getDatasetSequence() == null ? seq : seq
-                 .getDatasetSequence());
+         productSeqs.add(seq.getDatasetSequence() == null ? seq
+                 : seq.getDatasetSequence());
        }
      }
  
            /*
             * add a mapping from CDS to the (unchanged) mapped to range
             */
-           List<int[]> cdsRange = Collections.singletonList(new int[] { 1,
-               cdsSeq.getLength() });
+           List<int[]> cdsRange = Collections
+                   .singletonList(new int[]
+                   { 1, cdsSeq.getLength() });
            MapList cdsToProteinMap = new MapList(cdsRange,
                    mapList.getToRanges(), mapList.getFromRatio(),
                    mapList.getToRatio());
              // 'CDS|emblcdsacc'
              // assuming cds version same as dna ?!?
  
-             DBRefEntry proteinToCdsRef = new DBRefEntry(
-                     primRef.getSource(), primRef.getVersion(),
-                     cdsSeq.getName());
+             DBRefEntry proteinToCdsRef = new DBRefEntry(primRef.getSource(),
+                     primRef.getVersion(), cdsSeq.getName());
              //
-             proteinToCdsRef.setMap(new Mapping(cdsSeqDss, cdsToProteinMap
-                     .getInverse()));
+             proteinToCdsRef.setMap(
+                     new Mapping(cdsSeqDss, cdsToProteinMap.getInverse()));
              proteinProduct.addDBRef(proteinToCdsRef);
            }
  
        }
      }
  
-     AlignmentI cds = new Alignment(cdsSeqs.toArray(new SequenceI[cdsSeqs
-             .size()]));
+     AlignmentI cds = new Alignment(
+             cdsSeqs.toArray(new SequenceI[cdsSeqs.size()]));
      cds.setDataset(dataset);
  
      return cds;
       * is this mapping from the whole dna sequence (i.e. CDS)?
       * allowing for possible stop codon on dna but not peptide
       */
-     int mappedFromLength = MappingUtils.getLength(aMapping.getMap()
-             .getFromRanges());
+     int mappedFromLength = MappingUtils
+             .getLength(aMapping.getMap().getFromRanges());
      int dnaLength = seqDss.getLength();
      if (mappedFromLength == dnaLength
              || mappedFromLength == dnaLength - CODON_LENGTH)
                  && proteinProduct == mapping.getTo()
                  && seqDss != map.getFromSeq())
          {
-           mappedFromLength = MappingUtils.getLength(mapping.getMap()
-                   .getFromRanges());
+           mappedFromLength = MappingUtils
+                   .getLength(mapping.getMap().getFromRanges());
            if (mappedFromLength == map.getFromSeq().getLength())
            {
              /*
            }
            else
            {
-             System.err
-                     .println("JAL-2154 regression: warning - found (and ignnored a duplicate CDS sequence):"
+             System.err.println(
+                     "JAL-2154 regression: warning - found (and ignnored a duplicate CDS sequence):"
                              + mtch.toString());
            }
          }
      for (DBRefEntry cdsref : direct)
      {
        // clone maplist and mapping
-       MapList cdsposmap = new MapList(Arrays.asList(new int[][] { new int[]
-       { cdsSeq.getStart(), cdsSeq.getEnd() } }), cdsref.getMap().getMap()
-               .getToRanges(), 3, 1);
-       Mapping cdsmap = new Mapping(cdsref.getMap().getTo(), cdsref.getMap()
-               .getMap());
+       MapList cdsposmap = new MapList(
+               Arrays.asList(new int[][]
+               { new int[] { cdsSeq.getStart(), cdsSeq.getEnd() } }),
+               cdsref.getMap().getMap().getToRanges(), 3, 1);
+       Mapping cdsmap = new Mapping(cdsref.getMap().getTo(),
+               cdsref.getMap().getMap());
  
        // create dbref
        DBRefEntry newref = new DBRefEntry(cdsref.getSource(),
-               cdsref.getVersion(), cdsref.getAccessionId(), new Mapping(
-                       cdsmap.getTo(), cdsposmap));
+               cdsref.getVersion(), cdsref.getAccessionId(),
+               new Mapping(cdsmap.getTo(), cdsposmap));
  
        // and see if we can map to the protein product for this mapping.
        // onSource is the filtered set of accessions on protein that we are
     * 
     * @param fromSeq
     * @param toSeq
 +   * @param mapping
 +   *          the mapping from 'fromSeq' to 'toSeq'
     * @param select
     *          if not null, only features of this type are copied (including
     *          subtypes in the Sequence Ontology)
 -   * @param mapping
 -   *          the mapping from 'fromSeq' to 'toSeq'
     * @param omitting
     */
    public static int transferFeatures(SequenceI fromSeq, SequenceI toSeq,
        copyTo = copyTo.getDatasetSequence();
      }
  
 -    SequenceOntologyI so = SequenceOntologyFactory.getInstance();
 +    /*
 +     * get features, optionally restricted by an ontology term
 +     */
 +    List<SequenceFeature> sfs = select == null ? fromSeq.getFeatures()
 +            .getPositionalFeatures() : fromSeq.getFeatures()
 +            .getFeaturesByOntology(select);
 +
      int count = 0;
 -    SequenceFeature[] sfs = fromSeq.getSequenceFeatures();
 -    if (sfs != null)
 +    for (SequenceFeature sf : sfs)
      {
 -      for (SequenceFeature sf : sfs)
 +      String type = sf.getType();
 +      boolean omit = false;
 +      for (String toOmit : omitting)
        {
 -        String type = sf.getType();
 -        if (select != null && !so.isA(type, select))
 -        {
 -          continue;
 -        }
 -        boolean omit = false;
 -        for (String toOmit : omitting)
 +        if (type.equals(toOmit))
          {
 -          if (type.equals(toOmit))
 -          {
 -            omit = true;
 -          }
 -        }
 -        if (omit)
 -        {
 -          continue;
 +          omit = true;
          }
 +      }
 +      if (omit)
 +      {
 +        continue;
 +      }
  
 -        /*
 -         * locate the mapped range - null if either start or end is
 -         * not mapped (no partial overlaps are calculated)
 -         */
 -        int start = sf.getBegin();
 -        int end = sf.getEnd();
 -        int[] mappedTo = mapping.locateInTo(start, end);
 -        /*
 -         * if whole exon range doesn't map, try interpreting it
 -         * as 5' or 3' exon overlapping the CDS range
 -         */
 -        if (mappedTo == null)
 -        {
 -          mappedTo = mapping.locateInTo(end, end);
 -          if (mappedTo != null)
 -          {
 -            /*
 -             * end of exon is in CDS range - 5' overlap
 -             * to a range from the start of the peptide
 -             */
 -            mappedTo[0] = 1;
 -          }
 -        }
 -        if (mappedTo == null)
 +      /*
 +       * locate the mapped range - null if either start or end is
 +       * not mapped (no partial overlaps are calculated)
 +       */
 +      int start = sf.getBegin();
 +      int end = sf.getEnd();
 +      int[] mappedTo = mapping.locateInTo(start, end);
 +      /*
 +       * if whole exon range doesn't map, try interpreting it
 +       * as 5' or 3' exon overlapping the CDS range
 +       */
 +      if (mappedTo == null)
 +      {
 +        mappedTo = mapping.locateInTo(end, end);
 +        if (mappedTo != null)
          {
 -          mappedTo = mapping.locateInTo(start, start);
 -          if (mappedTo != null)
 -          {
 -            /*
 -             * start of exon is in CDS range - 3' overlap
 -             * to a range up to the end of the peptide
 -             */
 -            mappedTo[1] = toSeq.getLength();
 -          }
 +          /*
 +           * end of exon is in CDS range - 5' overlap
 +           * to a range from the start of the peptide
 +           */
 +          mappedTo[0] = 1;
          }
 +      }
 +      if (mappedTo == null)
 +      {
 +        mappedTo = mapping.locateInTo(start, start);
          if (mappedTo != null)
          {
 -          SequenceFeature copy = new SequenceFeature(sf);
 -          copy.setBegin(Math.min(mappedTo[0], mappedTo[1]));
 -          copy.setEnd(Math.max(mappedTo[0], mappedTo[1]));
 -          copyTo.addSequenceFeature(copy);
 -          count++;
 +          /*
 +           * start of exon is in CDS range - 3' overlap
 +           * to a range up to the end of the peptide
 +           */
 +          mappedTo[1] = toSeq.getLength();
          }
        }
 +      if (mappedTo != null)
 +      {
 +        int newBegin = Math.min(mappedTo[0], mappedTo[1]);
 +        int newEnd = Math.max(mappedTo[0], mappedTo[1]);
 +        SequenceFeature copy = new SequenceFeature(sf, newBegin, newEnd,
 +                sf.getFeatureGroup(), sf.getScore());
 +        copyTo.addSequenceFeature(copy);
 +        count++;
 +      }
      }
      return count;
    }
    public static List<int[]> findCdsPositions(SequenceI dnaSeq)
    {
      List<int[]> result = new ArrayList<int[]>();
 -    SequenceFeature[] sfs = dnaSeq.getSequenceFeatures();
 -    if (sfs == null)
 +
 +    List<SequenceFeature> sfs = dnaSeq.getFeatures().getFeaturesByOntology(
 +            SequenceOntologyI.CDS);
 +    if (sfs.isEmpty())
      {
        return result;
      }
 -
 -    SequenceOntologyI so = SequenceOntologyFactory.getInstance();
 +    SequenceFeatures.sortFeatures(sfs, true);
      int startPhase = 0;
  
      for (SequenceFeature sf : sfs)
      {
 +      int phase = 0;
 +      try
 +      {
 +        phase = Integer.parseInt(sf.getPhase());
 +      } catch (NumberFormatException e)
 +      {
 +        // ignore
 +      }
        /*
 -       * process a CDS feature (or a sub-type of CDS)
 +       * phase > 0 on first codon means 5' incomplete - skip to the start
 +       * of the next codon; example ENST00000496384
         */
 -      if (so.isA(sf.getType(), SequenceOntologyI.CDS))
 +      int begin = sf.getBegin();
 +      int end = sf.getEnd();
 +      if (result.isEmpty())
        {
 -        int phase = 0;
 -        try
 -        {
 -          phase = Integer.parseInt(sf.getPhase());
 -        } catch (NumberFormatException e)
 -        {
 -          // ignore
 -        }
 -        /*
 -         * phase > 0 on first codon means 5' incomplete - skip to the start
 -         * of the next codon; example ENST00000496384
 -         */
 -        int begin = sf.getBegin();
 -        int end = sf.getEnd();
 -        if (result.isEmpty())
 +        begin += phase;
 +        if (begin > end)
          {
 -          begin += phase;
 -          if (begin > end)
 -          {
 -            // shouldn't happen!
 -            System.err.println(
 -                    "Error: start phase extends beyond start CDS in "
 -                            + dnaSeq.getName());
 -          }
 +          // shouldn't happen!
 +          System.err
 +                  .println("Error: start phase extends beyond start CDS in "
 +                          + dnaSeq.getName());
          }
 -        result.add(new int[] { begin, end });
        }
 +      result.add(new int[] { begin, end });
      }
  
      /*
       * ranges are assembled in order. Other cases should not use this method,
       * but instead construct an explicit mapping for CDS (e.g. EMBL parsing).
       */
 -    Collections.sort(result, new RangeComparator(true));
 +    Collections.sort(result, IntRangeComparator.ASCENDING);
      return result;
    }
  
        count += computePeptideVariants(peptide, peptidePos, codonVariants);
      }
  
 -    /*
 -     * sort to get sequence features in start position order
 -     * - would be better to store in Sequence as a TreeSet or NCList?
 -     */
 -    if (peptide.getSequenceFeatures() != null)
 -    {
 -      Arrays.sort(peptide.getSequenceFeatures(),
 -              new Comparator<SequenceFeature>()
 -              {
 -                @Override
 -                public int compare(SequenceFeature o1, SequenceFeature o2)
 -                {
 -                  int c = Integer.compare(o1.getBegin(), o2.getBegin());
 -                  return c == 0 ? Integer.compare(o1.getEnd(), o2.getEnd())
 -                          : c;
 -                }
 -              });
 -    }
      return count;
    }
  
       * are currently ignored here
       */
      String trans = codon.contains("-") ? "-"
-             : (codon.length() > CODON_LENGTH ? null : ResidueProperties
-                     .codonTranslate(codon));
+             : (codon.length() > CODON_LENGTH ? null
+                     : ResidueProperties.codonTranslate(codon));
      if (trans != null && !trans.equals(residue))
      {
        String residue3Char = StringUtils
        String trans3Char = StringUtils
                .toSentenceCase(ResidueProperties.aa2Triplet.get(trans));
        String desc = "p." + residue3Char + peptidePos + trans3Char;
 -      // set score to 0f so 'graduated colour' option is offered! JAL-2060
        SequenceFeature sf = new SequenceFeature(
                SequenceOntologyI.SEQUENCE_VARIANT, desc, peptidePos,
 -              peptidePos, 0f, var.getSource());
 +              peptidePos, var.getSource());
        StringBuilder attributes = new StringBuilder(32);
        String id = (String) var.variant.getValue(ID);
        if (id != null)
          StringBuilder link = new StringBuilder(32);
          try
          {
-           link.append(desc)
-                   .append(" ")
-                   .append(id)
-                   .append("|http://www.ensembl.org/Homo_sapiens/Variation/Summary?v=")
+           link.append(desc).append(" ").append(id).append(
+                   "|http://www.ensembl.org/Homo_sapiens/Variation/Summary?v=")
                    .append(URLEncoder.encode(id, "UTF-8"));
            sf.addLink(link.toString());
          } catch (UnsupportedEncodingException e)
       * LinkedHashMap ensures we keep the peptide features in sequence order
       */
      LinkedHashMap<Integer, List<DnaVariant>[]> variants = new LinkedHashMap<Integer, List<DnaVariant>[]>();
 -    SequenceOntologyI so = SequenceOntologyFactory.getInstance();
  
 -    SequenceFeature[] dnaFeatures = dnaSeq.getSequenceFeatures();
 -    if (dnaFeatures == null)
 +    List<SequenceFeature> dnaFeatures = dnaSeq.getFeatures()
 +            .getFeaturesByOntology(SequenceOntologyI.SEQUENCE_VARIANT);
 +    if (dnaFeatures.isEmpty())
      {
        return variants;
      }
          // not handling multi-locus variant features
          continue;
        }
 -      if (so.isA(sf.getType(), SequenceOntologyI.SEQUENCE_VARIANT))
 +      int[] mapsTo = dnaToProtein.locateInTo(dnaCol, dnaCol);
 +      if (mapsTo == null)
        {
 -        int[] mapsTo = dnaToProtein.locateInTo(dnaCol, dnaCol);
 -        if (mapsTo == null)
 -        {
 -          // feature doesn't lie within coding region
 -          continue;
 -        }
 -        int peptidePosition = mapsTo[0];
 -        List<DnaVariant>[] codonVariants = variants.get(peptidePosition);
 -        if (codonVariants == null)
 -        {
 -          codonVariants = new ArrayList[CODON_LENGTH];
 -          codonVariants[0] = new ArrayList<DnaVariant>();
 -          codonVariants[1] = new ArrayList<DnaVariant>();
 -          codonVariants[2] = new ArrayList<DnaVariant>();
 -          variants.put(peptidePosition, codonVariants);
 -        }
 +        // feature doesn't lie within coding region
 +        continue;
 +      }
 +      int peptidePosition = mapsTo[0];
 +      List<DnaVariant>[] codonVariants = variants.get(peptidePosition);
 +      if (codonVariants == null)
 +      {
 +        codonVariants = new ArrayList[CODON_LENGTH];
 +        codonVariants[0] = new ArrayList<DnaVariant>();
 +        codonVariants[1] = new ArrayList<DnaVariant>();
 +        codonVariants[2] = new ArrayList<DnaVariant>();
 +        variants.put(peptidePosition, codonVariants);
 +      }
  
 -        /*
 -         * extract dna variants to a string array
 -         */
 -        String alls = (String) sf.getValue("alleles");
 -        if (alls == null)
 -        {
 -          continue;
 -        }
 -        String[] alleles = alls.toUpperCase().split(",");
 -        int i = 0;
 -        for (String allele : alleles)
 -        {
 -          alleles[i++] = allele.trim(); // lose any space characters "A, G"
 -        }
 +      /*
 +       * extract dna variants to a string array
 +       */
 +      String alls = (String) sf.getValue("alleles");
 +      if (alls == null)
 +      {
 +        continue;
 +      }
 +      String[] alleles = alls.toUpperCase().split(",");
 +      int i = 0;
 +      for (String allele : alleles)
 +      {
 +        alleles[i++] = allele.trim(); // lose any space characters "A, G"
 +      }
  
 -        /*
 -         * get this peptide's codon positions e.g. [3, 4, 5] or [4, 7, 10]
 -         */
 -        int[] codon = peptidePosition == lastPeptidePostion ? lastCodon
 -                : MappingUtils.flattenRanges(dnaToProtein
 -                        .locateInFrom(peptidePosition, peptidePosition));
 -        lastPeptidePostion = peptidePosition;
 -        lastCodon = codon;
 +      /*
 +       * get this peptide's codon positions e.g. [3, 4, 5] or [4, 7, 10]
 +       */
 +      int[] codon = peptidePosition == lastPeptidePostion ? lastCodon
 +              : MappingUtils.flattenRanges(dnaToProtein.locateInFrom(
 +                      peptidePosition, peptidePosition));
 +      lastPeptidePostion = peptidePosition;
 +      lastCodon = codon;
  
 -        /*
 -         * save nucleotide (and any variant) for each codon position
 -         */
 -        for (int codonPos = 0; codonPos < CODON_LENGTH; codonPos++)
 +      /*
 +       * save nucleotide (and any variant) for each codon position
 +       */
 +      for (int codonPos = 0; codonPos < CODON_LENGTH; codonPos++)
 +      {
 +        String nucleotide = String.valueOf(
 +                dnaSeq.getCharAt(codon[codonPos] - dnaStart)).toUpperCase();
 +        List<DnaVariant> codonVariant = codonVariants[codonPos];
 +        if (codon[codonPos] == dnaCol)
          {
 -          String nucleotide = String
 -                  .valueOf(dnaSeq.getCharAt(codon[codonPos] - dnaStart))
 -                  .toUpperCase();
 -          List<DnaVariant> codonVariant = codonVariants[codonPos];
 -          if (codon[codonPos] == dnaCol)
 +          if (!codonVariant.isEmpty()
 +                  && codonVariant.get(0).variant == null)
            {
 -            if (!codonVariant.isEmpty()
 -                    && codonVariant.get(0).variant == null)
 -            {
 -              /*
 -               * already recorded base value, add this variant
 -               */
 -              codonVariant.get(0).variant = sf;
 -            }
 -            else
 -            {
 -              /*
 -               * add variant with base value
 -               */
 -              codonVariant.add(new DnaVariant(nucleotide, sf));
 -            }
 +            /*
 +             * already recorded base value, add this variant
 +             */
 +            codonVariant.get(0).variant = sf;
            }
 -          else if (codonVariant.isEmpty())
 +          else
            {
              /*
 -             * record (possibly non-varying) base value
 +             * add variant with base value
               */
 -            codonVariant.add(new DnaVariant(nucleotide));
 +            codonVariant.add(new DnaVariant(nucleotide, sf));
            }
          }
 +        else if (codonVariant.isEmpty())
 +        {
 +          /*
 +           * record (possibly non-varying) base value
 +           */
 +          codonVariant.add(new DnaVariant(nucleotide));
 +        }
        }
      }
      return variants;
       */
      for (SequenceI seq : unaligned.getSequences())
      {
-       List<SequenceI> alignedSequences = alignedDatasets.get(seq
-               .getDatasetSequence());
+       List<SequenceI> alignedSequences = alignedDatasets
+               .get(seq.getDatasetSequence());
        // TODO: getSequenceAsString() will be deprecated in the future
        // TODO: need to leave to SequenceI implementor to update gaps
        seq.setSequence(alignedSequences.get(0).getSequenceAsString());
     * @return
     */
    static SortedMap<Integer, Map<SequenceI, Character>> buildMappedColumnsMap(
-           AlignmentI unaligned, AlignmentI aligned, List<SequenceI> unmapped)
+           AlignmentI unaligned, AlignmentI aligned,
+           List<SequenceI> unmapped)
    {
      /*
       * Map will hold, for each aligned column position, a map of
    }
  
    /**
-    * Helper method that adds to a map the mapped column positions of a sequence. <br>
+    * Helper method that adds to a map the mapped column positions of a sequence.
+    * <br>
     * For example if aaTT-Tg-gAAA is mapped to TTTAAA then the map should record
     * that columns 3,4,6,10,11,12 map to characters T,T,T,A,A,A of the mapped to
     * sequence.
       */
      if (seqMap.getTo() == fromSeq.getDatasetSequence())
      {
-       seqMap = new Mapping(seq.getDatasetSequence(), seqMap.getMap()
-               .getInverse());
+       seqMap = new Mapping(seq.getDatasetSequence(),
+               seqMap.getMap().getInverse());
      }
  
 -    char[] fromChars = fromSeq.getSequence();
      int toStart = seq.getStart();
 -    char[] toChars = seq.getSequence();
  
      /*
       * traverse [start, end, start, end...] ranges in fromSeq
           * of the next character of the mapped-to sequence; stop when all
           * the characters of the range have been counted
           */
 -        while (mappedCharPos <= range[1] && fromCol <= fromChars.length
 +        while (mappedCharPos <= range[1] && fromCol <= fromSeq.getLength()
                  && fromCol >= 0)
          {
 -          if (!Comparison.isGap(fromChars[fromCol - 1]))
 +          if (!Comparison.isGap(fromSeq.getCharAt(fromCol - 1)))
            {
              /*
               * mapped from sequence has a character in this column
                seqsMap = new HashMap<SequenceI, Character>();
                map.put(fromCol, seqsMap);
              }
 -            seqsMap.put(seq, toChars[mappedCharPos - toStart]);
 +            seqsMap.put(seq, seq.getCharAt(mappedCharPos - toStart));
              mappedCharPos++;
            }
            fromCol += (forward ? 1 : -1);
@@@ -143,8 -143,8 +143,8 @@@ public class Conservatio
     * @param end
     *          end column position
     */
-   public Conservation(String name, int threshold,
-           List<SequenceI> sequences, int start, int end)
+   public Conservation(String name, int threshold, List<SequenceI> sequences,
+           int start, int end)
    {
      this.name = name;
      this.threshold = threshold;
      else
      {
        // JBPNote INFO level debug
-       System.err
-               .println("ERROR: calcSeqNum called with out of range sequence index for Alignment\n");
+       System.err.println(
+               "ERROR: calcSeqNum called with out of range sequence index for Alignment\n");
      }
    }
  
          continue;
        }
  
-       char c = sequences[i].getCharAt(column); // gaps do not have upper/lower case
+       char c = sequences[i].getCharAt(column); // gaps do not have upper/lower
+                                                // case
  
        if (Comparison.isGap((c)))
        {
     * @param endCol
     * @param scoreMatrix
     */
-   protected void findQuality(int startCol, int endCol, ScoreMatrix scoreMatrix)
+   protected void findQuality(int startCol, int endCol,
+           ScoreMatrix scoreMatrix)
    {
      quality = new Vector<Double>();
  
    public void completeAnnotations(AlignmentAnnotation conservation,
            AlignmentAnnotation quality2, int istart, int alWidth)
    {
 -    char[] sequence = getConsSequence().getSequence();
 -    float minR;
 -    float minG;
 -    float minB;
 -    float maxR;
 -    float maxG;
 -    float maxB;
 -    minR = 0.3f;
 -    minG = 0.0f;
 -    minB = 0f;
 -    maxR = 1.0f - minR;
 -    maxG = 0.9f - minG;
 -    maxB = 0f - minB; // scalable range for colouring both Conservation and
 -    // Quality
 +    SequenceI cons = getConsSequence();
 +
 +    /*
 +     * colour scale for Conservation and Quality;
 +     */
 +    float minR = 0.3f;
 +    float minG = 0.0f;
 +    float minB = 0f;
 +    float maxR = 1.0f - minR;
 +    float maxG = 0.9f - minG;
 +    float maxB = 0f - minB;
  
      float min = 0f;
      float max = 11f;
      float qmin = 0f;
      float qmax = 0f;
  
 -    char c;
 -
      if (conservation != null && conservation.annotations != null
              && conservation.annotations.length < alWidth)
      {
      {
        float value = 0;
  
 -      c = sequence[i];
 +      char c = cons.getCharAt(i);
  
        if (Character.isDigit(c))
        {
          float vprop = value - min;
          vprop /= max;
          int consp = i - start;
-         String conssym = (value > 0 && consp > -1 && consp < consSymbs.length) ? consSymbs[consp]
-                 : "";
+         String conssym = (value > 0 && consp > -1
+                 && consp < consSymbs.length) ? consSymbs[consp] : "";
          conservation.annotations[i] = new Annotation(String.valueOf(c),
-                 conssym, ' ', value, new Color(minR + (maxR * vprop), minG
-                         + (maxG * vprop), minB + (maxB * vprop)));
+                 conssym, ' ', value, new Color(minR + (maxR * vprop),
+                         minG + (maxG * vprop), minB + (maxB * vprop)));
        }
  
        // Quality calc
          value = quality.elementAt(i).floatValue();
          float vprop = value - qmin;
          vprop /= qmax;
-         quality2.annotations[i] = new Annotation(" ",
-                 String.valueOf(value), ' ', value, new Color(minR
-                         + (maxR * vprop), minG + (maxG * vprop), minB
-                         + (maxB * vprop)));
+         quality2.annotations[i] = new Annotation(" ", String.valueOf(value),
+                 ' ', value, new Color(minR + (maxR * vprop),
+                         minG + (maxG * vprop), minB + (maxB * vprop)));
        }
      }
    }
     */
    String getTooltip(int column)
    {
 -    char[] sequence = getConsSequence().getSequence();
 -    char val = column < sequence.length ? sequence[column] : '-';
 +    SequenceI cons = getConsSequence();
 +    char val = column < cons.getLength() ? cons.getCharAt(column) : '-';
      boolean hasConservation = val != '-' && val != '0';
      int consp = column - start;
-     String tip = (hasConservation && consp > -1 && consp < consSymbs.length) ? consSymbs[consp]
+     String tip = (hasConservation && consp > -1 && consp < consSymbs.length)
+             ? consSymbs[consp]
              : "";
      return tip;
    }
@@@ -164,8 -164,8 +164,8 @@@ public class CrossRe
         */
        for (SequenceI rs : foundSeqs)
        {
-         DBRefEntry[] xrs = DBRefUtils
-                 .selectDbRefs(!fromDna, rs.getDBRefs());
+         DBRefEntry[] xrs = DBRefUtils.selectDbRefs(!fromDna,
+                 rs.getDBRefs());
          addXrefsToSources(xrs, sources);
        }
      }
          dss = dss.getDatasetSequence();
        }
        boolean found = false;
-       DBRefEntry[] xrfs = DBRefUtils
-               .selectDbRefs(!fromDna, dss.getDBRefs());
+       DBRefEntry[] xrfs = DBRefUtils.selectDbRefs(!fromDna,
+               dss.getDBRefs());
        // ENST & ENSP comes in to both Protein and nucleotide, so we need to
        // filter them
        // out later.
              if (matchInDataset != null && xref.getMap().getTo() != null
                      && matchInDataset != xref.getMap().getTo())
              {
-               System.err
-                       .println("Implementation problem (reopen JAL-2154): CrossRef.findInDataset seems to have recovered a different sequence than the one explicitly mapped for xref."
-                               + "Found:"
-                               + matchInDataset
-                               + "\nExpected:"
-                               + xref.getMap().getTo()
-                               + "\nFor xref:"
+               System.err.println(
+                       "Implementation problem (reopen JAL-2154): CrossRef.findInDataset seems to have recovered a different sequence than the one explicitly mapped for xref."
+                               + "Found:" + matchInDataset + "\nExpected:"
+                               + xref.getMap().getTo() + "\nFor xref:"
                                + xref);
              }
              /*matcher.findIdMatch(mappedTo);*/
                  }
                  else
                  {
-                   cf.addMap(matchInDataset, dss, xref.getMap().getMap()
-                           .getInverse(), xref.getMap().getMappedFromId());
+                   cf.addMap(matchInDataset, dss,
+                           xref.getMap().getMap().getInverse(),
+                           xref.getMap().getMappedFromId());
                  }
                }
  
                if (fromDna)
                {
                  // map is from dna seq to a protein product
-                 cf.addMap(dss, rsq, xref.getMap().getMap(), xref.getMap()
-                         .getMappedFromId());
+                 cf.addMap(dss, rsq, xref.getMap().getMap(),
+                         xref.getMap().getMappedFromId());
                }
                else
                {
  
          if (!found)
          {
-           SequenceI matchedSeq = matcher.findIdMatch(xref.getSource() + "|"
-                   + xref.getAccessionId());
+           SequenceI matchedSeq = matcher.findIdMatch(
+                   xref.getSource() + "|" + xref.getAccessionId());
            // if there was a match, check it's at least the right type of
            // molecule!
            if (matchedSeq != null && matchedSeq.isProtein() == fromDna)
    {
      ASequenceFetcher sftch = SequenceFetcherFactory.getSequenceFetcher();
      SequenceI[] retrieved = null;
-     SequenceI dss = seq.getDatasetSequence() == null ? seq : seq
-             .getDatasetSequence();
+     SequenceI dss = seq.getDatasetSequence() == null ? seq
+             : seq.getDatasetSequence();
      // first filter in case we are retrieving crossrefs that have already been
      // retrieved. this happens for cases where a database record doesn't yield
      // protein products for CDS
        retrieved = sftch.getSequences(sourceRefs, !fromDna);
      } catch (Exception e)
      {
-       System.err
-               .println("Problem whilst retrieving cross references for Sequence : "
+       System.err.println(
+               "Problem whilst retrieving cross references for Sequence : "
                        + seq.getName());
        e.printStackTrace();
      }
      if (retrieved != null)
      {
        boolean addedXref = false;
-       List<SequenceI> newDsSeqs = new ArrayList<SequenceI>(), doNotAdd = new ArrayList<SequenceI>();
+       List<SequenceI> newDsSeqs = new ArrayList<SequenceI>(),
+               doNotAdd = new ArrayList<SequenceI>();
  
        for (SequenceI retrievedSequence : retrieved)
        {
          // dataset gets contaminated ccwith non-ds sequences. why ??!
          // try: Ensembl -> Nuc->Ensembl, Nuc->Uniprot-->Protein->EMBL->
-         SequenceI retrievedDss = retrievedSequence.getDatasetSequence() == null ? retrievedSequence
-                 : retrievedSequence.getDatasetSequence();
+         SequenceI retrievedDss = retrievedSequence
+                 .getDatasetSequence() == null ? retrievedSequence
+                         : retrievedSequence.getDatasetSequence();
          addedXref |= importCrossRefSeq(cf, newDsSeqs, doNotAdd, dss,
                  retrievedDss);
        }
          {
            // dataset gets contaminated ccwith non-ds sequences. why ??!
            // try: Ensembl -> Nuc->Ensembl, Nuc->Uniprot-->Protein->EMBL->
-           SequenceI retrievedDss = retrievedSequence.getDatasetSequence() == null ? retrievedSequence
-                   : retrievedSequence.getDatasetSequence();
+           SequenceI retrievedDss = retrievedSequence
+                   .getDatasetSequence() == null ? retrievedSequence
+                           : retrievedSequence.getDatasetSequence();
            addedXref |= importCrossRefSeq(cf, newDsSeqs, doNotAdd, dss,
                    retrievedDss);
          }
                int sf = map.getMap().getToLowest();
                int st = map.getMap().getToHighest();
                SequenceI mappedrg = ms.getSubSequence(sf, st);
-               if (mappedrg.getLength() > 0
-                       && ms.getSequenceAsString().equals(
-                               matched.getSequenceAsString()))
+               if (mappedrg.getLength() > 0 && ms.getSequenceAsString()
+                       .equals(matched.getSequenceAsString()))
                {
                  /*
                   * sequences were a match, 
                     */
                    for (DBRefEntry ref : toRefs)
                    {
-                     if (dbref.getSrcAccString().equals(
-                             ref.getSrcAccString()))
+                     if (dbref.getSrcAccString()
+                             .equals(ref.getSrcAccString()))
                      {
                        continue; // avoid overwriting the ref on source sequence
                      }
                   * duplication (e.g. same variation from two 
                   * transcripts)
                   */
 -                SequenceFeature[] sfs = ms.getSequenceFeatures();
 -                if (sfs != null)
 +                List<SequenceFeature> sfs = ms.getFeatures()
 +                        .getAllFeatures();
 +                for (SequenceFeature feat : sfs)
                  {
 -                  for (SequenceFeature feat : sfs)
 +                  /*
 +                   * make a flyweight feature object which ignores Parent
 +                   * attribute in equality test; this avoids creating many
 +                   * otherwise duplicate exon features on genomic sequence
 +                   */
 +                  SequenceFeature newFeature = new SequenceFeature(feat)
                    {
 -                    /*
 -                     * make a flyweight feature object which ignores Parent
 -                     * attribute in equality test; this avoids creating many
 -                     * otherwise duplicate exon features on genomic sequence
 -                     */
 -                    SequenceFeature newFeature = new SequenceFeature(feat)
 +                    @Override
 +                    public boolean equals(Object o)
                      {
 -                      @Override
 -                      public boolean equals(Object o)
 -                      {
 -                        return super.equals(o, true);
 -                      }
 -                    };
 -                    matched.addSequenceFeature(newFeature);
 -                  }
 +                      return super.equals(o, true);
 +                    }
 +                  };
 +                  matched.addSequenceFeature(newFeature);
                  }
 -
                }
                cf.addMap(retrievedSequence, map.getTo(), map.getMap());
              } catch (Exception e)
              {
-               System.err
-                       .println("Exception when consolidating Mapped sequence set...");
+               System.err.println(
+                       "Exception when consolidating Mapped sequence set...");
                e.printStackTrace(System.err);
              }
            }
      SequenceI mapsTo = xref.getMap().getTo();
      String name = xref.getAccessionId();
      String name2 = xref.getSource() + "|" + name;
-     SequenceI dss = mapsTo.getDatasetSequence() == null ? mapsTo : mapsTo
-             .getDatasetSequence();
+     SequenceI dss = mapsTo.getDatasetSequence() == null ? mapsTo
+             : mapsTo.getDatasetSequence();
      // first check ds if ds is directly referenced
      if (dataset.findIndex(dss) > -1)
      {
      for (SequenceI seq : dataset.getSequences())
      {
        // first check primary refs.
-       List<DBRefEntry> match = DBRefUtils.searchRefs(seq.getPrimaryDBRefs()
-               .toArray(new DBRefEntry[0]), template);
+       List<DBRefEntry> match = DBRefUtils.searchRefs(
+               seq.getPrimaryDBRefs().toArray(new DBRefEntry[0]), template);
        if (match != null && match.size() == 1 && sameSequence(seq, dss))
        {
          return seq;
         * returns sequences with a dbref to the matched accession id 
         * which we don't want
         */
-       if (firstIdMatch == null
-               && (name.equals(seq.getName()) || seq.getName().startsWith(
-                       name2)))
+       if (firstIdMatch == null && (name.equals(seq.getName())
+               || seq.getName().startsWith(name2)))
        {
          if (sameSequence(seq, dss))
          {
      {
        return false;
      }
 -    char[] c1 = seq1.getSequence();
 -    char[] c2 = seq2.getSequence();
 -    if (c1.length != c2.length)
 +
 +    if (seq1.getLength() != seq2.getLength())
      {
        return false;
      }
 -    for (int i = 0; i < c1.length; i++)
 +    int length = seq1.getLength();
 +    for (int i = 0; i < length; i++)
      {
 -      int diff = c1[i] - c2[i];
 +      int diff = seq1.getCharAt(i) - seq2.getCharAt(i);
        /*
         * same char or differ in case only ('a'-'A' == 32)
         */
      MapList mapping = null;
      SequenceI dsmapFrom = mapFrom.getDatasetSequence() == null ? mapFrom
              : mapFrom.getDatasetSequence();
-     SequenceI dsmapTo = mapTo.getDatasetSequence() == null ? mapTo : mapTo
-             .getDatasetSequence();
+     SequenceI dsmapTo = mapTo.getDatasetSequence() == null ? mapTo
+             : mapTo.getDatasetSequence();
      /*
       * look for a reverse mapping, if found make its inverse. 
       * Note - we do this on dataset sequences only.
     * @return true if matches were found.
     */
    private boolean searchDatasetXrefs(boolean fromDna, SequenceI sequenceI,
-           DBRefEntry[] lrfs, List<SequenceI> foundSeqs, AlignedCodonFrame cf)
+           DBRefEntry[] lrfs, List<SequenceI> foundSeqs,
+           AlignedCodonFrame cf)
    {
      boolean found = false;
      if (lrfs == null)
        // add in wildcards
        xref.setVersion(null);
        xref.setMap(null);
-       found |= searchDataset(fromDna, sequenceI, xref, foundSeqs, cf, false);
+       found |= searchDataset(fromDna, sequenceI, xref, foundSeqs, cf,
+               false);
      }
      return found;
    }
          {
            if (nxt.getDatasetSequence() != null)
            {
-             System.err
-                     .println("Implementation warning: CrossRef initialised with a dataset alignment with non-dataset sequences in it! ("
-                             + nxt.getDisplayId(true)
-                             + " has ds reference "
+             System.err.println(
+                     "Implementation warning: CrossRef initialised with a dataset alignment with non-dataset sequences in it! ("
+                             + nxt.getDisplayId(true) + " has ds reference "
                              + nxt.getDatasetSequence().getDisplayId(true)
                              + ")");
            }
@@@ -45,6 -45,7 +45,6 @@@ import java.util.ArrayList
  import java.util.Arrays;
  import java.util.Comparator;
  import java.util.List;
 -import java.util.Map;
  
  public class Dna
  {
     * @param ac2
     * @return
     */
 -  public static final int compareCodonPos(AlignedCodon ac1,
 -          AlignedCodon ac2)
 +  public static final int compareCodonPos(AlignedCodon ac1, AlignedCodon ac2)
    {
      return comparator.compare(ac1, ac2);
      // return jalview_2_8_2compare(ac1, ac2);
     * @param ac2
     * @return
     */
-   private static int jalview_2_8_2compare(AlignedCodon ac1, AlignedCodon ac2)
+   private static int jalview_2_8_2compare(AlignedCodon ac1,
+           AlignedCodon ac2)
    {
      if (ac1 == null || ac2 == null || (ac1.equals(ac2)))
      {
          /*
           * Filled up a reading frame...
           */
-         AlignedCodon alignedCodon = new AlignedCodon(cdp[0], cdp[1], cdp[2]);
+         AlignedCodon alignedCodon = new AlignedCodon(cdp[0], cdp[1],
+                 cdp[2]);
          String aa = ResidueProperties.codonTranslate(new String(codon));
          rf = 0;
          final String gapString = String.valueOf(gapChar);
            aa = gapString;
            if (skipint == null)
            {
-             skipint = new int[] { alignedCodon.pos1, alignedCodon.pos3 /*
-                                                                         * cdp[0],
-                                                                         * cdp[2]
-                                                                         */};
+             skipint = new int[] { alignedCodon.pos1,
+                 alignedCodon.pos3 /*
+                                    * cdp[0],
+                                    * cdp[2]
+                                    */ };
            }
            skipint[1] = alignedCodon.pos3; // cdp[2];
          }
                        }
                        if (vc + 2 < t.length)
                        {
-                         System.arraycopy(scontigs, vc + 2, t, vc, t.length
-                                 - vc + 2);
+                         System.arraycopy(scontigs, vc + 2, t, vc,
+                                 t.length - vc + 2);
                        }
                        scontigs = t;
                      }
          }
          else if (!alignedCodons[aspos].equals(alignedCodon))
          {
-           throw new IllegalStateException("Tried to coalign "
-                   + alignedCodons[aspos].toString() + " with "
-                   + alignedCodon.toString());
+           throw new IllegalStateException(
+                   "Tried to coalign " + alignedCodons[aspos].toString()
+                           + " with " + alignedCodon.toString());
          }
          if (aspos >= aaWidth)
          {
           */
          MapList map = new MapList(scontigs, new int[] { 1, resSize }, 3, 1);
  
 -        transferCodedFeatures(selection, newseq, map, null, null);
 +        transferCodedFeatures(selection, newseq, map);
  
          /*
           * Construct a dataset sequence for our new peptide.
  
    /**
     * Given a peptide newly translated from a dna sequence, copy over and set any
 -   * features on the peptide from the DNA. If featureTypes is null, all features
 -   * on the dna sequence are searched (rather than just the displayed ones), and
 -   * similarly for featureGroups.
 +   * features on the peptide from the DNA.
     * 
     * @param dna
     * @param pep
     * @param map
 -   * @param featureTypes
 -   *          hash whose keys are the displayed feature type strings
 -   * @param featureGroups
 -   *          hash where keys are feature groups and values are Boolean objects
 -   *          indicating if they are displayed.
     */
    private static void transferCodedFeatures(SequenceI dna, SequenceI pep,
 -          MapList map, Map<String, Object> featureTypes,
 -          Map<String, Boolean> featureGroups)
 +          MapList map)
    {
 -    SequenceFeature[] sfs = dna.getSequenceFeatures();
 -    Boolean fgstate;
      DBRefEntry[] dnarefs = DBRefUtils.selectRefs(dna.getDBRefs(),
              DBRefSource.DNACODINGDBS);
      if (dnarefs != null)
          }
        }
      }
 -    if (sfs != null)
 +    for (SequenceFeature sf : dna.getFeatures().getAllFeatures())
      {
 -      for (SequenceFeature sf : sfs)
 -      {
 -        fgstate = (featureGroups == null) ? null
 -                : featureGroups.get(sf.featureGroup);
 -        if ((featureTypes == null || featureTypes.containsKey(sf.getType()))
 -                && (fgstate == null || fgstate.booleanValue()))
 +        if (FeatureProperties.isCodingFeature(null, sf.getType()))
          {
 -          if (FeatureProperties.isCodingFeature(null, sf.getType()))
 +          // if (map.intersectsFrom(sf[f].begin, sf[f].end))
            {
 -            // if (map.intersectsFrom(sf[f].begin, sf[f].end))
 -            {
  
 -            }
            }
          }
 -      }
      }
    }
  
@@@ -31,11 -31,10 +31,11 @@@ import jalview.datamodel.SequenceFeatur
  import jalview.util.MessageManager;
  
  import java.util.ArrayList;
 +import java.util.HashMap;
  import java.util.Hashtable;
  import java.util.List;
 +import java.util.Map;
  import java.util.Stack;
 -import java.util.Vector;
  
  public class Rna
  {
@@@ -49,7 -48,8 +49,8 @@@
     */
    public static boolean isOpeningParenthesis(char c)
    {
-     return ('A' <= c && c <= 'Z' || c == '(' || c == '[' || c == '{' || c == '<');
+     return ('A' <= c && c <= 'Z' || c == '(' || c == '[' || c == '{'
+             || c == '<');
    }
  
    /**
@@@ -74,7 -74,8 +75,8 @@@
     */
    public static boolean isClosingParenthesis(char c)
    {
-     return ('a' <= c && c <= 'z' || c == ')' || c == ']' || c == '}' || c == '>');
+     return ('a' <= c && c <= 'z' || c == ')' || c == ']' || c == '}'
+             || c == '>');
    }
  
    /**
     * @return
     * @throw {@link WUSSParseException}
     */
 -  public static Vector<SimpleBP> getSimpleBPs(CharSequence line)
 +  protected static List<SimpleBP> getSimpleBPs(CharSequence line)
            throws WUSSParseException
    {
      Hashtable<Character, Stack<Integer>> stacks = new Hashtable<Character, Stack<Integer>>();
 -    Vector<SimpleBP> pairs = new Vector<SimpleBP>();
 +    List<SimpleBP> pairs = new ArrayList<SimpleBP>();
      int i = 0;
      while (i < line.length())
      {
          if (!stacks.containsKey(opening))
          {
            throw new WUSSParseException(MessageManager.formatMessage(
-                   "exception.mismatched_unseen_closing_char",
-                   new String[] { String.valueOf(base) }), i);
+                   "exception.mismatched_unseen_closing_char", new String[]
+                   { String.valueOf(base) }), i);
          }
  
          Stack<Integer> stack = stacks.get(opening);
          {
            // error whilst parsing i'th position. pass back
            throw new WUSSParseException(MessageManager.formatMessage(
-                   "exception.mismatched_closing_char",
-                   new String[] { String.valueOf(base) }), i);
+                   "exception.mismatched_closing_char", new String[]
+                   { String.valueOf(base) }), i);
          }
          int temp = stack.pop();
  
           * i (length of input string)
           */
          throw new WUSSParseException(MessageManager.formatMessage(
-                 "exception.mismatched_opening_char",
-                 new String[] { String.valueOf(opening),
-                     String.valueOf(stack.pop()) }), i);
+                 "exception.mismatched_opening_char", new String[]
+                 { String.valueOf(opening), String.valueOf(stack.pop()) }),
+                 i);
        }
      }
      return pairs;
    }
  
 -  public static SequenceFeature[] getBasePairs(List<SimpleBP> bps)
 -          throws WUSSParseException
 -  {
 -    SequenceFeature[] outPairs = new SequenceFeature[bps.size()];
 -    for (int p = 0; p < bps.size(); p++)
 -    {
 -      SimpleBP bp = bps.get(p);
 -      outPairs[p] = new SequenceFeature("RNA helix", "", "", bp.getBP5(),
 -              bp.getBP3(), "");
 -    }
 -    return outPairs;
 -  }
 +  
  
 -  public static List<SimpleBP> getModeleBP(CharSequence line)
 -          throws WUSSParseException
 -  {
 -    Vector<SimpleBP> bps = getSimpleBPs(line);
 -    return new ArrayList<SimpleBP>(bps);
 -  }
 +  
  
    /**
     * Function to get the end position corresponding to a given start position
     */
  
    /**
 -   * Figures out which helix each position belongs to and stores the helix
 -   * number in the 'featureGroup' member of a SequenceFeature Based off of RALEE
 -   * code ralee-helix-map.
 -   * 
 -   * @param pairs
 -   *          Array of SequenceFeature (output from Rna.GetBasePairs)
 -   */
 -  public static void HelixMap(SequenceFeature[] pairs)
 -  {
 -
 -    int helix = 0; // Number of helices/current helix
 -    int lastopen = 0; // Position of last open bracket reviewed
 -    int lastclose = 9999999; // Position of last close bracket reviewed
 -    int i = pairs.length; // Number of pairs
 -
 -    int open; // Position of an open bracket under review
 -    int close; // Position of a close bracket under review
 -    int j; // Counter
 -
 -    Hashtable<Integer, Integer> helices = new Hashtable<Integer, Integer>();
 -    // Keep track of helix number for each position
 -
 -    // Go through each base pair and assign positions a helix
 -    for (i = 0; i < pairs.length; i++)
 -    {
 -
 -      open = pairs[i].getBegin();
 -      close = pairs[i].getEnd();
 -
 -      // System.out.println("open " + open + " close " + close);
 -      // System.out.println("lastclose " + lastclose + " lastopen " + lastopen);
 -
 -      // we're moving from right to left based on closing pair
 -      /*
 -       * catch things like <<..>>..<<..>> |
 -       */
 -      if (open > lastclose)
 -      {
 -        helix++;
 -      }
 -
 -      /*
 -       * catch things like <<..<<..>>..<<..>>>> |
 -       */
 -      j = pairs.length - 1;
 -      while (j >= 0)
 -      {
 -        int popen = pairs[j].getBegin();
 -
 -        // System.out.println("j " + j + " popen " + popen + " lastopen "
 -        // +lastopen + " open " + open);
 -        if ((popen < lastopen) && (popen > open))
 -        {
 -          if (helices.containsValue(popen)
 -                  && ((helices.get(popen)) == helix))
 -          {
 -            continue;
 -          }
 -          else
 -          {
 -            helix++;
 -            break;
 -          }
 -        }
 -
 -        j -= 1;
 -      }
 -
 -      // Put positions and helix information into the hashtable
 -      helices.put(open, helix);
 -      helices.put(close, helix);
 -
 -      // Record helix as featuregroup
 -      pairs[i].setFeatureGroup(Integer.toString(helix));
 -
 -      lastopen = open;
 -      lastclose = close;
 -
 -    }
 -  }
 -
 -  /**
     * Answers true if the character is a recognised symbol for RNA secondary
     * structure. Currently accepts a-z, A-Z, ()[]{}<>.
     * 
        return c;
      }
    }
 +
 +  public static SequenceFeature[] getHelixMap(CharSequence rnaAnnotation)
 +          throws WUSSParseException
 +  {
 +    List<SequenceFeature> result = new ArrayList<SequenceFeature>();
 +
 +    int helix = 0; // Number of helices/current helix
 +    int lastopen = 0; // Position of last open bracket reviewed
 +    int lastclose = 9999999; // Position of last close bracket reviewed
 +
 +    Map<Integer, Integer> helices = new HashMap<Integer, Integer>();
 +    // Keep track of helix number for each position
 +
 +    // Go through each base pair and assign positions a helix
 +    List<SimpleBP> bps = getSimpleBPs(rnaAnnotation);
 +    for (SimpleBP basePair : bps)
 +    {
 +      final int open = basePair.getBP5();
 +      final int close = basePair.getBP3();
 +
 +      // System.out.println("open " + open + " close " + close);
 +      // System.out.println("lastclose " + lastclose + " lastopen " + lastopen);
 +
 +      // we're moving from right to left based on closing pair
 +      /*
 +       * catch things like <<..>>..<<..>> |
 +       */
 +      if (open > lastclose)
 +      {
 +        helix++;
 +      }
 +
 +      /*
 +       * catch things like <<..<<..>>..<<..>>>> |
 +       */
 +      int j = bps.size() - 1;
 +      while (j >= 0)
 +      {
 +        int popen = bps.get(j).getBP5();
 +
 +        // System.out.println("j " + j + " popen " + popen + " lastopen "
 +        // +lastopen + " open " + open);
 +        if ((popen < lastopen) && (popen > open))
 +        {
 +          if (helices.containsValue(popen)
 +                  && ((helices.get(popen)) == helix))
 +          {
 +            continue;
 +          }
 +          else
 +          {
 +            helix++;
 +            break;
 +          }
 +        }
 +        j -= 1;
 +      }
 +
 +      // Put positions and helix information into the hashtable
 +      helices.put(open, helix);
 +      helices.put(close, helix);
 +
 +      // Record helix as featuregroup
 +      result.add(new SequenceFeature("RNA helix", "", open, close,
 +              String.valueOf(helix)));
 +
 +      lastopen = open;
 +      lastclose = close;
 +    }
 +
 +    return result.toArray(new SequenceFeature[result.size()]);
 +  }
  }
@@@ -27,7 -27,6 +27,7 @@@ import jalview.datamodel.SequenceI
  
  import java.util.Enumeration;
  import java.util.Hashtable;
 +import java.util.List;
  import java.util.Vector;
  
  public class SeqsetUtils
      {
        sqinfo.put("Description", seq.getDescription());
      }
 -    Vector sfeat = new Vector();
 -    jalview.datamodel.SequenceFeature[] sfarray = seq.getSequenceFeatures();
 -    if (sfarray != null && sfarray.length > 0)
 -    {
 -      for (int i = 0; i < sfarray.length; i++)
 -      {
 -        sfeat.addElement(sfarray[i]);
 -      }
 -    }
 +
 +    Vector<SequenceFeature> sfeat = new Vector<SequenceFeature>();
 +    List<SequenceFeature> sfs = seq.getFeatures().getAllFeatures();
 +    sfeat.addAll(sfs);
 +
      if (seq.getDatasetSequence() == null)
      {
        sqinfo.put("SeqFeatures", sfeat);
@@@ -92,8 -95,7 +92,8 @@@
      String oldname = (String) sqinfo.get("Name");
      Integer start = (Integer) sqinfo.get("Start");
      Integer end = (Integer) sqinfo.get("End");
 -    Vector sfeatures = (Vector) sqinfo.get("SeqFeatures");
 +    Vector<SequenceFeature> sfeatures = (Vector<SequenceFeature>) sqinfo
 +            .get("SeqFeatures");
      Vector<PDBEntry> pdbid = (Vector<PDBEntry>) sqinfo.get("PdbId");
      String description = (String) sqinfo.get("Description");
      Sequence seqds = (Sequence) sqinfo.get("datasetSequence");
        sq.setEnd(end.intValue());
      }
  
 -    if ((sfeatures != null) && (sfeatures.size() > 0))
 +    if (sfeatures != null && !sfeatures.isEmpty())
      {
 -      SequenceFeature[] sfarray = new SequenceFeature[sfeatures.size()];
 -      for (int is = 0, isize = sfeatures.size(); is < isize; is++)
 -      {
 -        sfarray[is] = (SequenceFeature) sfeatures.elementAt(is);
 -      }
 -      sq.setSequenceFeatures(sfarray);
 +      sq.setSequenceFeatures(sfeatures);
      }
      if (description != null)
      {
        sq.setDescription(description);
      }
-     if ((seqds != null)
-             && !(seqds.getName().equals("THISISAPLACEHOLDER") && seqds
-                     .getLength() == 0))
+     if ((seqds != null) && !(seqds.getName().equals("THISISAPLACEHOLDER")
+             && seqds.getLength() == 0))
      {
        if (sfeatures != null)
        {
-         System.err
-                 .println("Implementation error: setting dataset sequence for a sequence which has sequence features.\n\tDataset sequence features will not be visible.");
+         System.err.println(
+                 "Implementation error: setting dataset sequence for a sequence which has sequence features.\n\tDataset sequence features will not be visible.");
        }
        sq.setDatasetSequence(seqds);
      }
      if (unmatched.size() > 0 && !quiet)
      {
        System.err.println("Did not find matches for :");
-       for (Enumeration i = unmatched.elements(); i.hasMoreElements(); System.out
-               .println(((SequenceI) i.nextElement()).getName()))
+       for (Enumeration i = unmatched.elements(); i
+               .hasMoreElements(); System.out
+                       .println(((SequenceI) i.nextElement()).getName()))
        {
          ;
        }
@@@ -124,8 -124,8 +124,8 @@@ public class FeatureDistanceModel exten
          /*
           * first record feature types in this column for each sequence
           */
-         Map<SeqCigar, Set<String>> sfap = findFeatureTypesAtColumn(
-                 seqs, cpos);
+         Map<SeqCigar, Set<String>> sfap = findFeatureTypesAtColumn(seqs,
+                 cpos);
  
          /*
           * count feature types on either i'th or j'th sequence but not both
    /**
     * Builds and returns a map containing a (possibly empty) list (one per
     * SeqCigar) of visible feature types at the given column position. The map
 -   * has no entry for sequences which are gapped at the column position.
 +   * does not include entries for features which straddle a gapped column
 +   * positions.
     * 
     * @param seqs
     * @param columnPosition
 +   *          (0..)
     * @return
     */
    protected Map<SeqCigar, Set<String>> findFeatureTypesAtColumn(
        int spos = seq.findPosition(columnPosition);
        if (spos != -1)
        {
 +        /*
 +         * position is not a gap
 +         */
          Set<String> types = new HashSet<String>();
 -        List<SequenceFeature> sfs = fr.findFeaturesAtRes(seq.getRefSeq(),
 -                spos);
 +        List<SequenceFeature> sfs = fr.findFeaturesAtResidue(
 +                seq.getRefSeq(), spos);
          for (SequenceFeature sf : sfs)
          {
            types.add(sf.getType());
@@@ -76,8 -76,8 +76,8 @@@ import java.util.SortedMap
  import java.util.TreeMap;
  import java.util.Vector;
  
- public class APopupMenu extends java.awt.PopupMenu implements
-         ActionListener, ItemListener
+ public class APopupMenu extends java.awt.PopupMenu
+         implements ActionListener, ItemListener
  {
    Menu groupMenu = new Menu();
  
          purinePyrimidineColour.setEnabled(false);
          nucleotideColour.setEnabled(false);
        }
-       editGroupName.setLabel(MessageManager.formatMessage(
-               "label.name_param", new Object[] { sg.getName() }));
+       editGroupName.setLabel(
+               MessageManager.formatMessage("label.name_param", new Object[]
+               { sg.getName() }));
        showText.setState(sg.getDisplayText());
        showColourText.setState(sg.getColourText());
        showBoxes.setState(sg.getDisplayBoxes());
        seqMenu.setLabel(seq.getName());
        if (seq == ap.av.getAlignment().getSeqrep())
        {
-         makeReferenceSeq.setLabel(MessageManager
-                 .getString("action.unmark_as_reference"));// Unmark
-                                                           // representative");
+         makeReferenceSeq.setLabel(
+                 MessageManager.getString("action.unmark_as_reference"));// Unmark
+                                                                         // representative");
        }
        else
        {
-         makeReferenceSeq.setLabel(MessageManager
-                 .getString("action.set_as_reference")); // );
+         makeReferenceSeq.setLabel(
+                 MessageManager.getString("action.set_as_reference")); // );
        }
-       repGroup.setLabel(MessageManager.formatMessage(
-               "label.represent_group_with", new Object[] { seq.getName() }));
+       repGroup.setLabel(MessageManager
+               .formatMessage("label.represent_group_with", new Object[]
+               { seq.getName() }));
      }
      else
      {
      /*
       * First for the currently selected sequence (if there is one):
       */
-     final List<SequenceI> selectedSequence = (seq == null ? Collections
-             .<SequenceI> emptyList() : Arrays.asList(seq));
+     final List<SequenceI> selectedSequence = (seq == null
+             ? Collections.<SequenceI> emptyList()
+             : Arrays.asList(seq));
      buildAnnotationTypesMenus(seqShowAnnotationsMenu,
              seqHideAnnotationsMenu, selectedSequence);
      configureReferenceAnnotationsMenu(seqAddReferenceAnnotations,
      /*
       * and repeat for the current selection group (if there is one):
       */
-     final List<SequenceI> selectedGroup = (ap.av.getSelectionGroup() == null ? Collections
-             .<SequenceI> emptyList() : ap.av.getSelectionGroup()
-             .getSequences());
+     final List<SequenceI> selectedGroup = (ap.av.getSelectionGroup() == null
+             ? Collections.<SequenceI> emptyList()
+             : ap.av.getSelectionGroup().getSequences());
      buildAnnotationTypesMenus(groupShowAnnotationsMenu,
              groupHideAnnotationsMenu, selectedGroup);
      configureReferenceAnnotationsMenu(groupAddReferenceAnnotations,
      SortedMap<String, String> tipEntries = new TreeMap<String, String>();
      final Map<SequenceI, List<AlignmentAnnotation>> candidates = new LinkedHashMap<SequenceI, List<AlignmentAnnotation>>();
      AlignmentI al = this.ap.av.getAlignment();
-     AlignmentUtils.findAddableReferenceAnnotations(forSequences,
-             tipEntries, candidates, al);
+     AlignmentUtils.findAddableReferenceAnnotations(forSequences, tipEntries,
+             candidates, al);
      if (!candidates.isEmpty())
      {
        StringBuilder tooltip = new StringBuilder(64);
            seq = sg.getSequenceAt(0);
          }
  
-         EditNameDialog dialog = new EditNameDialog(seq.getSequenceAsString(
-                 sg.getStartRes(), sg.getEndRes() + 1), null,
-                 "Edit Sequence ", null,
+         EditNameDialog dialog = new EditNameDialog(
+                 seq.getSequenceAsString(sg.getStartRes(),
+                         sg.getEndRes() + 1),
+                 null, "Edit Sequence ", null,
  
                  ap.alignFrame, "Edit Sequence", 500, 100, true);
  
          {
            EditCommand editCommand = new EditCommand(
                    MessageManager.getString("label.edit_sequences"),
-                   Action.REPLACE, dialog.getName().replace(' ',
-                           ap.av.getGapCharacter()),
+                   Action.REPLACE,
+                   dialog.getName().replace(' ', ap.av.getGapCharacter()),
                    sg.getSequencesAsArray(ap.av.getHiddenRepSequences()),
                    sg.getStartRes(), sg.getEndRes() + 1,
                    ap.av.getAlignment());
  
            ap.alignFrame.addHistoryItem(editCommand);
  
-           ap.av.firePropertyChange("alignment", null, ap.av.getAlignment()
-                   .getSequences());
+           ap.av.firePropertyChange("alignment", null,
+                   ap.av.getAlignment().getSequences());
          }
        }
      }
  
          ap.alignFrame.addHistoryItem(caseCommand);
  
-         ap.av.firePropertyChange("alignment", null, ap.av.getAlignment()
-                 .getSequences());
+         ap.av.firePropertyChange("alignment", null,
+                 ap.av.getAlignment().getSequences());
  
        }
      }
          if (start <= end)
          {
            seqs.add(sg.getSequenceAt(i));
 -          features.add(new SequenceFeature(null, null, null, start, end,
 +          features.add(new SequenceFeature(null, null, start, end,
                    "Jalview"));
          }
        }
          {
            ap.alignFrame.sequenceFeatures.setState(true);
            ap.av.setShowSequenceFeatures(true);
 -          ap.highlightSearchResults(null);
 +          ap.av.setSearchResults(null); // clear highlighting
 +          ap.repaint(); // draw new/amended features
          }
        }
      }
  
      Frame frame = new Frame();
      frame.add(cap);
-     JalviewLite.addFrame(frame, MessageManager.formatMessage(
-             "label.selection_output_command",
-             new Object[] { e.getActionCommand() }), 600, 500);
+     JalviewLite.addFrame(frame, MessageManager
+             .formatMessage("label.selection_output_command", new Object[]
+             { e.getActionCommand() }), 600, 500);
      // JBPNote: getSelectionAsNewSequence behaviour has changed - this method
      // now returns a full copy of sequence data
      // TODO consider using getSequenceSelection instead here
  
-     FileFormatI fileFormat = FileFormats.getInstance().forName(
-             e.getActionCommand());
+     FileFormatI fileFormat = FileFormats.getInstance()
+             .forName(e.getActionCommand());
      cap.setText(new AppletFormatAdapter().formatSequences(fileFormat,
              ap.av.getShowJVSuffix(), ap, true));
  
      StringBuilder contents = new StringBuilder(128);
      for (SequenceI seq : sequences)
      {
-       contents.append(MessageManager.formatMessage(
-               "label.annotation_for_displayid",
-               new Object[] { seq.getDisplayId(true) }));
+       contents.append(MessageManager
+               .formatMessage("label.annotation_for_displayid", new Object[]
+               { seq.getDisplayId(true) }));
        new SequenceAnnotationReport(null).createSequenceAnnotationReport(
-               contents,
-               seq,
-               true,
-               true,
-               (ap.seqPanel.seqCanvas.fr != null) ? ap.seqPanel.seqCanvas.fr
-                       .getMinMax() : null);
+               contents, seq, true, true,
+               (ap.seqPanel.seqCanvas.fr != null)
+                       ? ap.seqPanel.seqCanvas.fr.getMinMax()
+                       : null);
        contents.append("</p>");
      }
      Frame frame = new Frame();
      frame.add(cap);
-     jalview.bin.JalviewLite.addFrame(frame, "Sequence Details for "
-             + (sequences.length == 1 ? sequences[0].getDisplayId(true)
-                     : "Selection"), 600, 500);
-     cap.setText(MessageManager.formatMessage("label.html_content",
-             new Object[] { contents.toString() }));
+     jalview.bin.JalviewLite.addFrame(frame,
+             "Sequence Details for " + (sequences.length == 1
+                     ? sequences[0].getDisplayId(true)
+                     : "Selection"),
+             600, 500);
+     cap.setText(
+             MessageManager.formatMessage("label.html_content", new Object[]
+             { contents.toString() }));
    }
  
    void editName()
        }
        else
        {
-         new MCview.AppletPDBViewer(entry, new SequenceI[] { seq }, null,
-                 ap, DataSourceType.URL);
+         new MCview.AppletPDBViewer(entry, new SequenceI[] { seq }, null, ap,
+                 DataSourceType.URL);
        }
  
      }
      else
      {
-       CutAndPasteTransfer cap = new CutAndPasteTransfer(true, ap.alignFrame);
+       CutAndPasteTransfer cap = new CutAndPasteTransfer(true,
+               ap.alignFrame);
        cap.setText(MessageManager.getString("label.paste_pdb_file"));
        cap.setPDBImport(seq);
        Frame frame = new Frame();
        frame.add(cap);
        JalviewLite.addFrame(frame, MessageManager.formatMessage(
-               "label.paste_pdb_file_for_sequence",
-               new Object[] { seq.getName() }), 400, 300);
+               "label.paste_pdb_file_for_sequence", new Object[]
+               { seq.getName() }), 400, 300);
      }
    }
  
      sequenceFeature.addActionListener(this);
  
      editGroupName.addActionListener(this);
-     unGroupMenuItem.setLabel(MessageManager
-             .getString("action.remove_group"));
+     unGroupMenuItem
+             .setLabel(MessageManager.getString("action.remove_group"));
      unGroupMenuItem.addActionListener(this);
  
-     createGroupMenuItem.setLabel(MessageManager
-             .getString("action.create_group"));
+     createGroupMenuItem
+             .setLabel(MessageManager.getString("action.create_group"));
      createGroupMenuItem.addActionListener(this);
  
      modifyPID.setEnabled(abovePIDColour.getState());
      sequenceName.addActionListener(this);
      sequenceDetails.addActionListener(this);
      selSeqDetails.addActionListener(this);
-     displayNonconserved.setLabel(MessageManager
-             .getString("label.show_non_conserved"));
+     displayNonconserved
+             .setLabel(MessageManager.getString("label.show_non_conserved"));
      displayNonconserved.setState(false);
      displayNonconserved.addItemListener(this);
      showText.setLabel(MessageManager.getString("action.text"));
      seqMenu.setLabel(MessageManager.getString("label.sequence"));
      pdb.setLabel(MessageManager.getString("label.view_pdb_structure"));
      hideSeqs.setLabel(MessageManager.getString("action.hide_sequences"));
-     repGroup.setLabel(MessageManager.formatMessage(
-             "label.represent_group_with", new Object[] { "" }));
+     repGroup.setLabel(MessageManager
+             .formatMessage("label.represent_group_with", new Object[]
+             { "" }));
      revealAll.setLabel(MessageManager.getString("action.reveal_all"));
      revealSeq.setLabel(MessageManager.getString("action.reveal_sequences"));
      menu1.setLabel(MessageManager.getString("label.group:"));
      /*
       * setName allows setSelectedColour to do its thing
       */
-     clustalColour.setLabel(MessageManager
-             .getString("label.colourScheme_clustal"));
+     clustalColour.setLabel(
+             MessageManager.getString("label.colourScheme_clustal"));
      clustalColour.setName(JalviewColourScheme.Clustal.toString());
      clustalColour.addItemListener(this);
-     BLOSUM62Colour.setLabel(MessageManager
-             .getString("label.colourScheme_blosum62"));
+     BLOSUM62Colour.setLabel(
+             MessageManager.getString("label.colourScheme_blosum62"));
      BLOSUM62Colour.setName(JalviewColourScheme.Blosum62.toString());
      BLOSUM62Colour.addItemListener(this);
-     PIDColour.setLabel(MessageManager
-             .getString("label.colourScheme_%_identity"));
+     PIDColour.setLabel(
+             MessageManager.getString("label.colourScheme_%_identity"));
      PIDColour.setName(JalviewColourScheme.PID.toString());
      PIDColour.addItemListener(this);
-     zappoColour.setLabel(MessageManager
-             .getString("label.colourScheme_zappo"));
+     zappoColour
+             .setLabel(MessageManager.getString("label.colourScheme_zappo"));
      zappoColour.setName(JalviewColourScheme.Zappo.toString());
      zappoColour.addItemListener(this);
-     taylorColour.setLabel(MessageManager
-             .getString("label.colourScheme_taylor"));
+     taylorColour.setLabel(
+             MessageManager.getString("label.colourScheme_taylor"));
      taylorColour.setName(JalviewColourScheme.Taylor.toString());
      taylorColour.addItemListener(this);
-     hydrophobicityColour.setLabel(MessageManager
-             .getString("label.colourScheme_hydrophobic"));
+     hydrophobicityColour.setLabel(
+             MessageManager.getString("label.colourScheme_hydrophobic"));
      hydrophobicityColour
              .setName(JalviewColourScheme.Hydrophobic.toString());
      hydrophobicityColour.addItemListener(this);
              .getString("label.colourScheme_strand_propensity"));
      strandColour.setName(JalviewColourScheme.Strand.toString());
      strandColour.addItemListener(this);
-     turnColour.setLabel(MessageManager
-             .getString("label.colourScheme_turn_propensity"));
+     turnColour.setLabel(
+             MessageManager.getString("label.colourScheme_turn_propensity"));
      turnColour.setName(JalviewColourScheme.Turn.toString());
      turnColour.addItemListener(this);
-     buriedColour.setLabel(MessageManager
-             .getString("label.colourScheme_buried_index"));
+     buriedColour.setLabel(
+             MessageManager.getString("label.colourScheme_buried_index"));
      buriedColour.setName(JalviewColourScheme.Buried.toString());
      buriedColour.addItemListener(this);
-     nucleotideColour.setLabel(MessageManager
-             .getString("label.colourScheme_nucleotide"));
+     nucleotideColour.setLabel(
+             MessageManager.getString("label.colourScheme_nucleotide"));
      nucleotideColour.setName(JalviewColourScheme.Nucleotide.toString());
      nucleotideColour.addItemListener(this);
      purinePyrimidineColour.setLabel(MessageManager
              .getString("label.colourScheme_purine/pyrimidine"));
-     purinePyrimidineColour.setName(JalviewColourScheme.PurinePyrimidine
-             .toString());
+     purinePyrimidineColour
+             .setName(JalviewColourScheme.PurinePyrimidine.toString());
      purinePyrimidineColour.addItemListener(this);
  
-     userDefinedColour.setLabel(MessageManager
-             .getString("action.user_defined"));
+     userDefinedColour
+             .setLabel(MessageManager.getString("action.user_defined"));
      userDefinedColour.addActionListener(this);
  
-     abovePIDColour.setLabel(MessageManager
-             .getString("label.above_identity_threshold"));
+     abovePIDColour.setLabel(
+             MessageManager.getString("label.above_identity_threshold"));
      abovePIDColour.addItemListener(this);
-     modifyPID.setLabel(MessageManager
-             .getString("label.modify_identity_threshold"));
+     modifyPID.setLabel(
+             MessageManager.getString("label.modify_identity_threshold"));
      modifyPID.addActionListener(this);
-     conservationColour.setLabel(MessageManager
-             .getString("action.by_conservation"));
+     conservationColour
+             .setLabel(MessageManager.getString("action.by_conservation"));
      conservationColour.addItemListener(this);
      modifyConservation.setLabel(MessageManager
              .getString("label.modify_conservation_threshold"));
    protected void clustalColour_actionPerformed()
    {
      SequenceGroup sg = getGroup();
-     sg.cs = new ResidueShader(new ClustalxColourScheme(sg,
-             ap.av.getHiddenRepSequences()));
+     sg.cs = new ResidueShader(
+             new ClustalxColourScheme(sg, ap.av.getHiddenRepSequences()));
      refresh();
    }
  
  
    public void purinePyrimidineColour_actionPerformed()
    {
-     getGroup().cs = new ResidueShader(
-             new PurinePyrimidineColourScheme());
+     getGroup().cs = new ResidueShader(new PurinePyrimidineColourScheme());
      refresh();
    }
  
  
      if (abovePIDColour.getState())
      {
-       sg.cs.setConsensus(AAFrequency.calculate(sg.getSequences(ap.av
-               .getHiddenRepSequences()), 0, ap.av.getAlignment().getWidth()));
-       int threshold = SliderPanel.setPIDSliderSource(ap, sg.cs, getGroup()
-               .getName());
+       sg.cs.setConsensus(AAFrequency.calculate(
+               sg.getSequences(ap.av.getHiddenRepSequences()), 0,
+               ap.av.getAlignment().getWidth()));
+       int threshold = SliderPanel.setPIDSliderSource(ap, sg.cs,
+               getGroup().getName());
  
        sg.cs.setThreshold(threshold, ap.av.isIgnoreGapsConsensus());
  
    {
      SequenceGroup sg = getGroup();
      sg.cs = new ResidueShader(new PIDColourScheme());
-     sg.cs.setConsensus(AAFrequency.calculate(sg.getSequences(ap.av
-             .getHiddenRepSequences()), 0, ap.av.getAlignment().getWidth()));
+     sg.cs.setConsensus(AAFrequency.calculate(
+             sg.getSequences(ap.av.getHiddenRepSequences()), 0,
+             ap.av.getAlignment().getWidth()));
      refresh();
    }
  
  
      sg.cs = new ResidueShader(new Blosum62ColourScheme());
  
-     sg.cs.setConsensus(AAFrequency.calculate(sg.getSequences(ap.av
-             .getHiddenRepSequences()), 0, ap.av.getAlignment().getWidth()));
+     sg.cs.setConsensus(AAFrequency.calculate(
+             sg.getSequences(ap.av.getHiddenRepSequences()), 0,
+             ap.av.getAlignment().getWidth()));
  
      refresh();
    }
      if (conservationColour.getState())
      {
        Conservation conservation = Conservation.calculateConservation(
-               "Group", sg
-               .getSequences(ap.av.getHiddenRepSequences()), 0, ap.av
-               .getAlignment().getWidth(), false, ap.av.getConsPercGaps(),
-               false);
+               "Group", sg.getSequences(ap.av.getHiddenRepSequences()), 0,
+               ap.av.getAlignment().getWidth(), false,
+               ap.av.getConsPercGaps(), false);
        sg.getGroupColourScheme().setConservation(conservation);
        SliderPanel.setConservationSlider(ap, sg.cs, sg.getName());
        SliderPanel.showConservationSlider();
      showMenu.removeAll();
      hideMenu.removeAll();
  
-     final List<String> all = Arrays.asList(new String[] { MessageManager
-             .getString("label.all") });
-     addAnnotationTypeToShowHide(showMenu, forSequences, "", all, true, true);
+     final List<String> all = Arrays
+             .asList(new String[]
+             { MessageManager.getString("label.all") });
+     addAnnotationTypeToShowHide(showMenu, forSequences, "", all, true,
+             true);
      addAnnotationTypeToShowHide(hideMenu, forSequences, "", all, true,
              false);
      showMenu.addSeparator();
@@@ -173,15 -173,15 +173,15 @@@ public class AlignFrame extends Embmenu
    }
  
    public AlignFrame(AlignmentI al, SequenceI[] hiddenSeqs,
-           HiddenColumns hidden, JalviewLite applet,
-           String title, boolean embedded)
+           HiddenColumns hidden, JalviewLite applet, String title,
+           boolean embedded)
    {
      this(al, hiddenSeqs, hidden, applet, title, embedded, true);
    }
  
    public AlignFrame(AlignmentI al, SequenceI[] hiddenSeqs,
-           HiddenColumns hidden, JalviewLite applet,
-           String title, boolean embedded, boolean addToDisplay)
+           HiddenColumns hidden, JalviewLite applet, String title,
+           boolean embedded, boolean addToDisplay)
    {
      if (applet != null)
      {
      {
        Map<String, FeatureColourI> colours = alignPanel.seqPanel.seqCanvas
                .getFeatureRenderer().getFeatureColours();
-       boolean relaxedIdMatching = viewport.applet.getDefaultParameter(
-               "relaxedidmatch", false);
+       boolean relaxedIdMatching = viewport.applet
+               .getDefaultParameter("relaxedidmatch", false);
        featuresFile = new FeaturesFile(file, sourceType).parse(
                viewport.getAlignment(), colours, true, relaxedIdMatching);
      } catch (Exception ex)
      ViewportRanges ranges = viewport.getRanges();
  
      if (viewport.cursorMode
-             && ((evt.getKeyCode() >= KeyEvent.VK_0 && evt.getKeyCode() <= KeyEvent.VK_9) || (evt
-                     .getKeyCode() >= KeyEvent.VK_NUMPAD0 && evt
-                     .getKeyCode() <= KeyEvent.VK_NUMPAD9))
+             && ((evt.getKeyCode() >= KeyEvent.VK_0
+                     && evt.getKeyCode() <= KeyEvent.VK_9)
+                     || (evt.getKeyCode() >= KeyEvent.VK_NUMPAD0
+                             && evt.getKeyCode() <= KeyEvent.VK_NUMPAD9))
              && Character.isDigit(evt.getKeyChar()))
      {
        alignPanel.seqPanel.numberPressed(evt.getKeyChar());
  
      case KeyEvent.VK_F2:
        viewport.cursorMode = !viewport.cursorMode;
-       statusBar.setText(MessageManager.formatMessage(
-               "label.keyboard_editing_mode",
-               new String[] { (viewport.cursorMode ? "on" : "off") }));
+       statusBar.setText(MessageManager
+               .formatMessage("label.keyboard_editing_mode", new String[]
+               { (viewport.cursorMode ? "on" : "off") }));
        if (viewport.cursorMode)
        {
          alignPanel.seqPanel.seqCanvas.cursorX = ranges.getStartRes();
        // Hide everything by the current selection - this is a hack - we do the
        // invert and then hide
        // first check that there will be visible columns after the invert.
-       if (viewport.hasSelectedColumns()
-               || (sg != null && sg.getSize() > 0 && sg.getStartRes() <= sg
-                       .getEndRes()))
+       if (viewport.hasSelectedColumns() || (sg != null && sg.getSize() > 0
+               && sg.getStartRes() <= sg.getEndRes()))
        {
          // now invert the sequence set, if required - empty selection implies
          // that no hiding is required.
      {
        boolean newState = sortAnnBySequence.getState();
        sortAnnByLabel.setState(false);
-       setAnnotationSortOrder(newState ? SequenceAnnotationOrder.SEQUENCE_AND_LABEL
-               : SequenceAnnotationOrder.NONE);
+       setAnnotationSortOrder(
+               newState ? SequenceAnnotationOrder.SEQUENCE_AND_LABEL
+                       : SequenceAnnotationOrder.NONE);
        setViewportAnnotationOrder();
      }
      else if (source == sortAnnByLabel)
      {
        boolean newState = sortAnnByLabel.getState();
        sortAnnBySequence.setState(false);
-       setAnnotationSortOrder(newState ? SequenceAnnotationOrder.LABEL_AND_SEQUENCE
-               : SequenceAnnotationOrder.NONE);
+       setAnnotationSortOrder(
+               newState ? SequenceAnnotationOrder.LABEL_AND_SEQUENCE
+                       : SequenceAnnotationOrder.NONE);
        setViewportAnnotationOrder();
      }
      else if (source == showAutoFirst)
        cap.setText(contents.toString());
        Frame frame = new Frame();
        frame.add(cap);
-       jalview.bin.JalviewLite.addFrame(frame, MessageManager.formatMessage(
-               "label.alignment_properties", new String[] { getTitle() }),
-               400, 250);
+       jalview.bin.JalviewLite.addFrame(frame, MessageManager
+               .formatMessage("label.alignment_properties", new String[]
+               { getTitle() }), 400, 250);
      }
      else if (source == overviewMenuItem)
      {
      CutAndPasteTransfer cap = new CutAndPasteTransfer(true, this);
      Frame frame = new Frame();
      frame.add(cap);
-     JalviewLite.addFrame(frame, MessageManager.formatMessage(
-             "label.alignment_output_command",
-             new Object[] { e.getActionCommand() }), 600, 500);
-     FileFormatI fileFormat = FileFormats.getInstance().forName(
-             e.getActionCommand());
-     cap.setText(new AppletFormatAdapter(alignPanel).formatSequences(
-             fileFormat, viewport.getAlignment(),
-             viewport.getShowJVSuffix()));
+     JalviewLite.addFrame(frame, MessageManager
+             .formatMessage("label.alignment_output_command", new Object[]
+             { e.getActionCommand() }), 600, 500);
+     FileFormatI fileFormat = FileFormats.getInstance()
+             .forName(e.getActionCommand());
+     cap.setText(
+             new AppletFormatAdapter(alignPanel).formatSequences(fileFormat,
+                     viewport.getAlignment(), viewport.getShowJVSuffix()));
    }
  
    public void loadAnnotations()
      return null;
    }
  
 +  private List<String> getDisplayedFeatureGroups()
 +  {
 +    if (alignPanel.getFeatureRenderer() != null
 +            && viewport.getFeaturesDisplayed() != null)
 +    {
 +      return alignPanel.getFeatureRenderer().getDisplayedFeatureGroups();
 +
 +    }
 +    return null;
 +  }
 +
    public String outputFeatures(boolean displayTextbox, String format)
    {
      String features;
      FeaturesFile formatter = new FeaturesFile();
      if (format.equalsIgnoreCase("Jalview"))
      {
 -      features = formatter.printJalviewFormat(
 -              viewport.getAlignment().getSequencesArray(),
 -              getDisplayedFeatureCols());
 +      features = formatter.printJalviewFormat(viewport.getAlignment()
 +              .getSequencesArray(), getDisplayedFeatureCols(),
 +              getDisplayedFeatureGroups(), true);
      }
      else
      {
 -      features = formatter.printGffFormat(
 -              viewport.getAlignment().getSequencesArray(),
 -              getDisplayedFeatureCols());
 +      features = formatter.printGffFormat(viewport.getAlignment()
 +              .getSequencesArray(), getDisplayedFeatureCols(),
 +              getDisplayedFeatureGroups(), true);
      }
  
      if (displayTextbox)
              : "?";
      url.append(firstSep);
  
-     url.append("open="
-             + appendProtocol(viewport.applet.getParameter("file")));
+     url.append(
+             "open=" + appendProtocol(viewport.applet.getParameter("file")));
  
      if (viewport.applet.getParameter("features") != null)
      {
      if (viewport.applet.getParameter("annotations") != null)
      {
        url.append("&annotations=");
-       url.append(appendProtocol(viewport.applet.getParameter("annotations")));
+       url.append(
+               appendProtocol(viewport.applet.getParameter("annotations")));
      }
  
      if (viewport.applet.getParameter("jnetfile") != null
              || viewport.applet.getParameter("jpredfile") != null)
      {
        url.append("&annotations=");
-       url.append(appendProtocol(viewport.applet.getParameter("jnetfile") != null ? viewport.applet
-               .getParameter("jnetfile") : viewport.applet
-               .getParameter("jpredfile")));
+       url.append(appendProtocol(
+               viewport.applet.getParameter("jnetfile") != null
+                       ? viewport.applet.getParameter("jnetfile")
+                       : viewport.applet.getParameter("jpredfile")));
      }
  
      if (viewport.applet.getParameter("defaultColour") != null)
      {
-       url.append("&colour="
-               + removeWhiteSpace(viewport.applet
-                       .getParameter("defaultColour")));
+       url.append("&colour=" + removeWhiteSpace(
+               viewport.applet.getParameter("defaultColour")));
      }
  
      if (viewport.applet.getParameter("userDefinedColour") != null)
      {
-       url.append("&colour="
-               + removeWhiteSpace(viewport.applet
-                       .getParameter("userDefinedColour")));
+       url.append("&colour=" + removeWhiteSpace(
+               viewport.applet.getParameter("userDefinedColour")));
      }
      if (viewport.applet.getParameter("tree") != null)
      {
      {
        undoMenuItem.setEnabled(true);
        CommandI command = viewport.getHistoryList().peek();
-       undoMenuItem.setLabel(MessageManager.formatMessage(
-               "label.undo_command",
-               new Object[] { command.getDescription() }));
+       undoMenuItem.setLabel(MessageManager
+               .formatMessage("label.undo_command", new Object[]
+               { command.getDescription() }));
      }
      else
      {
        redoMenuItem.setEnabled(true);
  
        CommandI command = viewport.getRedoList().peek();
-       redoMenuItem.setLabel(MessageManager.formatMessage(
-               "label.redo_command",
-               new Object[] { command.getDescription() }));
+       redoMenuItem.setLabel(MessageManager
+               .formatMessage("label.redo_command", new Object[]
+               { command.getDescription() }));
      }
      else
      {
                                            // viewport.getColumnSelection().getHiddenColumns()
                                            // != null;
      updateEditMenuBar();
-     originalSource.firePropertyChange("alignment", null, originalSource
-             .getAlignment().getSequences());
+     originalSource.firePropertyChange("alignment", null,
+             originalSource.getAlignment().getSequences());
    }
  
    /**
                                            // != null;
  
      updateEditMenuBar();
-     originalSource.firePropertyChange("alignment", null, originalSource
-             .getAlignment().getSequences());
+     originalSource.firePropertyChange("alignment", null,
+             originalSource.getAlignment().getSequences());
    }
  
    AlignmentViewport getOriginatingSource(CommandI command)
      {
        EditCommand editCommand = (EditCommand) command;
        al = editCommand.getAlignment();
-       Vector comps = (Vector) PaintRefresher.components.get(viewport
-               .getSequenceSetId());
 -      Vector comps = (Vector) PaintRefresher.components
++      Vector comps = PaintRefresher.components
+               .get(viewport.getSequenceSetId());
        for (int i = 0; i < comps.size(); i++)
        {
          if (comps.elementAt(i) instanceof AlignmentPanel)
      List<SequenceI> sg = new Vector<>();
      if (viewport.cursorMode)
      {
-       sg.add(viewport.getAlignment().getSequenceAt(
-               alignPanel.seqPanel.seqCanvas.cursorY));
+       sg.add(viewport.getAlignment()
+               .getSequenceAt(alignPanel.seqPanel.seqCanvas.cursorY));
      }
      else if (viewport.getSelectionGroup() != null
              && viewport.getSelectionGroup().getSize() != viewport
                      .getAlignment().getHeight())
      {
-       sg = viewport.getSelectionGroup().getSequences(
-               viewport.getHiddenRepSequences());
+       sg = viewport.getSelectionGroup()
+               .getSequences(viewport.getHiddenRepSequences());
      }
  
      if (sg.size() < 1)
  
      SequenceI[] seqs1 = sg.toArray(new SequenceI[sg.size()]);
  
-     SequenceI[] seqs2 = invertGroup.toArray(new SequenceI[invertGroup
-             .size()]);
+     SequenceI[] seqs2 = invertGroup
+             .toArray(new SequenceI[invertGroup.size()]);
      for (int i = 0; i < invertGroup.size(); i++)
      {
        seqs2[i] = invertGroup.elementAt(i);
      SlideSequencesCommand ssc;
      if (right)
      {
-       ssc = new SlideSequencesCommand("Slide Sequences", seqs2, seqs1,
-               size, viewport.getGapCharacter());
+       ssc = new SlideSequencesCommand("Slide Sequences", seqs2, seqs1, size,
+               viewport.getGapCharacter());
      }
      else
      {
-       ssc = new SlideSequencesCommand("Slide Sequences", seqs1, seqs2,
-               size, viewport.getGapCharacter());
+       ssc = new SlideSequencesCommand("Slide Sequences", seqs1, seqs2, size,
+               viewport.getGapCharacter());
      }
  
      int groupAdjustment = 0;
      if (historyList != null && historyList.size() > 0
              && historyList.peek() instanceof SlideSequencesCommand)
      {
-       appendHistoryItem = ssc
-               .appendSlideCommand((SlideSequencesCommand) historyList
-                       .peek());
+       appendHistoryItem = ssc.appendSlideCommand(
+               (SlideSequencesCommand) historyList.peek());
      }
  
      if (!appendHistoryItem)
          endRes += seq.getStart() - 1;
        }
  
-       copiedSequences.append(seq.getName()
-               + "\t"
-               + startRes
-               + "\t"
-               + endRes
-               + "\t"
-               + seq.getSequenceAsString(sg.getStartRes(),
-                       sg.getEndRes() + 1) + "\n");
+       copiedSequences.append(seq.getName() + "\t" + startRes + "\t" + endRes
+               + "\t" + seq.getSequenceAsString(sg.getStartRes(),
+                       sg.getEndRes() + 1)
+               + "\n");
      }
  
    }
          }
          else
          {
-           newtitle = newtitle.concat(MessageManager.formatMessage(
-                   "label.from_msname", new String[] { getTitle() }));
+           newtitle = newtitle.concat(MessageManager
+                   .formatMessage("label.from_msname", new String[]
+                   { getTitle() }));
          }
          AlignFrame af = new AlignFrame(new Alignment(newSeqs),
                  viewport.applet, newtitle, false);
  
      viewport.getRanges().setEndSeq(viewport.getAlignment().getHeight());
      viewport.getAlignment().getWidth();
-     viewport.firePropertyChange("alignment", null, viewport.getAlignment()
-             .getSequences());
+     viewport.firePropertyChange("alignment", null,
+             viewport.getAlignment().getSequences());
  
    }
  
       */
      if (sg.getSize() == viewport.getAlignment().getHeight())
      {
-       boolean isEntireAlignWidth = (((sg.getEndRes() - sg.getStartRes()) + 1) == viewport
-               .getAlignment().getWidth()) ? true : false;
+       boolean isEntireAlignWidth = (((sg.getEndRes() - sg.getStartRes())
+               + 1) == viewport.getAlignment().getWidth()) ? true : false;
        if (isEntireAlignWidth)
        {
          String title = MessageManager.getString("label.delete_all");
          Panel infoPanel = new Panel();
          infoPanel.setLayout(new FlowLayout());
-         infoPanel
-                 .add(new Label(MessageManager.getString("warn.delete_all")));
+         infoPanel.add(
+                 new Label(MessageManager.getString("warn.delete_all")));
  
          final JVDialog dialog = new JVDialog(this, title, true, 400, 200);
          dialog.setMainPanel(infoPanel);
      viewport.setSelectionGroup(null);
      viewport.getAlignment().deleteGroup(sg);
  
-     viewport.firePropertyChange("alignment", null, viewport.getAlignment()
-             .getSequences());
+     viewport.firePropertyChange("alignment", null,
+             viewport.getAlignment().getSequences());
  
      if (viewport.getAlignment().getHeight() < 1)
      {
        SequenceI[] seqs;
        if (viewport.getSelectionGroup() != null)
        {
-         seqs = viewport.getSelectionGroup().getSequencesAsArray(
-                 viewport.getHiddenRepSequences());
+         seqs = viewport.getSelectionGroup()
+                 .getSequencesAsArray(viewport.getHiddenRepSequences());
        }
        else
        {
                  column, al);
        }
  
-       statusBar.setText(MessageManager.formatMessage(
-               "label.removed_columns",
-               new String[] { Integer.valueOf(trimRegion.getSize())
-                       .toString() }));
+       statusBar.setText(MessageManager
+               .formatMessage("label.removed_columns", new String[]
+               { Integer.valueOf(trimRegion.getSize()).toString() }));
        addHistoryItem(trimRegion);
  
        for (SequenceGroup sg : al.getGroups())
      SequenceI[] seqs;
      if (viewport.getSelectionGroup() != null)
      {
-       seqs = viewport.getSelectionGroup().getSequencesAsArray(
-               viewport.getHiddenRepSequences());
+       seqs = viewport.getSelectionGroup()
+               .getSequencesAsArray(viewport.getHiddenRepSequences());
        start = viewport.getSelectionGroup().getStartRes();
        end = viewport.getSelectionGroup().getEndRes();
      }
  
      addHistoryItem(removeGapCols);
  
-     statusBar.setText(MessageManager.formatMessage(
-             "label.removed_empty_columns",
-             new String[] { Integer.valueOf(removeGapCols.getSize())
-                     .toString() }));
+     statusBar.setText(MessageManager
+             .formatMessage("label.removed_empty_columns", new String[]
+             { Integer.valueOf(removeGapCols.getSize()).toString() }));
  
      // This is to maintain viewport position on first residue
      // of first sequence
      SequenceI[] seqs;
      if (viewport.getSelectionGroup() != null)
      {
-       seqs = viewport.getSelectionGroup().getSequencesAsArray(
-               viewport.getHiddenRepSequences());
+       seqs = viewport.getSelectionGroup()
+               .getSequencesAsArray(viewport.getHiddenRepSequences());
        start = viewport.getSelectionGroup().getStartRes();
        end = viewport.getSelectionGroup().getEndRes();
      }
      SequenceI seq = al.getSequenceAt(0);
      int startRes = seq.findPosition(ranges.getStartRes());
  
-     addHistoryItem(new RemoveGapsCommand("Remove Gaps", seqs, start, end,
-             al));
+     addHistoryItem(
+             new RemoveGapsCommand("Remove Gaps", seqs, start, end, al));
  
      ranges.setStartRes(seq.findIndex(startRes) - 1);
  
  
      if (viewport.getAlignment().getAlignmentAnnotation() != null)
      {
-       for (int i = 0; i < viewport.getAlignment().getAlignmentAnnotation().length; i++)
+       for (int i = 0; i < viewport.getAlignment()
+               .getAlignmentAnnotation().length; i++)
        {
-         if (!viewport.getAlignment().getAlignmentAnnotation()[i].autoCalculated)
+         if (!viewport.getAlignment()
+                 .getAlignmentAnnotation()[i].autoCalculated)
          {
-           newal.addAnnotation(viewport.getAlignment()
-                   .getAlignmentAnnotation()[i]);
+           newal.addAnnotation(
+                   viewport.getAlignment().getAlignmentAnnotation()[i]);
          }
        }
      }
      PaintRefresher.Register(newaf.alignPanel.seqPanel.seqCanvas,
              newaf.alignPanel.av.getSequenceSetId());
  
-     Vector comps = (Vector) PaintRefresher.components.get(viewport
-             .getSequenceSetId());
 -    Vector comps = (Vector) PaintRefresher.components
++    Vector comps = PaintRefresher.components
+             .get(viewport.getSequenceSetId());
      int viewSize = -1;
      for (int i = 0; i < comps.size(); i++)
      {
      final OverviewPanel overview = new OverviewPanel(alignPanel);
      frame.add(overview);
      // +50 must allow for applet frame window
-     jalview.bin.JalviewLite.addFrame(frame, MessageManager.formatMessage(
-             "label.overview_params", new String[] { this.getTitle() }),
-             overview.getPreferredSize().width,
+     jalview.bin.JalviewLite.addFrame(frame, MessageManager
+             .formatMessage("label.overview_params", new String[]
+             { this.getTitle() }), overview.getPreferredSize().width,
              overview.getPreferredSize().height + 50);
  
      frame.pack();
    public void sortPairwiseMenuItem_actionPerformed()
    {
      SequenceI[] oldOrder = viewport.getAlignment().getSequencesArray();
-     AlignmentSorter.sortByPID(viewport.getAlignment(), viewport
-             .getAlignment().getSequenceAt(0));
+     AlignmentSorter.sortByPID(viewport.getAlignment(),
+             viewport.getAlignment().getSequenceAt(0));
  
      addHistoryItem(new OrderCommand("Pairwise Sort", oldOrder,
              viewport.getAlignment()));
    {
      SequenceI[] oldOrder = viewport.getAlignment().getSequencesArray();
      AlignmentSorter.sortByID(viewport.getAlignment());
-     addHistoryItem(new OrderCommand("ID Sort", oldOrder,
-             viewport.getAlignment()));
+     addHistoryItem(
+             new OrderCommand("ID Sort", oldOrder, viewport.getAlignment()));
      alignPanel.paintAlignment(true);
    }
  
        SequenceI current;
        int Width = viewport.getAlignment().getWidth();
  
-       for (int i = 0; i < viewport.getAlignment().getSequences().size(); i++)
+       for (int i = 0; i < viewport.getAlignment().getSequences()
+               .size(); i++)
        {
          current = viewport.getAlignment().getSequenceAt(i);
  
      }
  
      if ((viewport.getSelectionGroup() != null
-             && viewport.getSelectionGroup().getSize() < 4 && viewport
-             .getSelectionGroup().getSize() > 0)
+             && viewport.getSelectionGroup().getSize() < 4
+             && viewport.getSelectionGroup().getSize() > 0)
              || viewport.getAlignment().getHeight() < 4)
      {
        return;
  
    protected void njTreeBlosumMenuItem_actionPerformed()
    {
-     newTreePanel(TreeBuilder.NEIGHBOUR_JOINING, ScoreModels.getInstance()
-             .getBlosum62().getName(),
+     newTreePanel(TreeBuilder.NEIGHBOUR_JOINING,
+             ScoreModels.getInstance().getBlosum62().getName(),
              "Neighbour joining tree using BLOSUM62");
    }
  
    protected void avTreeBlosumMenuItem_actionPerformed()
    {
-     newTreePanel(TreeBuilder.AVERAGE_DISTANCE, ScoreModels.getInstance()
-             .getBlosum62().getName(),
+     newTreePanel(TreeBuilder.AVERAGE_DISTANCE,
+             ScoreModels.getInstance().getBlosum62().getName(),
              "Average distance tree using BLOSUM62");
    }
  
        SequenceI current;
        int Width = viewport.getAlignment().getWidth();
  
-       for (int i = 0; i < viewport.getAlignment().getSequences().size(); i++)
+       for (int i = 0; i < viewport.getAlignment().getSequences()
+               .size(); i++)
        {
          current = viewport.getAlignment().getSequenceAt(i);
  
  
      }
  
-     if ((viewport.getSelectionGroup() != null && viewport
-             .getSelectionGroup().getSize() > 1)
+     if ((viewport.getSelectionGroup() != null
+             && viewport.getSelectionGroup().getSize() > 1)
              || (viewport.getAlignment().getHeight() > 1))
      {
        final TreePanel tp = new TreePanel(alignPanel, type, pwType);
    public void sortByTree(TreePanel treePanel, String title)
    {
      SequenceI[] oldOrder = viewport.getAlignment().getSequencesArray();
-     AlignmentSorter
-             .sortByTree(viewport.getAlignment(), treePanel.getTree());
+     AlignmentSorter.sortByTree(viewport.getAlignment(),
+             treePanel.getTree());
      // addHistoryItem(new HistoryItem("Sort", viewport.alignment,
      // HistoryItem.SORT));
-     addHistoryItem(new OrderCommand(MessageManager.formatMessage(
-             "label.order_by_params", new String[] { title }), oldOrder,
-             viewport.getAlignment()));
+     addHistoryItem(new OrderCommand(MessageManager
+             .formatMessage("label.order_by_params", new String[]
+             { title }), oldOrder, viewport.getAlignment()));
      alignPanel.paintAlignment(true);
    }
  
          // TODO: update this text for each release or centrally store it for
          // lite and application
          g.setFont(new Font("Helvetica", Font.BOLD, 14));
-         g.drawString(MessageManager.formatMessage(
-                 "label.jalviewLite_release", new String[] { version }), x,
-                 y += fh);
+         g.drawString(MessageManager
+                 .formatMessage("label.jalviewLite_release", new String[]
+                 { version }), x, y += fh);
          g.setFont(new Font("Helvetica", Font.BOLD, 12));
-         g.drawString(MessageManager.formatMessage(
-                 "label.jaview_build_date", new String[] { builddate }), x,
-                 y += fh);
+         g.drawString(MessageManager.formatMessage("label.jaview_build_date",
+                 new String[]
+                 { builddate }), x, y += fh);
          g.setFont(new Font("Helvetica", Font.PLAIN, 12));
-         g.drawString(MessageManager.getString("label.jalview_authors_1"),
-                 x, y += fh * 1.5);
+         g.drawString(MessageManager.getString("label.jalview_authors_1"), x,
+                 y += fh * 1.5);
          g.drawString(MessageManager.getString("label.jalview_authors_2"),
                  x + 50, y += fh + 8);
-         g.drawString(
-                 MessageManager.getString("label.jalview_dev_managers"), x,
-                 y += fh);
+         g.drawString(MessageManager.getString("label.jalview_dev_managers"),
+                 x, y += fh);
          g.drawString(MessageManager
                  .getString("label.jalview_distribution_lists"), x, y += fh);
          g.drawString(MessageManager.getString("label.jalview_please_cite"),
                  x, y += fh + 8);
          g.drawString(
-                 MessageManager.getString("label.jalview_cite_1_authors"),
-                 x, y += fh);
-         g.drawString(
-                 MessageManager.getString("label.jalview_cite_1_title"), x,
+                 MessageManager.getString("label.jalview_cite_1_authors"), x,
                  y += fh);
+         g.drawString(MessageManager.getString("label.jalview_cite_1_title"),
+                 x, y += fh);
          g.drawString(MessageManager.getString("label.jalview_cite_1_ref"),
                  x, y += fh);
        }
      }
  
      Frame frame = new Frame();
-     frame.add(new AboutPanel(JalviewLite.getVersion(), JalviewLite
-             .getBuildDate()));
+     frame.add(new AboutPanel(JalviewLite.getVersion(),
+             JalviewLite.getBuildDate()));
      jalview.bin.JalviewLite.addFrame(frame,
              MessageManager.getString("label.jalview"), 580, 220);
  
      pasteNew.addActionListener(this);
      pasteThis.setLabel(MessageManager.getString("label.to_this_alignment"));
      pasteThis.addActionListener(this);
-     remove2LeftMenuItem.setLabel(MessageManager
-             .getString("action.remove_left"));
+     remove2LeftMenuItem
+             .setLabel(MessageManager.getString("action.remove_left"));
      remove2LeftMenuItem.addActionListener(this);
-     remove2RightMenuItem.setLabel(MessageManager
-             .getString("action.remove_right"));
+     remove2RightMenuItem
+             .setLabel(MessageManager.getString("action.remove_right"));
      remove2RightMenuItem.addActionListener(this);
-     removeGappedColumnMenuItem.setLabel(MessageManager
-             .getString("action.remove_empty_columns"));
+     removeGappedColumnMenuItem.setLabel(
+             MessageManager.getString("action.remove_empty_columns"));
      removeGappedColumnMenuItem.addActionListener(this);
-     removeAllGapsMenuItem.setLabel(MessageManager
-             .getString("action.remove_all_gaps"));
+     removeAllGapsMenuItem
+             .setLabel(MessageManager.getString("action.remove_all_gaps"));
      removeAllGapsMenuItem.addActionListener(this);
-     removeRedundancyMenuItem.setLabel(MessageManager
-             .getString("action.remove_redundancy"));
+     removeRedundancyMenuItem
+             .setLabel(MessageManager.getString("action.remove_redundancy"));
      removeRedundancyMenuItem.addActionListener(this);
  
      /*
      findMenuItem.addActionListener(this);
      selectAllSequenceMenuItem.addActionListener(this);
      deselectAllSequenceMenuItem.addActionListener(this);
-     invertSequenceMenuItem.setLabel(MessageManager
-             .getString("action.invert_sequence_selection"));
+     invertSequenceMenuItem.setLabel(
+             MessageManager.getString("action.invert_sequence_selection"));
      invertSequenceMenuItem.addActionListener(this);
-     invertColSel.setLabel(MessageManager
-             .getString("action.invert_column_selection"));
+     invertColSel.setLabel(
+             MessageManager.getString("action.invert_column_selection"));
      invertColSel.addActionListener(this);
-     deleteGroups.setLabel(MessageManager
-             .getString("action.undefine_groups"));
+     deleteGroups
+             .setLabel(MessageManager.getString("action.undefine_groups"));
      deleteGroups.addActionListener(this);
-     grpsFromSelection.setLabel(MessageManager
-             .getString("action.make_groups_selection"));
+     grpsFromSelection.setLabel(
+             MessageManager.getString("action.make_groups_selection"));
      grpsFromSelection.addActionListener(this);
      createGroup.setLabel(MessageManager.getString("action.create_group"));
      createGroup.addActionListener(this);
      unGroup.setLabel(MessageManager.getString("action.remove_group"));
      unGroup.addActionListener(this);
  
-     annotationColumnSelection.setLabel(MessageManager
-             .getString("action.select_by_annotation"));
+     annotationColumnSelection.setLabel(
+             MessageManager.getString("action.select_by_annotation"));
      annotationColumnSelection.addActionListener(this);
  
      /*
      Menu hideMenu = new Menu(MessageManager.getString("action.hide"));
      hideColumns
              .setLabel(MessageManager.getString("label.selected_columns"));
-     hideSequences.setLabel(MessageManager
-             .getString("label.selected_sequences"));
-     hideAllButSelection.setLabel(MessageManager
-             .getString("label.all_but_selected_region"));
-     hideAllSelection.setLabel(MessageManager
-             .getString("label.selected_region"));
-     showAllHidden.setLabel(MessageManager
-             .getString("label.all_sequences_columns"));
+     hideSequences
+             .setLabel(MessageManager.getString("label.selected_sequences"));
+     hideAllButSelection.setLabel(
+             MessageManager.getString("label.all_but_selected_region"));
+     hideAllSelection
+             .setLabel(MessageManager.getString("label.selected_region"));
+     showAllHidden.setLabel(
+             MessageManager.getString("label.all_sequences_columns"));
      showColumns.addActionListener(this);
      showSeqs.addActionListener(this);
      hideColumns.addActionListener(this);
      hideAllButSelection.addActionListener(this);
      hideAllSelection.addActionListener(this);
      showAllHidden.addActionListener(this);
-     featureSettings.setLabel(MessageManager
-             .getString("action.feature_settings"));
+     featureSettings
+             .setLabel(MessageManager.getString("action.feature_settings"));
      featureSettings.addActionListener(this);
-     sequenceFeatures.setLabel(MessageManager
-             .getString("label.show_sequence_features"));
+     sequenceFeatures.setLabel(
+             MessageManager.getString("label.show_sequence_features"));
      sequenceFeatures.addItemListener(this);
      sequenceFeatures.setState(false);
-     followMouseOverFlag.setLabel(MessageManager
-             .getString("label.automatic_scrolling"));
+     followMouseOverFlag.setLabel(
+             MessageManager.getString("label.automatic_scrolling"));
      followMouseOverFlag.addItemListener(this);
      alProperties.addActionListener(this);
-     overviewMenuItem.setLabel(MessageManager
-             .getString("label.overview_window"));
+     overviewMenuItem
+             .setLabel(MessageManager.getString("label.overview_window"));
      overviewMenuItem.addActionListener(this);
  
      /*
       * Configure Annotations menu items and actions
       */
-     annotationPanelMenuItem.setLabel(MessageManager
-             .getString("label.show_annotations"));
+     annotationPanelMenuItem
+             .setLabel(MessageManager.getString("label.show_annotations"));
      annotationPanelMenuItem.addItemListener(this);
-     showGroupConsensus.setLabel(MessageManager
-             .getString("label.group_consensus"));
-     showGroupConservation.setLabel(MessageManager
-             .getString("label.group_conservation"));
-     showConsensusHistogram.setLabel(MessageManager
-             .getString("label.show_consensus_histogram"));
-     showSequenceLogo.setLabel(MessageManager
-             .getString("label.show_consensus_logo"));
-     normSequenceLogo.setLabel(MessageManager
-             .getString("label.norm_consensus_logo"));
-     applyAutoAnnotationSettings.setLabel(MessageManager
-             .getString("label.apply_all_groups"));
+     showGroupConsensus
+             .setLabel(MessageManager.getString("label.group_consensus"));
+     showGroupConservation
+             .setLabel(MessageManager.getString("label.group_conservation"));
+     showConsensusHistogram.setLabel(
+             MessageManager.getString("label.show_consensus_histogram"));
+     showSequenceLogo.setLabel(
+             MessageManager.getString("label.show_consensus_logo"));
+     normSequenceLogo.setLabel(
+             MessageManager.getString("label.norm_consensus_logo"));
+     applyAutoAnnotationSettings
+             .setLabel(MessageManager.getString("label.apply_all_groups"));
      applyAutoAnnotationSettings.setState(true);
      Menu autoAnnMenu = new Menu(
              MessageManager.getString("label.autocalculated_annotation"));
      viewTextMenuItem.setLabel(MessageManager.getString("action.text"));
      viewTextMenuItem.setState(true);
      viewTextMenuItem.addItemListener(this);
-     colourTextMenuItem.setLabel(MessageManager
-             .getString("label.colour_text"));
+     colourTextMenuItem
+             .setLabel(MessageManager.getString("label.colour_text"));
      colourTextMenuItem.addItemListener(this);
-     displayNonconservedMenuItem.setLabel(MessageManager
-             .getString("label.show_non_conserved"));
+     displayNonconservedMenuItem
+             .setLabel(MessageManager.getString("label.show_non_conserved"));
      displayNonconservedMenuItem.addItemListener(this);
      wrapMenuItem.setLabel(MessageManager.getString("action.wrap"));
      wrapMenuItem.addItemListener(this);
-     renderGapsMenuItem.setLabel(MessageManager
-             .getString("action.show_gaps"));
+     renderGapsMenuItem
+             .setLabel(MessageManager.getString("action.show_gaps"));
      renderGapsMenuItem.setState(true);
      renderGapsMenuItem.addItemListener(this);
-     centreColumnLabelFlag.setLabel(MessageManager
-             .getString("label.centre_column_labels"));
+     centreColumnLabelFlag.setLabel(
+             MessageManager.getString("label.centre_column_labels"));
      centreColumnLabelFlag.addItemListener(this);
      seqLimits.setState(true);
-     seqLimits.setLabel(MessageManager
-             .getString("label.show_sequence_limits"));
+     seqLimits.setLabel(
+             MessageManager.getString("label.show_sequence_limits"));
      seqLimits.addItemListener(this);
  
      /*
       * Configure Colour menu items and actions
       */
-     applyToAllGroups.setLabel(MessageManager
-             .getString("label.apply_colour_to_all_groups"));
+     applyToAllGroups.setLabel(
+             MessageManager.getString("label.apply_colour_to_all_groups"));
      applyToAllGroups.setState(true);
      applyToAllGroups.addItemListener(this);
-     clustalColour.setLabel(MessageManager
-             .getString("label.colourScheme_clustal"));
+     clustalColour.setLabel(
+             MessageManager.getString("label.colourScheme_clustal"));
      clustalColour.addActionListener(this);
-     zappoColour.setLabel(MessageManager
-             .getString("label.colourScheme_zappo"));
+     zappoColour
+             .setLabel(MessageManager.getString("label.colourScheme_zappo"));
      zappoColour.addActionListener(this);
-     taylorColour.setLabel(MessageManager
-             .getString("label.colourScheme_taylor"));
+     taylorColour.setLabel(
+             MessageManager.getString("label.colourScheme_taylor"));
      taylorColour.addActionListener(this);
-     hydrophobicityColour.setLabel(MessageManager
-             .getString("label.colourScheme_hydrophobic"));
+     hydrophobicityColour.setLabel(
+             MessageManager.getString("label.colourScheme_hydrophobic"));
      hydrophobicityColour.addActionListener(this);
      helixColour.setLabel(MessageManager
              .getString("label.colourScheme_helix_propensity"));
      strandColour.setLabel(MessageManager
              .getString("label.colourScheme_strand_propensity"));
      strandColour.addActionListener(this);
-     turnColour.setLabel(MessageManager
-             .getString("label.colourScheme_turn_propensity"));
+     turnColour.setLabel(
+             MessageManager.getString("label.colourScheme_turn_propensity"));
      turnColour.addActionListener(this);
-     buriedColour.setLabel(MessageManager
-             .getString("label.colourScheme_buried_index"));
+     buriedColour.setLabel(
+             MessageManager.getString("label.colourScheme_buried_index"));
      buriedColour.addActionListener(this);
      purinePyrimidineColour.setLabel(MessageManager
              .getString("label.colourScheme_purine/pyrimidine"));
      // RNAInteractionColour.setLabel(MessageManager
      // .getString("label.rna_interaction"));
      // RNAInteractionColour.addActionListener(this);
-     RNAHelixColour.setLabel(MessageManager
-             .getString("label.colourScheme_rna_helices"));
+     RNAHelixColour.setLabel(
+             MessageManager.getString("label.colourScheme_rna_helices"));
      RNAHelixColour.addActionListener(this);
-     userDefinedColour.setLabel(MessageManager
-             .getString("action.user_defined"));
+     userDefinedColour
+             .setLabel(MessageManager.getString("action.user_defined"));
      userDefinedColour.addActionListener(this);
-     PIDColour.setLabel(MessageManager
-             .getString("label.colourScheme_%_identity"));
+     PIDColour.setLabel(
+             MessageManager.getString("label.colourScheme_%_identity"));
      PIDColour.addActionListener(this);
-     BLOSUM62Colour.setLabel(MessageManager
-             .getString("label.colourScheme_blosum62"));
+     BLOSUM62Colour.setLabel(
+             MessageManager.getString("label.colourScheme_blosum62"));
      BLOSUM62Colour.addActionListener(this);
-     tcoffeeColour.setLabel(MessageManager
-             .getString("label.colourScheme_t-coffee_scores"));
+     tcoffeeColour.setLabel(
+             MessageManager.getString("label.colourScheme_t-coffee_scores"));
      // it will be enabled only if a score file is provided
      tcoffeeColour.setEnabled(false);
      tcoffeeColour.addActionListener(this);
-     conservationMenuItem.setLabel(MessageManager
-             .getString("action.by_conservation"));
+     conservationMenuItem
+             .setLabel(MessageManager.getString("action.by_conservation"));
      conservationMenuItem.addItemListener(this);
      noColourmenuItem.setLabel(MessageManager.getString("label.none"));
      noColourmenuItem.addActionListener(this);
-     abovePIDThreshold.setLabel(MessageManager
-             .getString("label.above_identity_threshold"));
+     abovePIDThreshold.setLabel(
+             MessageManager.getString("label.above_identity_threshold"));
      abovePIDThreshold.addItemListener(this);
-     nucleotideColour.setLabel(MessageManager
-             .getString("label.colourScheme_nucleotide"));
+     nucleotideColour.setLabel(
+             MessageManager.getString("label.colourScheme_nucleotide"));
      nucleotideColour.addActionListener(this);
-     modifyPID.setLabel(MessageManager
-             .getString("label.modify_identity_threshold"));
+     modifyPID.setLabel(
+             MessageManager.getString("label.modify_identity_threshold"));
      modifyPID.setEnabled(abovePIDThreshold.getState());
      modifyPID.addActionListener(this);
      modifyConservation.setLabel(MessageManager
              .getString("label.modify_conservation_threshold"));
      modifyConservation.setEnabled(conservationMenuItem.getState());
      modifyConservation.addActionListener(this);
-     annotationColour.setLabel(MessageManager
-             .getString("action.by_annotation"));
+     annotationColour
+             .setLabel(MessageManager.getString("action.by_annotation"));
      annotationColour.addActionListener(this);
  
      /*
       * Configure Calculate menu items and actions
       */
-     sortPairwiseMenuItem.setLabel(MessageManager
-             .getString("action.by_pairwise_id"));
+     sortPairwiseMenuItem
+             .setLabel(MessageManager.getString("action.by_pairwise_id"));
      sortPairwiseMenuItem.addActionListener(this);
      sortIDMenuItem.setLabel(MessageManager.getString("action.by_id"));
      sortIDMenuItem.addActionListener(this);
-     sortLengthMenuItem.setLabel(MessageManager
-             .getString("action.by_length"));
+     sortLengthMenuItem
+             .setLabel(MessageManager.getString("action.by_length"));
      sortLengthMenuItem.addActionListener(this);
      sortGroupMenuItem.setLabel(MessageManager.getString("action.by_group"));
      sortGroupMenuItem.addActionListener(this);
-     pairwiseAlignmentMenuItem.setLabel(MessageManager
-             .getString("action.pairwise_alignment"));
+     pairwiseAlignmentMenuItem.setLabel(
+             MessageManager.getString("action.pairwise_alignment"));
      pairwiseAlignmentMenuItem.addActionListener(this);
-     PCAMenuItem.setLabel(MessageManager
-             .getString("label.principal_component_analysis"));
+     PCAMenuItem.setLabel(
+             MessageManager.getString("label.principal_component_analysis"));
      PCAMenuItem.addActionListener(this);
      autoCalculate = new CheckboxMenuItem(
-             MessageManager.getString("label.autocalculate_consensus"), true);
-     averageDistanceTreeMenuItem.setLabel(MessageManager
-             .getString("label.average_distance_identity"));
+             MessageManager.getString("label.autocalculate_consensus"),
+             true);
+     averageDistanceTreeMenuItem.setLabel(
+             MessageManager.getString("label.average_distance_identity"));
      averageDistanceTreeMenuItem.addActionListener(this);
-     neighbourTreeMenuItem.setLabel(MessageManager
-             .getString("label.neighbour_joining_identity"));
+     neighbourTreeMenuItem.setLabel(
+             MessageManager.getString("label.neighbour_joining_identity"));
      neighbourTreeMenuItem.addActionListener(this);
-     avDistanceTreeBlosumMenuItem.setLabel(MessageManager
-             .getString("label.average_distance_blosum62"));
+     avDistanceTreeBlosumMenuItem.setLabel(
+             MessageManager.getString("label.average_distance_blosum62"));
      avDistanceTreeBlosumMenuItem.addActionListener(this);
-     njTreeBlosumMenuItem.setLabel(MessageManager
-             .getString("label.neighbour_blosum62"));
+     njTreeBlosumMenuItem
+             .setLabel(MessageManager.getString("label.neighbour_blosum62"));
      njTreeBlosumMenuItem.addActionListener(this);
-     sortByTreeMenu.setLabel(MessageManager
-             .getString("action.by_tree_order"));
+     sortByTreeMenu
+             .setLabel(MessageManager.getString("action.by_tree_order"));
      Menu sortMenu = new Menu(MessageManager.getString("action.sort"));
      Menu calculateTreeMenu = new Menu(
              MessageManager.getString("action.calculate_tree"));
      theApplet.add(embeddedMenu, BorderLayout.NORTH);
      theApplet.add(statusBar, BorderLayout.SOUTH);
      // TODO should size be left to the layout manager?
-     alignPanel.setSize(theApplet.getSize().width,
-             theApplet.getSize().height - embeddedMenu.getHeight()
-                     - statusBar.getHeight());
+     alignPanel.setSize(theApplet.getSize().width, theApplet.getSize().height
+             - embeddedMenu.getHeight() - statusBar.getHeight());
      theApplet.add(alignPanel, BorderLayout.CENTER);
      final AlignFrame me = this;
      theApplet.addFocusListener(new FocusListener()
        viewer = (Viewer) jmolviewer;
      } catch (ClassCastException ex)
      {
-       System.err.println("Unsupported viewer object :"
-               + jmolviewer.getClass());
+       System.err.println(
+               "Unsupported viewer object :" + jmolviewer.getClass());
      }
      if (viewer == null)
      {
        }
        // resolve data source
        // TODO: this code should be a refactored to an io package
-       DataSourceType protocol = AppletFormatAdapter.resolveProtocol(
-               pdbFile, FileFormat.PDB);
+       DataSourceType protocol = AppletFormatAdapter.resolveProtocol(pdbFile,
+               FileFormat.PDB);
        if (protocol == null)
        {
          return false;
        {
          if (seqs[i] != null)
          {
-           sequences.addElement(new Object[] { seqs[i],
-               (chains != null) ? chains[i] : null });
+           sequences
+                   .addElement(new Object[]
+                   { seqs[i], (chains != null) ? chains[i] : null });
          }
        }
        seqs = new SequenceI[sequences.size()];
      chains = (String[]) sqch[1];
      if (seqs == null || seqs.length == 0)
      {
-       System.err
-               .println("JalviewLite.AlignFrame:newStructureView: No sequence to bind structure to.");
+       System.err.println(
+               "JalviewLite.AlignFrame:newStructureView: No sequence to bind structure to.");
      }
      if (protocol == null)
      {
        }
        if (ajm != null)
        {
-         System.err
-                 .println("Incremental adding and aligning structure to existing Jmol view not yet implemented.");
+         System.err.println(
+                 "Incremental adding and aligning structure to existing Jmol view not yet implemented.");
          // try and add the pdb structure
          // ajm.addS
          ajm = null;
      // otherwise, create a new window
      if (applet.jmolAvailable)
      {
-       new AppletJmol(pdb, seqs, chains, alignPanel,
-               protocol);
+       new AppletJmol(pdb, seqs, chains, alignPanel, protocol);
        applet.lastFrameX += 40;
        applet.lastFrameY += 40;
      }
       */
      AlignmentI aln;
      if ((aln = viewport.getAlignment()) != null
-             && (aln.getHeight() != file.getHeight() || aln.getWidth() != file
-                     .getWidth()))
+             && (aln.getHeight() != file.getHeight()
+                     || aln.getWidth() != file.getWidth()))
      {
        // TODO: raise a dialog box here rather than bomb out.
-       System.err
-               .println("The scores matrix does not match the alignment dimensions");
+       System.err.println(
+               "The scores matrix does not match the alignment dimensions");
  
      }
  
@@@ -42,8 -42,6 +42,8 @@@ import java.awt.event.ActionEvent
  import java.awt.event.ActionListener;
  import java.awt.event.AdjustmentEvent;
  import java.awt.event.AdjustmentListener;
 +import java.awt.event.FocusAdapter;
 +import java.awt.event.FocusEvent;
  import java.awt.event.ItemEvent;
  import java.awt.event.ItemListener;
  import java.awt.event.MouseEvent;
@@@ -158,8 -156,8 +158,8 @@@ public class FeatureColourChooser exten
      } catch (Exception ex)
      {
      }
-     threshold.select(cs.isAboveThreshold() ? 1 : (cs.isBelowThreshold() ? 2
-             : 0));
+     threshold.select(
+             cs.isAboveThreshold() ? 1 : (cs.isBelowThreshold() ? 2 : 0));
  
      adjusting = false;
      changeColour(true);
      slider.addAdjustmentListener(this);
      slider.addMouseListener(this);
      owner = (af != null) ? af : fs.frame;
-     frame = new JVDialog(owner, MessageManager.formatMessage(
-             "label.graduated_color_for_params", new String[] { type }),
-             true, 480, 248);
+     frame = new JVDialog(owner, MessageManager
+             .formatMessage("label.graduated_color_for_params", new String[]
+             { type }), true, 480, 248);
      frame.setMainPanel(this);
      validate();
      frame.setVisible(true);
  
    private void jbInit() throws Exception
    {
-     Label minLabel = new Label(MessageManager.getString("label.min")), maxLabel = new Label(
-             MessageManager.getString("label.max"));
+     Label minLabel = new Label(MessageManager.getString("label.min")),
+             maxLabel = new Label(MessageManager.getString("label.max"));
      minLabel.setFont(new java.awt.Font("Verdana", Font.PLAIN, 11));
      maxLabel.setFont(new java.awt.Font("Verdana", Font.PLAIN, 11));
      // minColour.setFont(new java.awt.Font("Verdana", Font.PLAIN, 11));
      threshold.addItem(MessageManager
              .getString("label.threshold_feature_below_threshold"));
      thresholdValue.addActionListener(this);
 +    thresholdValue.addFocusListener(new FocusAdapter()
 +    {
 +      @Override
 +      public void focusLost(FocusEvent e)
 +      {
 +        thresholdValue_actionPerformed();
 +      }
 +    });
      slider.setBackground(Color.white);
      slider.setEnabled(false);
      slider.setSize(new Dimension(93, 21));
      jPanel3.setBackground(Color.white);
  
      colourFromLabel.setFont(new java.awt.Font("Verdana", Font.PLAIN, 11));
-     colourFromLabel.setLabel(MessageManager
-             .getString("label.colour_by_label"));
+     colourFromLabel
+             .setLabel(MessageManager.getString("label.colour_by_label"));
      colourFromLabel.setSize(new Dimension(139, 22));
      // threshold.setBounds(new Rectangle(11, 3, 139, 22));
      thresholdIsMin.setBackground(Color.white);
-     thresholdIsMin.setLabel(MessageManager
-             .getString("label.threshold_minmax"));
+     thresholdIsMin
+             .setLabel(MessageManager.getString("label.threshold_minmax"));
      thresholdIsMin.setSize(new Dimension(135, 23));
      // thresholdIsMin.setBounds(new Rectangle(328, 3, 135, 23));
      jPanel1.add(minLabel);
    {
      if (evt.getSource() == thresholdValue)
      {
 -      try
 -      {
 -        float f = new Float(thresholdValue.getText()).floatValue();
 -        slider.setValue((int) (f * SCALE_FACTOR_1K));
 -        adjustmentValueChanged(null);
 -
 -        /*
 -         * force repaint of any Overview window or structure
 -         */
 -        changeColour(true);
 -      } catch (NumberFormatException ex)
 -      {
 -      }
 +      thresholdValue_actionPerformed();
      }
      else if (evt.getSource() == minColour)
      {
      }
    }
  
 +  /**
 +   * Action on input of a value for colour score threshold
 +   */
 +  protected void thresholdValue_actionPerformed()
 +  {
 +    try
 +    {
 +      float f = new Float(thresholdValue.getText()).floatValue();
 +      slider.setValue((int) (f * SCALE_FACTOR_1K));
 +      adjustmentValueChanged(null);
 +
 +      /*
 +       * force repaint of any Overview window or structure
 +       */
 +      changeColour(true);
 +    } catch (NumberFormatException ex)
 +    {
 +    }
 +  }
 +
    @Override
    public void itemStateChanged(ItemEvent evt)
    {
    {
      if (newCol == null)
      {
-       new UserDefinedColours(this,
-               minColour.getBackground(), owner,
-               MessageManager.getString("label.select_colour_minimum_value"));
+       new UserDefinedColours(this, minColour.getBackground(), owner,
+               MessageManager
+                       .getString("label.select_colour_minimum_value"));
      }
      else
      {
    {
      if (newCol == null)
      {
-       new UserDefinedColours(this,
-               maxColour.getBackground(), owner,
-               MessageManager.getString("label.select_colour_maximum_value"));
+       new UserDefinedColours(this, maxColour.getBackground(), owner,
+               MessageManager
+                       .getString("label.select_colour_maximum_value"));
      }
      else
      {
        adjusting = false;
      }
  
-     acg.setAboveThreshold(thresholdOption == AnnotationColourGradient.ABOVE_THRESHOLD);
-     acg.setBelowThreshold(thresholdOption == AnnotationColourGradient.BELOW_THRESHOLD);
+     acg.setAboveThreshold(
+             thresholdOption == AnnotationColourGradient.ABOVE_THRESHOLD);
+     acg.setBelowThreshold(
+             thresholdOption == AnnotationColourGradient.BELOW_THRESHOLD);
  
      if (thresholdIsMin.getState()
              && thresholdOption != AnnotationColourGradient.NO_THRESHOLD)
@@@ -61,8 -61,8 +61,8 @@@ import java.util.List
   * @author $author$
   * @version $Revision$
   */
- public class FeatureRenderer extends
-         jalview.renderer.seqfeatures.FeatureRenderer
+ public class FeatureRenderer
+         extends jalview.renderer.seqfeatures.FeatureRenderer
  {
    /*
     * creating a new feature defaults to the type and group as
            FeatureColourI col = getFeatureStyle(name.getText());
            if (col == null)
            {
-             Color generatedColour = ColorUtils.createColourFromName(name
-                     .getText());
+             Color generatedColour = ColorUtils
+                     .createColourFromName(name.getText());
              col = new FeatureColour(generatedColour);
            }
  
  
      tmp = new Panel();
      panel.add(tmp);
-     tmp.add(new Label(MessageManager.getString("label.name:"), Label.RIGHT));
+     tmp.add(new Label(MessageManager.getString("label.name:"),
+             Label.RIGHT));
      tmp.add(name);
  
      tmp = new Panel();
      panel.add(tmp);
-     tmp.add(new Label(MessageManager.getString("label.group:"), Label.RIGHT));
+     tmp.add(new Label(MessageManager.getString("label.group:"),
+             Label.RIGHT));
      tmp.add(group);
  
      tmp = new Panel();
      panel.add(tmp);
-     tmp.add(new Label(MessageManager.getString("label.colour"), Label.RIGHT));
+     tmp.add(new Label(MessageManager.getString("label.colour"),
+             Label.RIGHT));
      tmp.add(colourPanel);
  
      bigPanel.add(panel, BorderLayout.NORTH);
       */
      SequenceFeature firstFeature = features.get(0);
      boolean useLastDefaults = firstFeature.getType() == null;
-     String featureType = useLastDefaults ? lastFeatureAdded : firstFeature
-             .getType();
+     String featureType = useLastDefaults ? lastFeatureAdded
+             : firstFeature.getType();
      String featureGroup = useLastDefaults ? lastFeatureGroupAdded
              : firstFeature.getFeatureGroup();
  
-     String title = create ? MessageManager
-             .getString("label.create_new_sequence_features")
+     String title = create
+             ? MessageManager.getString("label.create_new_sequence_features")
              : MessageManager.formatMessage("label.amend_delete_features",
-                     new String[] { sequences.get(0).getName() });
+                     new String[]
+                     { sequences.get(0).getName() });
  
      final JVDialog dialog = new JVDialog(ap.alignFrame, title, true, 385,
              240);
      /*
       * only update default type and group if we used defaults
       */
 -    String enteredType = name.getText().trim();
 +    final String enteredType = name.getText().trim();
 +    final String enteredGroup = group.getText().trim();
 +    final String enteredDesc = description.getText().replace('\n', ' ');
 +
      if (dialog.accept && useLastDefaults)
      {
        lastFeatureAdded = enteredType;
 -      lastFeatureGroupAdded = group.getText().trim();
 +      lastFeatureGroupAdded = enteredGroup;
      }
  
      if (!create)
        SequenceFeature sf = features.get(featureIndex);
        if (dialog.accept)
        {
          if (!colourPanel.isGcol)
          {
            // update colour - otherwise its already done.
 -          setColour(sf.type,
 +          setColour(enteredType,
                    new FeatureColour(colourPanel.getBackground()));
          }
 +        int newBegin = sf.begin;
 +        int newEnd = sf.end;
          try
          {
 -          sf.begin = Integer.parseInt(start.getText());
 -          sf.end = Integer.parseInt(end.getText());
 +          newBegin = Integer.parseInt(start.getText());
 +          newEnd = Integer.parseInt(end.getText());
          } catch (NumberFormatException ex)
          {
 -          //
 +          // 
          }
 -        boolean typeOrGroupChanged = (!featureType.equals(sf.type)
 -                || !featureGroup.equals(sf.featureGroup));
 +
 +        /*
 +         * replace the feature by deleting it and adding a new one
 +         * (to ensure integrity of SequenceFeatures data store)
 +         */
 +        sequences.get(0).deleteFeature(sf);
 +        SequenceFeature newSf = new SequenceFeature(sf, enteredType,
 +                newBegin, newEnd, enteredGroup, sf.getScore());
 +        newSf.setDescription(enteredDesc);
 +        ffile.parseDescriptionHTML(newSf, false);
 +        // amend features dialog only updates one sequence at a time
 +        sequences.get(0).addSequenceFeature(newSf);
 +        boolean typeOrGroupChanged = (!featureType.equals(newSf.getType()) || !featureGroup
 +                .equals(newSf.getFeatureGroup()));
  
          ffile.parseDescriptionHTML(sf, false);
          if (typeOrGroupChanged)
        {
          for (int i = 0; i < sequences.size(); i++)
          {
 -          features.get(i).type = enteredType;
 -          features.get(i).featureGroup = group.getText().trim();
 -          features.get(i).description = description.getText().replace('\n',
 -                  ' ');
 -          sequences.get(i).addSequenceFeature(features.get(i));
 -          ffile.parseDescriptionHTML(features.get(i), false);
 +          SequenceFeature sf = features.get(i);
 +          SequenceFeature sf2 = new SequenceFeature(enteredType,
 +                  enteredDesc, sf.getBegin(), sf.getEnd(), enteredGroup);
 +          ffile.parseDescriptionHTML(sf2, false);
 +          sequences.get(i).addSequenceFeature(sf2);
          }
  
          Color newColour = colourPanel.getBackground();
@@@ -23,7 -23,7 +23,7 @@@ package jalview.appletgui
  import jalview.api.FeatureColourI;
  import jalview.api.FeatureSettingsControllerI;
  import jalview.datamodel.AlignmentI;
 -import jalview.datamodel.SequenceFeature;
 +import jalview.datamodel.SequenceI;
  import jalview.util.MessageManager;
  
  import java.awt.BorderLayout;
@@@ -56,16 -56,17 +56,16 @@@ import java.awt.event.MouseListener
  import java.awt.event.MouseMotionListener;
  import java.awt.event.WindowAdapter;
  import java.awt.event.WindowEvent;
 +import java.util.ArrayList;
  import java.util.Arrays;
 -import java.util.Enumeration;
  import java.util.HashSet;
  import java.util.List;
  import java.util.Map;
  import java.util.Set;
 -import java.util.Vector;
  
- public class FeatureSettings extends Panel implements ItemListener,
-         MouseListener, MouseMotionListener, ActionListener,
-         AdjustmentListener, FeatureSettingsControllerI
+ public class FeatureSettings extends Panel
+         implements ItemListener, MouseListener, MouseMotionListener,
+         ActionListener, AdjustmentListener, FeatureSettingsControllerI
  {
    FeatureRenderer fr;
  
  
      add(lowerPanel, BorderLayout.SOUTH);
  
-     groupPanel.setLayout(new GridLayout(
-             (fr.getFeatureGroupsSize()) / 4 + 1, 4)); // JBPNote - this was
-                                                       // scaled on number of
-                                                       // visible groups. seems
-                                                       // broken
+     groupPanel.setLayout(
+             new GridLayout((fr.getFeatureGroupsSize()) / 4 + 1, 4)); // JBPNote
+                                                                      // - this
+                                                                      // was
+                                                                      // scaled
+                                                                      // on
+                                                                      // number
+                                                                      // of
+                                                                      // visible
+                                                                      // groups.
+                                                                      // seems
+                                                                      // broken
      groupPanel.validate();
  
      add(groupPanel, BorderLayout.NORTH);
    public void paint(Graphics g)
    {
      g.setColor(Color.black);
-     g.drawString(MessageManager
-             .getString("label.no_features_added_to_this_alignment"), 10, 20);
-     g.drawString(MessageManager
-             .getString("label.features_can_be_added_from_searches_1"), 10,
-             40);
-     g.drawString(MessageManager
-             .getString("label.features_can_be_added_from_searches_2"), 10,
-             60);
+     g.drawString(MessageManager.getString(
+             "label.no_features_added_to_this_alignment"), 10, 20);
+     g.drawString(MessageManager.getString(
+             "label.features_can_be_added_from_searches_1"), 10, 40);
+     g.drawString(MessageManager.getString(
+             "label.features_can_be_added_from_searches_2"), 10, 60);
    }
  
    protected void popupSort(final MyCheckbox check,
    {
      final String type = check.type;
      final FeatureColourI typeCol = fr.getFeatureStyle(type);
-     PopupMenu men = new PopupMenu(MessageManager.formatMessage(
-             "label.settings_for_type", new String[] { type }));
+     PopupMenu men = new PopupMenu(MessageManager
+             .formatMessage("label.settings_for_type", new String[]
+             { type }));
      java.awt.MenuItem scr = new MenuItem(
              MessageManager.getString("label.sort_by_score"));
      men.add(scr);
        @Override
        public void actionPerformed(ActionEvent e)
        {
-         me.ap.alignFrame.avc.sortAlignmentByFeatureScore(Arrays
-                 .asList(new String[] { type }));
+         me.ap.alignFrame.avc
+                 .sortAlignmentByFeatureScore(Arrays.asList(new String[]
+                 { type }));
        }
  
      });
        @Override
        public void actionPerformed(ActionEvent e)
        {
-         me.ap.alignFrame.avc.sortAlignmentByFeatureDensity(Arrays
-                 .asList(new String[] { type }));
+         me.ap.alignFrame.avc
+                 .sortAlignmentByFeatureDensity(Arrays.asList(new String[]
+                 { type }));
        }
  
      });
      });
      men.add(selectContaining);
  
-     MenuItem selectNotContaining = new MenuItem(
-             MessageManager.getString("label.select_columns_not_containing"));
+     MenuItem selectNotContaining = new MenuItem(MessageManager
+             .getString("label.select_columns_not_containing"));
      selectNotContaining.addActionListener(new ActionListener()
      {
        @Override
    // Group selection states
    void resetTable(boolean groupsChanged)
    {
 -    SequenceFeature[] tmpfeatures;
 -    String group = null, type;
 -    Vector<String> visibleChecks = new Vector<String>();
 +    List<String> displayableTypes = new ArrayList<String>();
      Set<String> foundGroups = new HashSet<String>();
 +
      AlignmentI alignment = av.getAlignment();
  
      for (int i = 0; i < alignment.getHeight(); i++)
      {
 -      if (alignment.getSequenceAt(i).getSequenceFeatures() == null)
 -      {
 -        continue;
 -      }
 +      SequenceI seq = alignment.getSequenceAt(i);
  
 -      tmpfeatures = alignment.getSequenceAt(i).getSequenceFeatures();
 -      int index = 0;
 -      while (index < tmpfeatures.length)
 +      /*
 +       * get the sequence's groups for positional features
 +       * and keep track of which groups are visible
 +       */
 +      Set<String> groups = seq.getFeatures().getFeatureGroups(true);
 +      Set<String> visibleGroups = new HashSet<String>();
 +      for (String group : groups)
        {
 -        group = tmpfeatures[index].featureGroup;
 -        foundGroups.add(group);
 -
 +        // if (group == null || fr.checkGroupVisibility(group, true))
          if (group == null || checkGroupState(group))
          {
 -          type = tmpfeatures[index].getType();
 -          if (!visibleChecks.contains(type))
 -          {
 -            visibleChecks.addElement(type);
 -          }
 +          visibleGroups.add(group);
          }
 -        index++;
        }
 +      foundGroups.addAll(groups);
 +
 +      /*
 +       * get distinct feature types for visible groups
 +       * record distinct visible types
 +       */
 +      Set<String> types = seq.getFeatures().getFeatureTypesForGroups(true,
 +              visibleGroups.toArray(new String[visibleGroups.size()]));
 +      displayableTypes.addAll(types);
      }
  
      /*
      {
        comps = featurePanel.getComponents();
        check = (MyCheckbox) comps[i];
 -      if (!visibleChecks.contains(check.type))
 +      if (!displayableTypes.contains(check.type))
        {
          featurePanel.remove(i);
          cSize--;
        {
          String item = rol.get(ro);
  
 -        if (!visibleChecks.contains(item))
 +        if (!displayableTypes.contains(item))
          {
            continue;
          }
  
 -        visibleChecks.removeElement(item);
 +        displayableTypes.remove(item);
  
          addCheck(false, item);
        }
      }
  
 -    // now add checkboxes which should be visible,
 -    // if they have not already been added
 -    Enumeration<String> en = visibleChecks.elements();
 -
 -    while (en.hasMoreElements())
 +    /*
 +     * now add checkboxes which should be visible,
 +     * if they have not already been added
 +     */
 +    for (String type : displayableTypes)
      {
 -      addCheck(groupsChanged, en.nextElement().toString());
 +      addCheck(groupsChanged, type);
      }
  
-     featurePanel.setLayout(new GridLayout(featurePanel.getComponentCount(),
-             1, 10, 5));
+     featurePanel.setLayout(
+             new GridLayout(featurePanel.getComponentCount(), 1, 10, 5));
      featurePanel.validate();
  
      if (scrollPane != null)
      }
      else
      {
-       comp = featurePanel.getComponentAt(evt.getX(), evt.getY()
-               + evt.getComponent().getLocation().y);
+       comp = featurePanel.getComponentAt(evt.getX(),
+               evt.getY() + evt.getComponent().getLocation().y);
      }
  
      if (comp != null && comp instanceof Checkbox)
@@@ -122,7 -122,7 +122,7 @@@ public class Finder extends Panel imple
      for (SearchResultMatchI match : searchResults.getResults())
      {
        seqs.add(match.getSequence().getDatasetSequence());
 -      features.add(new SequenceFeature(searchString, "Search Results", null,
 +      features.add(new SequenceFeature(searchString, "Search Results",
                match.getStart(), match.getEnd(), "Search Results"));
      }
  
      // 'SelectRegion' selection
      if (!haveResults)
      {
-       ap.alignFrame.statusBar.setText(MessageManager
-               .getString("label.finished_searching"));
+       ap.alignFrame.statusBar.setText(
+               MessageManager.getString("label.finished_searching"));
        resIndex = -1;
        seqIndex = 0;
      }
      {
        if (findAll)
        {
-         String message = (idMatch.size() > 0) ? "" + idMatch.size()
-                 + " IDs" : "";
+         String message = (idMatch.size() > 0) ? "" + idMatch.size() + " IDs"
+                 : "";
          if (idMatch.size() > 0 && searchResults != null
                  && searchResults.getSize() > 0)
          {
          {
            message += searchResults.getSize() + " subsequence matches.";
          }
-         ap.alignFrame.statusBar.setText(MessageManager.formatMessage(
-                 "label.search_results", new String[] { searchString,
-                     message }));
+         ap.alignFrame.statusBar.setText(MessageManager
+                 .formatMessage("label.search_results", new String[]
+                 { searchString, message }));
  
        }
        else
        {
          // TODO: indicate sequence and matching position in status bar
-         ap.alignFrame.statusBar.setText(MessageManager.formatMessage(
-                 "label.found_match_for", new String[] { searchString }));
+         ap.alignFrame.statusBar.setText(MessageManager
+                 .formatMessage("label.found_match_for", new String[]
+                 { searchString }));
        }
      }
    }
      caseSensitive.setLabel(MessageManager.getString("label.match_case"));
      caseSensitive.setBounds(new Rectangle(30, 39, 126, 23));
  
-     searchDescription.setLabel(MessageManager
-             .getString("label.include_description"));
+     searchDescription.setLabel(
+             MessageManager.getString("label.include_description"));
      searchDescription.setBounds(new Rectangle(30, 59, 170, 23));
      actionsPanel.add(findNext, null);
      actionsPanel.add(findAll, null);
@@@ -20,6 -20,7 +20,6 @@@
   */
  package jalview.appletgui;
  
 -import jalview.datamodel.Sequence;
  import jalview.datamodel.SequenceFeature;
  import jalview.datamodel.SequenceGroup;
  import jalview.datamodel.SequenceI;
@@@ -38,8 -39,8 +38,8 @@@ import java.util.ArrayList
  import java.util.HashMap;
  import java.util.List;
  
- public class IdPanel extends Panel implements MouseListener,
-         MouseMotionListener
+ public class IdPanel extends Panel
+         implements MouseListener, MouseMotionListener
  {
  
    protected IdCanvas idCanvas;
  
    UrlProviderI urlProvider = null;
  
 -  public IdPanel(AlignViewport av, AlignmentPanel parent)
 +  public IdPanel(AlignViewport viewport, AlignmentPanel parent)
    {
 -    this.av = av;
 +    this.av = viewport;
      alignPanel = parent;
 -    idCanvas = new IdCanvas(av);
 +    idCanvas = new IdCanvas(viewport);
      setLayout(new BorderLayout());
      add(idCanvas, BorderLayout.CENTER);
      idCanvas.addMouseListener(this);
  
      // make a list of label,url pairs
      HashMap<String, String> urlList = new HashMap<String, String>();
 -    if (av.applet != null)
 +    if (viewport.applet != null)
      {
        for (int i = 1; i < 10; i++)
        {
 -        label = av.applet.getParameter("linkLabel_" + i);
 -        url = av.applet.getParameter("linkURL_" + i);
 +        label = viewport.applet.getParameter("linkLabel_" + i);
 +        url = viewport.applet.getParameter("linkURL_" + i);
  
          // only add non-null parameters
          if (label != null)
@@@ -88,7 -89,7 +88,7 @@@
        if (!urlList.isEmpty())
        {
          // set default as first entry in list
 -        String defaultUrl = av.applet.getParameter("linkLabel_1");
 +        String defaultUrl = viewport.applet.getParameter("linkLabel_1");
          UrlProviderFactoryI factory = new AppletUrlProviderFactory(
                  defaultUrl, urlList);
          urlProvider = factory.createUrlProvider();
  
      SequenceI sequence = av.getAlignment().getSequenceAt(seq);
  
 -    // look for non-pos features
      StringBuffer tooltiptext = new StringBuffer();
 -    if (sequence != null)
 +    if (sequence == null)
      {
 -      if (sequence.getDescription() != null)
 +      return;
 +    }
 +    if (sequence.getDescription() != null)
 +    {
 +      tooltiptext.append(sequence.getDescription());
 +      tooltiptext.append("\n");
 +    }
 +
 +    for (SequenceFeature sf : sequence.getFeatures()
 +            .getNonPositionalFeatures())
 +    {
 +      boolean nl = false;
 +      if (sf.getFeatureGroup() != null)
        {
 -        tooltiptext.append(sequence.getDescription());
 -        tooltiptext.append("\n");
 +        tooltiptext.append(sf.getFeatureGroup());
 +        nl = true;
        }
 -
 -      SequenceFeature sf[] = sequence.getSequenceFeatures();
 -      for (int sl = 0; sf != null && sl < sf.length; sl++)
 +      if (sf.getType() != null)
        {
 -        if (sf[sl].begin == sf[sl].end && sf[sl].begin == 0)
 -        {
 -          boolean nl = false;
 -          if (sf[sl].getFeatureGroup() != null)
 -          {
 -            tooltiptext.append(sf[sl].getFeatureGroup());
 -            nl = true;
 -          }
 -          ;
 -          if (sf[sl].getType() != null)
 -          {
 -            tooltiptext.append(" ");
 -            tooltiptext.append(sf[sl].getType());
 -            nl = true;
 -          }
 -          ;
 -          if (sf[sl].getDescription() != null)
 -          {
 -            tooltiptext.append(" ");
 -            tooltiptext.append(sf[sl].getDescription());
 -            nl = true;
 -          }
 -          ;
 -          if (!Float.isNaN(sf[sl].getScore()) && sf[sl].getScore() != 0f)
 -          {
 -            tooltiptext.append(" Score = ");
 -            tooltiptext.append(sf[sl].getScore());
 -            nl = true;
 -          }
 -          ;
 -          if (sf[sl].getStatus() != null && sf[sl].getStatus().length() > 0)
 -          {
 -            tooltiptext.append(" (");
 -            tooltiptext.append(sf[sl].getStatus());
 -            tooltiptext.append(")");
 -            nl = true;
 -          }
 -          ;
 -          if (nl)
 -          {
 -            tooltiptext.append("\n");
 -          }
 -        }
 +        tooltiptext.append(" ");
 +        tooltiptext.append(sf.getType());
 +        nl = true;
 +      }
 +      if (sf.getDescription() != null)
 +      {
 +        tooltiptext.append(" ");
 +        tooltiptext.append(sf.getDescription());
 +        nl = true;
 +      }
 +      if (!Float.isNaN(sf.getScore()) && sf.getScore() != 0f)
 +      {
 +        tooltiptext.append(" Score = ");
 +        tooltiptext.append(sf.getScore());
 +        nl = true;
 +      }
 +      if (sf.getStatus() != null && sf.getStatus().length() > 0)
 +      {
 +        tooltiptext.append(" (");
 +        tooltiptext.append(sf.getStatus());
 +        tooltiptext.append(")");
 +        nl = true;
 +      }
 +      if (nl)
 +      {
 +        tooltiptext.append("\n");
        }
      }
 +
      if (tooltiptext.length() == 0)
      {
        // nothing to display - so clear tooltip if one is visible
      }
      if (tooltip == null)
      {
-       tooltip = new Tooltip(sequence.getDisplayId(true) + "\n"
-               + tooltiptext.toString(), idCanvas);
+       tooltip = new Tooltip(
+               sequence.getDisplayId(true) + "\n" + tooltiptext.toString(),
+               idCanvas);
      }
      else
      {
-       tooltip.setTip(sequence.getDisplayId(true) + "\n"
-               + tooltiptext.toString());
+       tooltip.setTip(
+               sequence.getDisplayId(true) + "\n" + tooltiptext.toString());
      }
      tooltiptext = null;
    }
  
      int seq = alignPanel.seqPanel.findSeq(e);
  
-     if ((e.getModifiers() & InputEvent.BUTTON3_MASK) == InputEvent.BUTTON3_MASK)
+     if ((e.getModifiers()
+             & InputEvent.BUTTON3_MASK) == InputEvent.BUTTON3_MASK)
      {
 -      Sequence sq = (Sequence) av.getAlignment().getSequenceAt(seq);
 +      SequenceI sq = av.getAlignment().getSequenceAt(seq);
  
 -      // build a new links menu based on the current links + any non-positional
 -      // features
 +      /*
 +       *  build a new links menu based on the current links
 +       *  and any non-positional features
 +       */
        List<String> nlinks;
        if (urlProvider != null)
        {
        {
          nlinks = new ArrayList<String>();
        }
 -      SequenceFeature sf[] = sq == null ? null : sq.getSequenceFeatures();
 -      for (int sl = 0; sf != null && sl < sf.length; sl++)
 +
 +      for (SequenceFeature sf : sq.getFeatures().getNonPositionalFeatures())
        {
 -        if (sf[sl].begin == sf[sl].end && sf[sl].begin == 0)
 +        if (sf.links != null)
          {
 -          if (sf[sl].links != null && sf[sl].links.size() > 0)
 +          for (String link : sf.links)
            {
 -            for (int l = 0, lSize = sf[sl].links.size(); l < lSize; l++)
 -            {
 -              nlinks.add(sf[sl].links.elementAt(l));
 -            }
 +            nlinks.add(link);
            }
          }
        }
      }
  
      if ((av.getSelectionGroup() == null)
-             || ((!jalview.util.Platform.isControlDown(e) && !e
-                     .isShiftDown()) && av.getSelectionGroup() != null))
+             || ((!jalview.util.Platform.isControlDown(e)
+                     && !e.isShiftDown()) && av.getSelectionGroup() != null))
      {
        av.setSelectionGroup(new SequenceGroup());
        av.getSelectionGroup().setStartRes(0);
      }
      for (int i = start; i <= end; i++)
      {
-       av.getSelectionGroup().addSequence(
-               av.getAlignment().getSequenceAt(i), i == end);
+       av.getSelectionGroup().addSequence(av.getAlignment().getSequenceAt(i),
+               i == end);
      }
  
    }
  
      boolean up = true;
  
 -    public ScrollThread(boolean up)
 +    public ScrollThread(boolean isUp)
      {
 -      this.up = up;
 +      this.up = isUp;
        start();
      }
  
@@@ -113,12 -113,12 +113,12 @@@ public class SeqCanvas extends Panel im
        {
          if (mstring != null)
          {
-           g.drawString(mstring, mpos * avcharWidth, ypos
-                   - (avcharHeight / 2));
+           g.drawString(mstring, mpos * avcharWidth,
+                   ypos - (avcharHeight / 2));
          }
-         g.drawLine((mpos * avcharWidth) + (avcharWidth / 2), (ypos + 2)
-                 - (avcharHeight / 2), (mpos * avcharWidth)
-                 + (avcharWidth / 2), ypos - 2);
+         g.drawLine((mpos * avcharWidth) + (avcharWidth / 2),
+                 (ypos + 2) - (avcharHeight / 2),
+                 (mpos * avcharWidth) + (avcharWidth / 2), ypos - 2);
        }
      }
    }
        {
          int x = LABEL_WEST - fm.stringWidth(String.valueOf(value))
                  - avcharWidth / 2;
-         g.drawString(value + "", x, (ypos + (i * avcharHeight))
-                 - (avcharHeight / 5));
+         g.drawString(value + "", x,
+                 (ypos + (i * avcharHeight)) - (avcharHeight / 5));
        }
      }
    }
  
        if (value != -1)
        {
-         g.drawString(String.valueOf(value), 0, (ypos + (i * avcharHeight))
-                 - (avcharHeight / 5));
+         g.drawString(String.valueOf(value), 0,
+                 (ypos + (i * avcharHeight)) - (avcharHeight / 5));
        }
      }
    }
      lastsr = ranges.getStartRes();
  
      fastPaint = true;
-     gg.copyArea(horizontal * avcharWidth, vertical * avcharHeight, imgWidth
-             - horizontal * avcharWidth,
+     gg.copyArea(horizontal * avcharWidth, vertical * avcharHeight,
+             imgWidth - horizontal * avcharWidth,
              imgHeight - vertical * avcharHeight, -horizontal * avcharWidth,
              -vertical * avcharHeight);
  
-     int sr = ranges.getStartRes(), er = ranges.getEndRes(), ss = ranges
-             .getStartSeq(), es = ranges
-             .getEndSeq(), transX = 0, transY = 0;
+     int sr = ranges.getStartRes(), er = ranges.getEndRes(),
+             ss = ranges.getStartSeq(), es = ranges.getEndSeq(), transX = 0,
+             transY = 0;
  
      if (horizontal > 0) // scrollbar pulled right, image to the left
      {
        ss = es - vertical;
        if (ss < ranges.getStartSeq()) // ie scrolling too fast, more than a page
                                       // at a
-                                  // time
+       // time
        {
          ss = ranges.getStartSeq();
        }
    {
  
      if (img != null
-             && (fastPaint || (getSize().width != g.getClipBounds().width) || (getSize().height != g
-                     .getClipBounds().height)))
+             && (fastPaint || (getSize().width != g.getClipBounds().width)
+                     || (getSize().height != g.getClipBounds().height)))
      {
        g.drawImage(img, 0, 0, this);
        fastPaint = false;
              continue;
            }
  
-           gg.fillPolygon(new int[] { res * avcharWidth - avcharHeight / 4,
-               res * avcharWidth + avcharHeight / 4, res * avcharWidth },
-                   new int[] { ypos - (avcharHeight / 2),
-                       ypos - (avcharHeight / 2),
-                       ypos - (avcharHeight / 2) + 8 }, 3);
+           gg.fillPolygon(
+                   new int[]
+                   { res * avcharWidth - avcharHeight / 4,
+                       res * avcharWidth + avcharHeight / 4,
+                       res * avcharWidth },
+                   new int[]
+                   { ypos - (avcharHeight / 2), ypos - (avcharHeight / 2),
+                       ypos - (avcharHeight / 2) + 8 },
+                   3);
  
          }
        }
      return annotations.adjustPanelHeight();
    }
  
 -  private void drawPanel(Graphics g1, int startRes, int endRes,
 -          int startSeq, int endSeq, int offset)
 +  private void drawPanel(Graphics g1, final int startRes, final int endRes,
 +          final int startSeq, final int endSeq, final int offset)
    {
  
      if (!av.hasHiddenColumns())
      }
      else
      {
 -
        int screenY = 0;
 +      final int screenYMax = endRes - startRes;
        int blockStart = startRes;
        int blockEnd = endRes;
  
              continue;
            }
  
 -          blockEnd = hideStart - 1;
 +          /*
 +           * draw up to just before the next hidden region, or the end of
 +           * the visible region, whichever comes first
 +           */
 +          blockEnd = Math.min(hideStart - 1, blockStart + screenYMax
 +                  - screenY);
  
            g1.translate(screenY * avcharWidth, 0);
  
            draw(g1, blockStart, blockEnd, startSeq, endSeq, offset);
  
 -          if (av.getShowHiddenMarkers())
 +          /*
 +           * draw the downline of the hidden column marker (ScalePanel draws the
 +           * triangle on top) if we reached it
 +           */
 +          if (av.getShowHiddenMarkers() && blockEnd == hideStart - 1)
            {
              g1.setColor(Color.blue);
              g1.drawLine((blockEnd - blockStart + 1) * avcharWidth - 1,
-                     0 + offset, (blockEnd - blockStart + 1) * avcharWidth
-                             - 1, (endSeq - startSeq + 1) * avcharHeight
-                             + offset);
+                     0 + offset,
+                     (blockEnd - blockStart + 1) * avcharWidth - 1,
+                     (endSeq - startSeq + 1) * avcharHeight + offset);
            }
  
            g1.translate(-screenY * avcharWidth, 0);
            screenY += blockEnd - blockStart + 1;
            blockStart = hideEnd + 1;
  
 -          if (screenY > (endRes - startRes))
 +          if (screenY > screenYMax)
            {
              // already rendered last block
              return;
            }
          }
        }
 -      if (screenY <= (endRes - startRes))
 +      if (screenY <= screenYMax)
        {
          // remaining visible region to render
          blockEnd = blockStart + (endRes - startRes) - screenY;
  
        if (av.isShowSequenceFeatures())
        {
-         fr.drawSequence(g, nextSeq, startRes, endRes, offset
-                 + ((i - startSeq) * avcharHeight), false);
+         fr.drawSequence(g, nextSeq, startRes, endRes,
+                 offset + ((i - startSeq) * avcharHeight), false);
        }
  
        // / Highlight search Results once all sequences have been drawn
        if (av.hasSearchResults())
        {
          int[] visibleResults = av.getSearchResults().getResults(nextSeq,
-                 startRes,
-                 endRes);
+                 startRes, endRes);
          if (visibleResults != null)
          {
            for (int r = 0; r < visibleResults.length; r += 2)
            {
              sr.drawHighlightedText(nextSeq, visibleResults[r],
-                     visibleResults[r + 1], (visibleResults[r] - startRes)
-                             * avcharWidth, offset
-                             + ((i - startSeq) * avcharHeight));
+                     visibleResults[r + 1],
+                     (visibleResults[r] - startRes) * avcharWidth,
+                     offset + ((i - startSeq) * avcharHeight));
            }
          }
        }
          {
            sx = (group.getStartRes() - startRes) * avcharWidth;
            sy = offset + ((i - startSeq) * avcharHeight);
-           ex = (((group.getEndRes() + 1) - group.getStartRes()) * avcharWidth) - 1;
+           ex = (((group.getEndRes() + 1) - group.getStartRes())
+                   * avcharWidth) - 1;
  
            if (sx + ex < 0 || sx > imgWidth)
            {
            }
  
            if ((sx <= (endRes - startRes) * avcharWidth)
-                   && group.getSequences(null).contains(
-                           av.getAlignment().getSequenceAt(i)))
+                   && group.getSequences(null)
+                           .contains(av.getAlignment().getSequenceAt(i)))
            {
              if ((bottom == -1)
-                     && (i >= alHeight || !group.getSequences(null)
-                             .contains(
-                                     av.getAlignment().getSequenceAt(i + 1))))
+                     && (i >= alHeight || !group.getSequences(null).contains(
+                             av.getAlignment().getSequenceAt(i + 1))))
              {
                bottom = sy + avcharHeight;
              }
  
              if (!inGroup)
              {
-               if (((top == -1) && (i == 0))
-                       || !group.getSequences(null).contains(
-                               av.getAlignment().getSequenceAt(i - 1)))
+               if (((top == -1) && (i == 0)) || !group.getSequences(null)
+                       .contains(av.getAlignment().getSequenceAt(i - 1)))
                {
                  top = sy;
                }
@@@ -54,8 -54,10 +54,8 @@@ import java.awt.event.InputEvent
  import java.awt.event.MouseEvent;
  import java.awt.event.MouseListener;
  import java.awt.event.MouseMotionListener;
 -import java.util.ArrayList;
  import java.util.Collections;
  import java.util.List;
 -import java.util.ListIterator;
  import java.util.Vector;
  
  public class SeqPanel extends Panel implements MouseMotionListener,
      if (editCommand != null && editCommand.getSize() > 0)
      {
        ap.alignFrame.addHistoryItem(editCommand);
-       av.firePropertyChange("alignment", null, av.getAlignment()
-               .getSequences());
+       av.firePropertyChange("alignment", null,
+               av.getAlignment().getSequences());
      }
  
      startseq = -1;
    {
      seqCanvas.cursorX += dx;
      seqCanvas.cursorY += dy;
-     if (av.hasHiddenColumns()
-             && !av.getAlignment().getHiddenColumns()
-                     .isVisible(seqCanvas.cursorX))
+     if (av.hasHiddenColumns() && !av.getAlignment().getHiddenColumns()
+             .isVisible(seqCanvas.cursorX))
      {
        int original = seqCanvas.cursorX - dx;
        int maxWidth = av.getAlignment().getWidth();
  
        while (!av.getAlignment().getHiddenColumns()
-               .isVisible(seqCanvas.cursorX)
-               && seqCanvas.cursorX < maxWidth && seqCanvas.cursorX > 0)
+               .isVisible(seqCanvas.cursorX) && seqCanvas.cursorX < maxWidth
+               && seqCanvas.cursorX > 0)
        {
          seqCanvas.cursorX += dx;
        }
  
-       if (seqCanvas.cursorX >= maxWidth
-               || !av.getAlignment().getHiddenColumns()
-                       .isVisible(seqCanvas.cursorX))
+       if (seqCanvas.cursorX >= maxWidth || !av.getAlignment()
+               .getHiddenColumns().isVisible(seqCanvas.cursorX))
        {
          seqCanvas.cursorX = original;
        }
        {
          ranges.scrollUp(false);
        }
-       while (seqCanvas.cursorX < hidden.adjustForHiddenColumns(ranges
-               .getStartRes()))
+       while (seqCanvas.cursorX < hidden
+               .adjustForHiddenColumns(ranges.getStartRes()))
        {
  
          if (!ranges.scrollRight(false))
            break;
          }
        }
-       while (seqCanvas.cursorX > hidden.adjustForHiddenColumns(ranges
-               .getEndRes()))
+       while (seqCanvas.cursorX > hidden
+               .adjustForHiddenColumns(ranges.getEndRes()))
        {
          if (!ranges.scrollRight(true))
          {
      }
      else
      {
-       residue = "X".equalsIgnoreCase(displayChar) ? "X" : ("*"
-               .equals(displayChar) ? "STOP" : ResidueProperties.aa2Triplet
-               .get(displayChar));
+       residue = "X".equalsIgnoreCase(displayChar) ? "X"
+               : ("*".equals(displayChar) ? "STOP"
+                       : ResidueProperties.aa2Triplet.get(displayChar));
        if (residue != null)
        {
          text.append(" Residue: ").append(residue);
  
      // For now, ignore the mouseWheel font resizing on Macs
      // As the Button2_mask always seems to be true
-     if ((evt.getModifiers() & InputEvent.BUTTON2_MASK) == InputEvent.BUTTON2_MASK
+     if ((evt.getModifiers()
+             & InputEvent.BUTTON2_MASK) == InputEvent.BUTTON2_MASK
              && !av.MAC)
      {
        mouseWheelPressed = true;
      }
  
      int seq = findSeq(evt);
 -    int res = findRes(evt);
 +    int res = findColumn(evt);
  
      if (seq < 0 || res < 0)
      {
          av.setSelectionGroup(null);
        }
  
 -      int column = findRes(evt);
 -      boolean isGapped = Comparison.isGap(sequence.getCharAt(column));
 -      List<SequenceFeature> features = findFeaturesAtRes(sequence,
 -              sequence.findPosition(column));
 -      if (isGapped)
 -      {
 -        removeAdjacentFeatures(features, column + 1, sequence);
 -      }
 +      int column = findColumn(evt);
 +      List<SequenceFeature> features = findFeaturesAtColumn(sequence,
 +              column + 1);
  
        if (!features.isEmpty())
        {
          SearchResultsI highlight = new SearchResults();
-         highlight.addResult(sequence, features.get(0).getBegin(), features
-                 .get(0).getEnd());
+         highlight.addResult(sequence, features.get(0).getBegin(),
+                 features.get(0).getEnd());
          seqCanvas.highlightSearchResults(highlight);
          seqCanvas.getFeatureRenderer().amendFeatures(
                  Collections.singletonList(sequence), features, false, ap);
 -
 -        seqCanvas.highlightSearchResults(null);
 +        av.setSearchResults(null); // clear highlighting
 +        seqCanvas.repaint(); // draw new/amended features
        }
      }
    }
  
    int wrappedBlock = -1;
  
 -  int findRes(MouseEvent evt)
 +  /**
 +   * Returns the aligned sequence position (base 0) at the mouse position, or
 +   * the closest visible one
 +   * 
 +   * @param evt
 +   * @return
 +   */
 +  int findColumn(MouseEvent evt)
    {
      int res = 0;
      int x = evt.getX();
  
        y -= hgap;
  
-       seq = Math.min((y % cHeight) / av.getCharHeight(), av.getAlignment()
-               .getHeight() - 1);
+       seq = Math.min((y % cHeight) / av.getCharHeight(),
+               av.getAlignment().getHeight() - 1);
        if (seq < 0)
        {
          seq = -1;
      }
      else
      {
-       seq = Math.min((y / av.getCharHeight())
-               + av.getRanges().getStartSeq(),
-               av
-               .getAlignment().getHeight() - 1);
+       seq = Math.min(
+               (y / av.getCharHeight()) + av.getRanges().getStartSeq(),
+               av.getAlignment().getHeight() - 1);
        if (seq < 0)
        {
          seq = -1;
    {
  
      int seq = findSeq(evt);
 -    int res = findRes(evt);
 +    int res = findColumn(evt);
  
      if (seq < av.getAlignment().getHeight()
              && res < av.getAlignment().getSequenceAt(seq).getLength())
    @Override
    public void mouseMoved(MouseEvent evt)
    {
 -    final int column = findRes(evt);
 +    final int column = findColumn(evt);
      int seq = findSeq(evt);
  
      if (seq >= av.getAlignment().getHeight() || seq < 0 || column < 0)
        else
        {
          String residue = (ch == 'x' || ch == 'X') ? "X"
-                 : ResidueProperties.aa2Triplet
-                 .get(String.valueOf(ch));
+                 : ResidueProperties.aa2Triplet.get(String.valueOf(ch));
          text.append(" Residue: ").append(residue == null ? ch : residue);
        }
        text.append(" (").append(Integer.toString(respos)).append(")");
      {
        for (int g = 0; g < groups.length; g++)
        {
-         if (groups[g].getStartRes() <= column && groups[g].getEndRes() >= column)
+         if (groups[g].getStartRes() <= column
+                 && groups[g].getEndRes() >= column)
          {
            if (!groups[g].getName().startsWith("JTreeGroup")
                    && !groups[g].getName().startsWith("JGroup"))
       */
      if (av.isShowSequenceFeatures())
      {
 -      List<SequenceFeature> allFeatures = findFeaturesAtRes(sequence,
 -              sequence.findPosition(column));
 -      if (isGapped)
 -      {
 -        removeAdjacentFeatures(allFeatures, column + 1, sequence);
 -      }
 +      List<SequenceFeature> allFeatures = findFeaturesAtColumn(sequence,
 +              column + 1);
        for (SequenceFeature sf : allFeatures)
        {
          tooltipText.append(sf.getType() + " " + sf.begin + ":" + sf.end);
    }
  
    /**
 -   * Removes from the list of features any that start after, or end before, the
 -   * given column position. This allows us to retain only those features
 -   * adjacent to a gapped position that straddle the position. Contact features
 -   * that 'straddle' the position are also removed, since they are not 'at' the
 -   * position.
 +   * Returns features at the specified aligned column on the given sequence.
 +   * Non-positional features are not included. If the column has a gap, then
 +   * enclosing features are included (but not contact features).
     * 
 -   * @param features
 -   * @param column
 -   *          alignment column (1..)
     * @param sequence
 +   * @param column
 +   *          (1..)
 +   * @return
     */
 -  protected void removeAdjacentFeatures(List<SequenceFeature> features,
 -          int column, SequenceI sequence)
 -  {
 -    // TODO should this be an AlignViewController method (shared by gui)?
 -    ListIterator<SequenceFeature> it = features.listIterator();
 -    while (it.hasNext())
 -    {
 -      SequenceFeature sf = it.next();
 -      if (sf.isContactFeature()
 -              || sequence.findIndex(sf.getBegin()) > column
 -              || sequence.findIndex(sf.getEnd()) < column)
 -      {
 -        it.remove();
 -      }
 -    }
 -  }
 -
 -  List<SequenceFeature> findFeaturesAtRes(SequenceI sequence, int res)
 +  List<SequenceFeature> findFeaturesAtColumn(SequenceI sequence, int column)
    {
 -    List<SequenceFeature> result = new ArrayList<>();
 -    SequenceFeature[] features = sequence.getSequenceFeatures();
 -    if (features != null)
 -    {
 -      for (int i = 0; i < features.length; i++)
 -      {
 -        if (av.getFeaturesDisplayed() == null || !av.getFeaturesDisplayed()
 -                .isVisible(features[i].getType()))
 -        {
 -          continue;
 -        }
 -
 -        if (features[i].featureGroup != null && !seqCanvas.fr
 -                .checkGroupVisibility(features[i].featureGroup, false))
 -        {
 -          continue;
 -        }
 -
 -        if ((features[i].getBegin() <= res)
 -                && (features[i].getEnd() >= res))
 -        {
 -          result.add(features[i]);
 -        }
 -      }
 -    }
 -
 -    return result;
 +    return seqCanvas.getFeatureRenderer().findFeaturesAtColumn(sequence, column);
    }
  
    Tooltip tooltip;
        int oldWidth = av.getCharWidth();
  
        // Which is bigger, left-right or up-down?
-       if (Math.abs(evt.getY() - lastMousePress.y) > Math.abs(evt.getX()
-               - lastMousePress.x))
+       if (Math.abs(evt.getY() - lastMousePress.y) > Math
+               .abs(evt.getX() - lastMousePress.x))
        {
          int fontSize = av.font.getSize();
  
        return;
      }
  
 -    int res = findRes(evt);
 +    int res = findColumn(evt);
  
      if (res < 0)
      {
      StringBuffer message = new StringBuffer();
      if (groupEditing)
      {
-       message.append(MessageManager.getString("action.edit_group")).append(
-               ":");
+       message.append(MessageManager.getString("action.edit_group"))
+               .append(":");
        if (editCommand == null)
        {
          editCommand = new EditCommand(
        }
        if (editCommand == null)
        {
-         editCommand = new EditCommand(MessageManager.formatMessage(
-                 "label.edit_params", new String[] { label }));
+         editCommand = new EditCommand(MessageManager
+                 .formatMessage("label.edit_params", new String[]
+                 { label }));
        }
      }
  
      ap.alignFrame.statusBar.setText(message.toString());
  
      // Are we editing within a selection group?
-     if (groupEditing
-             || (sg != null && sg.getSequences(av.getHiddenRepSequences())
-                     .contains(seq)))
+     if (groupEditing || (sg != null
+             && sg.getSequences(av.getHiddenRepSequences()).contains(seq)))
      {
        fixedColumns = true;
  
          // Find the next gap before the end
          // of the visible region boundary
          boolean blank = false;
 -        for (fixedRight = fixedRight; fixedRight > lastres; fixedRight--)
 +        for (; fixedRight > lastres; fixedRight--)
          {
            blank = true;
  
            {
              for (int j = 0; j < startres - lastres; j++)
              {
-               if (!jalview.util.Comparison.isGap(gs.getCharAt(fixedRight
-                       - j)))
+               if (!jalview.util.Comparison
+                       .isGap(gs.getCharAt(fixedRight - j)))
                {
                  blank = false;
                  break;
          }
          else
          {
-           editCommand.appendEdit(Action.INSERT_GAP,
-                   new SequenceI[] { seq }, lastres, startres - lastres,
-                   av.getAlignment(), true);
+           editCommand.appendEdit(Action.INSERT_GAP, new SequenceI[] { seq },
+                   lastres, startres - lastres, av.getAlignment(), true);
          }
        }
        else
            if (max > 0)
            {
              editCommand.appendEdit(Action.DELETE_GAP,
-                     new SequenceI[] { seq }, startres, max,
-                     av.getAlignment(), true);
+                     new SequenceI[]
+                     { seq }, startres, max, av.getAlignment(), true);
            }
          }
        }
        scrollThread = null;
      }
  
 -    int res = findRes(evt);
 +    int column = findColumn(evt);
      int seq = findSeq(evt);
      oldSeq = seq;
      startWrapBlock = wrappedBlock;
  
      SequenceI sequence = av.getAlignment().getSequenceAt(seq);
  
 -    if (sequence == null || res > sequence.getLength())
 +    if (sequence == null || column > sequence.getLength())
      {
        return;
      }
  
      stretchGroup = av.getSelectionGroup();
  
 -    if (stretchGroup == null || !stretchGroup.contains(sequence, res))
 +    if (stretchGroup == null || !stretchGroup.contains(sequence, column))
      {
 -      stretchGroup = av.getAlignment().findGroup(sequence, res);
 +      stretchGroup = av.getAlignment().findGroup(sequence, column);
        if (stretchGroup != null)
        {
          // only update the current selection if the popup menu has a group to
      }
  
      // DETECT RIGHT MOUSE BUTTON IN AWT
-     if ((evt.getModifiers() & InputEvent.BUTTON3_MASK) == InputEvent.BUTTON3_MASK)
+     if ((evt.getModifiers()
+             & InputEvent.BUTTON3_MASK) == InputEvent.BUTTON3_MASK)
      {
 -      List<SequenceFeature> allFeatures = findFeaturesAtRes(sequence,
 -              sequence.findPosition(res));
 +      List<SequenceFeature> allFeatures = findFeaturesAtColumn(sequence,
 +              sequence.findPosition(column + 1));
  
        Vector<String> links = null;
        for (SequenceFeature sf : allFeatures)
          {
            if (links == null)
            {
 -            links = new Vector<>();
 -          }
 -          for (int j = 0; j < sf.links.size(); j++)
 -          {
 -            links.addElement(sf.links.elementAt(j));
 +            links = new Vector<String>();
            }
 +          links.addAll(sf.links);
          }
        }
        APopupMenu popup = new APopupMenu(ap, null, links);
  
      if (av.cursorMode)
      {
 -      seqCanvas.cursorX = findRes(evt);
 +      seqCanvas.cursorX = findColumn(evt);
        seqCanvas.cursorY = findSeq(evt);
        seqCanvas.repaint();
        return;
      {
        // define a new group here
        SequenceGroup sg = new SequenceGroup();
 -      sg.setStartRes(res);
 -      sg.setEndRes(res);
 +      sg.setStartRes(column);
 +      sg.setEndRes(column);
        sg.addSequence(sequence, false);
        av.setSelectionGroup(sg);
        stretchGroup = sg;
  
    public void doMouseDraggedDefineMode(MouseEvent evt)
    {
 -    int res = findRes(evt);
 +    int res = findColumn(evt);
      int y = findSeq(evt);
  
      if (wrappedBlock != startWrapBlock)
              running = av.getRanges().scrollUp(true);
            }
  
-           if (mouseDragging && evt.getY() >= getSize().height
-                   && av.getAlignment().getHeight() > av.getRanges()
-                           .getEndSeq())
+           if (mouseDragging && evt.getY() >= getSize().height && av
+                   .getAlignment().getHeight() > av.getRanges().getEndSeq())
            {
              running = av.getRanges().scrollUp(false);
            }
      // handles selection messages...
      // TODO: extend config options to allow user to control if selections may be
      // shared between viewports.
-     if (av != null
-             && (av == source || !av.followSelection || (source instanceof AlignViewport && ((AlignmentViewport) source)
-                     .getSequenceSetId().equals(av.getSequenceSetId()))))
+     if (av != null && (av == source || !av.followSelection
+             || (source instanceof AlignViewport
+                     && ((AlignmentViewport) source).getSequenceSetId()
+                             .equals(av.getSequenceSetId()))))
      {
        return;
      }
        {
          if (av.getAlignment() == null)
          {
-           System.out
-                   .println("Selection message: alignviewport av SeqSetId="
-                           + av.getSequenceSetId() + " ViewId="
-                           + av.getViewId()
-                           + " 's alignment is NULL! returning immediatly.");
+           System.out.println("Selection message: alignviewport av SeqSetId="
+                   + av.getSequenceSetId() + " ViewId=" + av.getViewId()
+                   + " 's alignment is NULL! returning immediatly.");
            return;
          }
          sgroup = seqsel.intersect(av.getAlignment(),
        }
        repaint = av.isSelectionGroupChanged(true);
      }
-     if (copycolsel
-             && (av.getColumnSelection() == null || !av
-                     .isColSelChanged(true)))
+     if (copycolsel && (av.getColumnSelection() == null
+             || !av.isColSelChanged(true)))
      {
        // the current selection is unset or from a previous message
        // so import the new colsel.
        }
        repaint |= av.isColSelChanged(true);
      }
-     if (copycolsel
-             && av.hasHiddenColumns()
+     if (copycolsel && av.hasHiddenColumns()
              && (av.getColumnSelection() == null))
      {
        System.err.println("Bad things");
    {
  
      row = row < 0 ? ap.av.getRanges().getStartSeq() : row;
-     ap.scrollTo(ap.av.getRanges().getStartRes(), ap.av.getRanges()
-             .getStartRes(), row, true, true);
+     ap.scrollTo(ap.av.getRanges().getStartRes(),
+             ap.av.getRanges().getStartRes(), row, true, true);
    }
  
    /**
    {
  
      column = column < 0 ? ap.av.getRanges().getStartRes() : column;
-     ap.scrollTo(column, column, ap.av.getRanges().getStartSeq(), true, true);
+     ap.scrollTo(column, column, ap.av.getRanges().getStartSeq(), true,
+             true);
    }
  
    /**
@@@ -122,15 -122,15 +122,15 @@@ public class EditCommand implements Com
    {
    }
  
 -  public EditCommand(String description)
 +  public EditCommand(String desc)
    {
 -    this.description = description;
 +    this.description = desc;
    }
  
 -  public EditCommand(String description, Action command, SequenceI[] seqs,
 +  public EditCommand(String desc, Action command, SequenceI[] seqs,
            int position, int number, AlignmentI al)
    {
 -    this.description = description;
 +    this.description = desc;
      if (command == Action.CUT || command == Action.PASTE)
      {
        setEdit(new Edit(command, seqs, position, number, al));
      performEdit(0, null);
    }
  
 -  public EditCommand(String description, Action command, String replace,
 +  public EditCommand(String desc, Action command, String replace,
            SequenceI[] seqs, int position, int number, AlignmentI al)
    {
 -    this.description = description;
 +    this.description = desc;
      if (command == Action.REPLACE)
      {
        setEdit(new Edit(command, seqs, position, number, al, replace));
      /**
       * Check a contiguous edit; either
       * <ul>
-      * <li>a new Insert <n> positions to the right of the last <insert n>, or</li>
+      * <li>a new Insert <n> positions to the right of the last <insert n>,
+      * or</li>
       * <li>a new Delete <n> gaps which is <n> positions to the left of the last
       * delete.</li>
       * </ul>
       */
-     boolean contiguous = (action == Action.INSERT_GAP && e.position == lastEdit.position
-             + lastEdit.number)
-             || (action == Action.DELETE_GAP && e.position + e.number == lastEdit.position);
+     boolean contiguous = (action == Action.INSERT_GAP
+             && e.position == lastEdit.position + lastEdit.number)
+             || (action == Action.DELETE_GAP
+                     && e.position + e.number == lastEdit.position);
      if (contiguous)
      {
        /*
      {
        command.seqs[s].insertCharAt(command.position, command.number,
                command.gapChar);
-       // System.out.println("pos: "+command.position+" number: "+command.number);
+       // System.out.println("pos: "+command.position+" number:
+       // "+command.number);
      }
  
      adjustAnnotations(command, true, false, null);
    {
      for (int s = 0; s < command.seqs.length; s++)
      {
-       command.seqs[s].deleteChars(command.position, command.position
-               + command.number);
+       command.seqs[s].deleteChars(command.position,
+               command.position + command.number);
      }
  
      adjustAnnotations(command, false, false, null);
            // we are redoing an undone cut.
            sequence.setDatasetSequence(null);
          }
-         sequence.deleteChars(command.position, command.position
-                 + command.number);
+         sequence.deleteChars(command.position,
+                 command.position + command.number);
          if (command.oldds != null && command.oldds[i] != null)
          {
            // oldds entry contains the cut dataset sequence.
          {
            // modify the oldds if necessary
            if (oldds != sequence.getDatasetSequence()
 -                  || sequence.getSequenceFeatures() != null)
 +                  || sequence.getFeatures().hasFeatures())
            {
              if (command.oldds == null)
              {
                command.oldds = new SequenceI[command.seqs.length];
              }
              command.oldds[i] = oldds;
 -            adjustFeatures(command, i,
 +            // FIXME JAL-2541 JAL-2526 get correct positions if on a gap
 +            adjustFeatures(
 +                    command,
 +                    i,
                      sequence.findPosition(command.position),
 -                    sequence.findPosition(
 -                            command.position + command.number),
 +                    sequence.findPosition(command.position + command.number),
                      false);
            }
          }
              {
                newDSNeeded = true;
                start = command.seqs[i].findPosition(command.position);
-               end = command.seqs[i].findPosition(command.position
-                       + command.number);
+               end = command.seqs[i]
+                       .findPosition(command.position + command.number);
              }
              if (command.seqs[i].getStart() == start)
              {
        tmp = new StringBuffer(oldstring.substring(0, start));
        tmp.append(command.string[i]);
        String nogaprep = jalview.analysis.AlignSeq.extractGaps(
-               jalview.util.Comparison.GapChars, new String(
-                       command.string[i]));
+               jalview.util.Comparison.GapChars,
+               new String(command.string[i]));
        int ipos = command.seqs[i].findPosition(start)
                - command.seqs[i].getStart();
        tmp.append(oldstring.substring(end));
        command.seqs[i].setSequence(tmp.toString());
        command.string[i] = oldstring.substring(start, end).toCharArray();
        String nogapold = jalview.analysis.AlignSeq.extractGaps(
-               jalview.util.Comparison.GapChars, new String(
-                       command.string[i]));
+               jalview.util.Comparison.GapChars,
+               new String(command.string[i]));
        if (!nogaprep.toLowerCase().equals(nogapold.toLowerCase()))
        {
          if (newDSWasNeeded)
        AlignmentAnnotation[] tmp;
        for (int s = 0; s < command.seqs.length; s++)
        {
 +        command.seqs[s].sequenceChanged();
 +
          if (modifyVisibility)
          {
            // Rows are only removed or added to sequence object.
                  tmp = saved;
                  command.deletedAnnotationRows.put(command.seqs[s], saved);
                  // and then remove any annotation in the other views
-                 for (int alview = 0; views != null && alview < views.length; alview++)
+                 for (int alview = 0; views != null
+                         && alview < views.length; alview++)
                  {
                    if (views[alview] != command.al)
                    {
                  }
                  // and then duplicate added annotation on every other alignment
                  // view
-                 for (int vnum = 0; views != null && vnum < views.length; vnum++)
+                 for (int vnum = 0; views != null
+                         && vnum < views.length; vnum++)
                  {
                    if (views[vnum] != command.al)
                    {
            }
  
            System.arraycopy(annotations[a].annotations, command.position,
-                   temp, command.position + command.number, aSize
-                           - command.position);
+                   temp, command.position + command.number,
+                   aSize - command.position);
          }
          else
          {
                      annotations[a].annotations.length - command.position);
              if (copylen > 0)
              {
-               System.arraycopy(annotations[a].annotations,
-                       command.position, deleted, 0, copylen); // command.number);
+               System.arraycopy(annotations[a].annotations, command.position,
+                       deleted, 0, copylen); // command.number);
              }
            }
  
            if (annotations[a].annotations.length > command.position
                    + command.number)
            {
-             System.arraycopy(annotations[a].annotations, command.position
-                     + command.number, temp, command.position,
-                     annotations[a].annotations.length - command.position
-                             - command.number); // aSize
+             System.arraycopy(annotations[a].annotations,
+                     command.position + command.number, temp,
+                     command.position, annotations[a].annotations.length
+                             - command.position - command.number); // aSize
            }
          }
          else
      }
    }
  
 -  final static void adjustFeatures(Edit command, int index, int i, int j,
 -          boolean insert)
 +  final static void adjustFeatures(Edit command, int index, final int i,
 +          final int j, boolean insert)
    {
      SequenceI seq = command.seqs[index];
      SequenceI sequence = seq.getDatasetSequence();
        return;
      }
  
 -    SequenceFeature[] sf = sequence.getSequenceFeatures();
 +    List<SequenceFeature> sf = sequence.getFeatures()
 +            .getPositionalFeatures();
  
 -    if (sf == null)
 +    if (sf.isEmpty())
      {
        return;
      }
  
 -    SequenceFeature[] oldsf = new SequenceFeature[sf.length];
 +    List<SequenceFeature> oldsf = new ArrayList<SequenceFeature>();
  
      int cSize = j - i;
  
 -    for (int s = 0; s < sf.length; s++)
 +    for (SequenceFeature feature : sf)
      {
 -      SequenceFeature copy = new SequenceFeature(sf[s]);
 +      SequenceFeature copy = new SequenceFeature(feature);
  
 -      oldsf[s] = copy;
 +      oldsf.add(copy);
  
 -      if (sf[s].getEnd() < i)
 +      if (feature.getEnd() < i)
        {
          continue;
        }
  
 -      if (sf[s].getBegin() > j)
 +      if (feature.getBegin() > j)
        {
 -        sf[s].setBegin(copy.getBegin() - cSize);
 -        sf[s].setEnd(copy.getEnd() - cSize);
 +        int newBegin = copy.getBegin() - cSize;
 +        int newEnd = copy.getEnd() - cSize;
 +        SequenceFeature newSf = new SequenceFeature(feature, newBegin,
 +                newEnd, feature.getFeatureGroup(), feature.getScore());
 +        sequence.deleteFeature(feature);
 +        sequence.addSequenceFeature(newSf);
 +        // feature.setBegin(newBegin);
 +        // feature.setEnd(newEnd);
          continue;
        }
  
 -      if (sf[s].getBegin() >= i)
 +      int newBegin = feature.getBegin();
 +      int newEnd = feature.getEnd();
 +      if (newBegin >= i)
        {
 -        sf[s].setBegin(i);
 +        newBegin = i;
 +        // feature.setBegin(i);
        }
  
 -      if (sf[s].getEnd() < j)
 +      if (newEnd < j)
        {
 -        sf[s].setEnd(j - 1);
 +        newEnd = j - 1;
 +        // feature.setEnd(j - 1);
        }
 +      newEnd = newEnd - cSize;
 +      // feature.setEnd(feature.getEnd() - (cSize));
  
 -      sf[s].setEnd(sf[s].getEnd() - (cSize));
 -
 -      if (sf[s].getBegin() > sf[s].getEnd())
 +      sequence.deleteFeature(feature);
 +      if (newEnd >= newBegin)
        {
 -        sequence.deleteFeature(sf[s]);
 +        sequence.addSequenceFeature(new SequenceFeature(feature, newBegin,
 +                newEnd, feature.getFeatureGroup(), feature.getScore()));
        }
 +      // if (feature.getBegin() > feature.getEnd())
 +      // {
 +      // sequence.deleteFeature(feature);
 +      // }
      }
  
      if (command.editedFeatures == null)
      {
 -      command.editedFeatures = new Hashtable<SequenceI, SequenceFeature[]>();
 +      command.editedFeatures = new Hashtable<SequenceI, List<SequenceFeature>>();
      }
  
      command.editedFeatures.put(seq, oldsf);
  
      Hashtable<String, Annotation[]> deletedAnnotations;
  
 -    Hashtable<SequenceI, SequenceFeature[]> editedFeatures;
 +    Hashtable<SequenceI, List<SequenceFeature>> editedFeatures;
  
      AlignmentI al;
  
  
      char gapChar;
  
 -    public Edit(Action command, SequenceI[] seqs, int position, int number,
 -            char gapChar)
 +    public Edit(Action cmd, SequenceI[] sqs, int pos, int count,
 +            char gap)
      {
 -      this.command = command;
 -      this.seqs = seqs;
 -      this.position = position;
 -      this.number = number;
 -      this.gapChar = gapChar;
 +      this.command = cmd;
 +      this.seqs = sqs;
 +      this.position = pos;
 +      this.number = count;
 +      this.gapChar = gap;
      }
  
 -    Edit(Action command, SequenceI[] seqs, int position, int number,
 -            AlignmentI al)
 +    Edit(Action cmd, SequenceI[] sqs, int pos, int count,
 +            AlignmentI align)
      {
 -      this.gapChar = al.getGapCharacter();
 -      this.command = command;
 -      this.seqs = seqs;
 -      this.position = position;
 -      this.number = number;
 -      this.al = al;
 -
 -      alIndex = new int[seqs.length];
 -      for (int i = 0; i < seqs.length; i++)
 +      this.gapChar = align.getGapCharacter();
 +      this.command = cmd;
 +      this.seqs = sqs;
 +      this.position = pos;
 +      this.number = count;
 +      this.al = align;
 +
 +      alIndex = new int[sqs.length];
 +      for (int i = 0; i < sqs.length; i++)
        {
 -        alIndex[i] = al.findIndex(seqs[i]);
 +        alIndex[i] = align.findIndex(sqs[i]);
        }
  
 -      fullAlignmentHeight = (al.getHeight() == seqs.length);
 +      fullAlignmentHeight = (align.getHeight() == sqs.length);
      }
  
 -    Edit(Action command, SequenceI[] seqs, int position, int number,
 -            AlignmentI al, String replace)
 +    Edit(Action cmd, SequenceI[] sqs, int pos, int count,
 +            AlignmentI align, String replace)
      {
 -      this.command = command;
 -      this.seqs = seqs;
 -      this.position = position;
 -      this.number = number;
 -      this.al = al;
 -      this.gapChar = al.getGapCharacter();
 -      string = new char[seqs.length][];
 -      for (int i = 0; i < seqs.length; i++)
 +      this.command = cmd;
 +      this.seqs = sqs;
 +      this.position = pos;
 +      this.number = count;
 +      this.al = align;
 +      this.gapChar = align.getGapCharacter();
 +      string = new char[sqs.length][];
 +      for (int i = 0; i < sqs.length; i++)
        {
          string[i] = replace.toCharArray();
        }
  
 -      fullAlignmentHeight = (al.getHeight() == seqs.length);
 +      fullAlignmentHeight = (align.getHeight() == sqs.length);
      }
  
      public SequenceI[] getSequences()
@@@ -85,10 -85,11 +85,11 @@@ public class AlignViewController implem
      SequenceGroup[] gps = null;
      if (sg != null && (cs == null || cs.isEmpty()))
      {
-       gps = jalview.analysis.Grouping.makeGroupsFrom(viewport
-               .getSequenceSelection(), viewport.getAlignmentView(true)
-               .getSequenceStrings(viewport.getGapCharacter()), viewport
-               .getAlignment().getGroups());
+       gps = jalview.analysis.Grouping.makeGroupsFrom(
+               viewport.getSequenceSelection(),
+               viewport.getAlignmentView(true)
+                       .getSequenceStrings(viewport.getGapCharacter()),
+               viewport.getAlignment().getGroups());
      }
      else
      {
@@@ -96,8 -97,8 +97,8 @@@
        {
          gps = jalview.analysis.Grouping.makeGroupsFromCols(
                  (sg == null) ? viewport.getAlignment().getSequencesArray()
-                         : sg.getSequences().toArray(new SequenceI[0]), cs,
-                 viewport.getAlignment().getGroups());
+                         : sg.getSequences().toArray(new SequenceI[0]),
+                 cs, viewport.getAlignment().getGroups());
        }
      }
      if (gps != null)
      // JBPNote this routine could also mark rows, not just columns.
      // need a decent query structure to allow all types of feature searches
      BitSet bs = new BitSet();
-     SequenceCollectionI sqcol = (viewport.getSelectionGroup() == null || extendCurrent) ? viewport
-             .getAlignment() : viewport.getSelectionGroup();
+     SequenceCollectionI sqcol = (viewport.getSelectionGroup() == null
+             || extendCurrent) ? viewport.getAlignment()
+                     : viewport.getSelectionGroup();
  
      int nseq = findColumnsWithFeature(featureType, sqcol, bs);
  
        {
          viewport.setColumnSelection(cs);
          alignPanel.paintAlignment(true);
-         int columnCount = invert ? (sqcol.getEndRes() - sqcol.getStartRes() + 1)
-                 - bs.cardinality()
+         int columnCount = invert
+                 ? (sqcol.getEndRes() - sqcol.getStartRes() + 1)
+                         - bs.cardinality()
                  : bs.cardinality();
          avcg.setStatus(MessageManager.formatMessage(
-                 "label.view_controller_toggled_marked",
-                 new String[] {
-                     toggle ? MessageManager.getString("label.toggled")
-                             : MessageManager.getString("label.marked"),
+                 "label.view_controller_toggled_marked", new String[]
+                 { toggle ? MessageManager.getString("label.toggled")
+                         : MessageManager.getString("label.marked"),
                      String.valueOf(columnCount),
                      invert ? MessageManager
                              .getString("label.not_containing")
      }
      else
      {
-       avcg.setStatus(MessageManager.formatMessage(
-               "label.no_feature_of_type_found",
-               new String[] { featureType }));
+       avcg.setStatus(MessageManager
+               .formatMessage("label.no_feature_of_type_found", new String[]
+               { featureType }));
        if (!extendCurrent)
        {
          cs.clear();
    static int findColumnsWithFeature(String featureType,
            SequenceCollectionI sqcol, BitSet bs)
    {
 -    final int startPosition = sqcol.getStartRes() + 1; // converted to base 1
 -    final int endPosition = sqcol.getEndRes() + 1;
 +    final int startColumn = sqcol.getStartRes() + 1; // converted to base 1
 +    final int endColumn = sqcol.getEndRes() + 1;
      List<SequenceI> seqs = sqcol.getSequences();
      int nseq = 0;
      for (SequenceI sq : seqs)
      {
 -      boolean sequenceHasFeature = false;
        if (sq != null)
        {
 -        SequenceFeature[] sfs = sq.getSequenceFeatures();
 -        if (sfs != null)
 -        {
 -          int ist = sq.findIndex(sq.getStart());
 -          int iend = sq.findIndex(sq.getEnd());
 -          if (iend < startPosition || ist > endPosition)
 -          {
 -            // sequence not in region
 -            continue;
 -          }
 -          for (SequenceFeature sf : sfs)
 -          {
 -            // future functionality - featureType == null means mark columns
 -            // containing all displayed features
 -            if (sf != null && (featureType.equals(sf.getType())))
 -            {
 -              // optimisation - could consider 'spos,apos' like cursor argument
 -              // - findIndex wastes time by starting from first character and
 -              // counting
 +        // int ist = sq.findPosition(sqcol.getStartRes());
 +        List<SequenceFeature> sfs = sq.findFeatures(startColumn,
 +                endColumn, featureType);
  
 -              int sfStartCol = sq.findIndex(sf.getBegin());
 -              int sfEndCol = sq.findIndex(sf.getEnd());
 +        if (!sfs.isEmpty())
 +        {
 +          nseq++;
 +        }
  
 -              if (sf.isContactFeature())
 -              {
 -                /*
 -                 * 'contact' feature - check for 'start' or 'end'
 -                 * position within the selected region
 -                 */
 -                if (sfStartCol >= startPosition
 -                        && sfStartCol <= endPosition)
 -                {
 -                  bs.set(sfStartCol - 1);
 -                  sequenceHasFeature = true;
 -                }
 -                if (sfEndCol >= startPosition && sfEndCol <= endPosition)
 -                {
 -                  bs.set(sfEndCol - 1);
 -                  sequenceHasFeature = true;
 -                }
 -                continue;
 -              }
 +        for (SequenceFeature sf : sfs)
 +        {
 +          int sfStartCol = sq.findIndex(sf.getBegin());
 +          int sfEndCol = sq.findIndex(sf.getEnd());
  
 -              /*
 -               * contiguous feature - select feature positions (if any) 
 -               * within the selected region
 -               */
 -              if (sfStartCol > endPosition || sfEndCol < startPosition)
 -              {
 -                // feature is outside selected region
 -                continue;
 -              }
 -              sequenceHasFeature = true;
 -              if (sfStartCol < startPosition)
 -              {
 -                sfStartCol = startPosition;
 -              }
 -              if (sfStartCol < ist)
 -              {
 -                sfStartCol = ist;
 -              }
 -              if (sfEndCol > endPosition)
 -              {
 -                sfEndCol = endPosition;
 -              }
 -              for (; sfStartCol <= sfEndCol; sfStartCol++)
 -              {
 -                bs.set(sfStartCol - 1); // convert to base 0
 -              }
 +          if (sf.isContactFeature())
 +          {
 +            /*
 +             * 'contact' feature - check for 'start' or 'end'
 +             * position within the selected region
 +             */
 +            if (sfStartCol >= startColumn && sfStartCol <= endColumn)
 +            {
 +              bs.set(sfStartCol - 1);
              }
 +            if (sfEndCol >= startColumn && sfEndCol <= endColumn)
 +            {
 +              bs.set(sfEndCol - 1);
 +            }
 +            continue;
            }
 -        }
  
 -        if (sequenceHasFeature)
 -        {
 -          nseq++;
 +          /*
 +           * contiguous feature - select feature positions (if any) 
 +           * within the selected region
 +           */
 +          if (sfStartCol < startColumn)
 +          {
 +            sfStartCol = startColumn;
 +          }
 +          // not sure what the point of this is
 +          // if (sfStartCol < ist)
 +          // {
 +          // sfStartCol = ist;
 +          // }
 +          if (sfEndCol > endColumn)
 +          {
 +            sfEndCol = endColumn;
 +          }
 +          for (; sfStartCol <= sfEndCol; sfStartCol++)
 +          {
 +            bs.set(sfStartCol - 1); // convert to base 0
 +          }
          }
        }
      }
      }
      SequenceI[] oldOrder = al.getSequencesArray();
      AlignmentSorter.sortByFeature(typ, gps, start, stop, al, method);
-     avcg.addHistoryItem(new OrderCommand(methodText, oldOrder, viewport
-             .getAlignment()));
+     avcg.addHistoryItem(new OrderCommand(methodText, oldOrder,
+             viewport.getAlignment()));
      alignPanel.paintAlignment(true);
  
    }
      boolean featuresFile = false;
      try
      {
-       featuresFile = new FeaturesFile(false, file, protocol).parse(viewport
-               .getAlignment().getDataset(), alignPanel.getFeatureRenderer()
-               .getFeatureColours(), false, relaxedIdMatching);
+       featuresFile = new FeaturesFile(false, file, protocol).parse(
+               viewport.getAlignment().getDataset(),
+               alignPanel.getFeatureRenderer().getFeatureColours(), false,
+               relaxedIdMatching);
      } catch (Exception ex)
      {
        ex.printStackTrace();
      }
      // JBPNote this routine could also mark rows, not just columns.
      BitSet bs = new BitSet();
-     SequenceCollectionI sqcol = (viewport.getSelectionGroup() == null || extendCurrent) ? viewport
-             .getAlignment() : viewport.getSelectionGroup();
+     SequenceCollectionI sqcol = (viewport.getSelectionGroup() == null
+             || extendCurrent) ? viewport.getAlignment()
+                     : viewport.getSelectionGroup();
  
      // this could be a lambda... - the remains of the method is boilerplate,
      // except for the different messages for reporting selection.
        {
          viewport.setColumnSelection(cs);
          alignPanel.paintAlignment(true);
-         int columnCount = invert ? (sqcol.getEndRes() - sqcol.getStartRes() + 1)
-                 - bs.cardinality()
+         int columnCount = invert
+                 ? (sqcol.getEndRes() - sqcol.getStartRes() + 1)
+                         - bs.cardinality()
                  : bs.cardinality();
          avcg.setStatus(MessageManager.formatMessage(
-                 "label.view_controller_toggled_marked",
-                 new String[] {
-                     toggle ? MessageManager.getString("label.toggled")
-                             : MessageManager.getString("label.marked"),
+                 "label.view_controller_toggled_marked", new String[]
+                 { toggle ? MessageManager.getString("label.toggled")
+                         : MessageManager.getString("label.marked"),
                      String.valueOf(columnCount),
                      invert ? MessageManager
                              .getString("label.not_containing")
@@@ -90,10 -90,11 +90,11 @@@ public class AlignedCodonFram
          return that.mapping == null;
        }
        // TODO: can simplify by asserting fromSeq is a dataset sequence
-       return (this.fromSeq == that.fromSeq || (this.fromSeq != null
-               && that.fromSeq != null
-               && this.fromSeq.getDatasetSequence() != null && this.fromSeq
-               .getDatasetSequence() == that.fromSeq.getDatasetSequence()))
+       return (this.fromSeq == that.fromSeq
+               || (this.fromSeq != null && that.fromSeq != null
+                       && this.fromSeq.getDatasetSequence() != null
+                       && this.fromSeq.getDatasetSequence() == that.fromSeq
+                               .getDatasetSequence()))
                && this.mapping.equals(that.mapping);
      }
  
  
      SequenceI fromSeq = (dnaseq.getDatasetSequence() == null) ? dnaseq
              : dnaseq.getDatasetSequence();
-     SequenceI toSeq = (aaseq.getDatasetSequence() == null) ? aaseq : aaseq
-             .getDatasetSequence();
+     SequenceI toSeq = (aaseq.getDatasetSequence() == null) ? aaseq
+             : aaseq.getDatasetSequence();
  
      /*
       * if we already hold a mapping between these sequences, just add to it 
    {
      SequenceI targetDs = target.getDatasetSequence() == null ? target
              : target.getDatasetSequence();
-     SequenceI queryDs = query.getDatasetSequence() == null ? query : query
-             .getDatasetSequence();
+     SequenceI queryDs = query.getDatasetSequence() == null ? query
+             : query.getDatasetSequence();
      if (targetDs == null || queryDs == null /*|| dnaToProt == null*/)
      {
        return null;
           * Read off the mapped nucleotides (converting to position base 0)
           */
          codonPos = MappingUtils.flattenRanges(codonPos);
 -        char[] dna = dnaSeq.getSequence();
          int start = dnaSeq.getStart();
 -        result.add(
 -                new char[]
 -                { dna[codonPos[0] - start], dna[codonPos[1] - start],
 -                    dna[codonPos[2] - start] });
 +        char c1 = dnaSeq.getCharAt(codonPos[0] - start);
 +        char c2 = dnaSeq.getCharAt(codonPos[1] - start);
 +        char c3 = dnaSeq.getCharAt(codonPos[2] - start);
 +        result.add(new char[] { c1, c2, c3 });
        }
      }
      return result.isEmpty() ? null : result;
     */
    protected int realiseWith(SequenceI seq, boolean doUpdate)
    {
-     SequenceI ds = seq.getDatasetSequence() != null ? seq
-             .getDatasetSequence() : seq;
+     SequenceI ds = seq.getDatasetSequence() != null
+             ? seq.getDatasetSequence()
+             : seq;
      int count = 0;
  
      /*
      {
        int start = replacement.getStart();
        int end = replacement.getEnd();
-       boolean mappingOverlapsSequence = (mapStart >= start && mapStart <= end)
-               || (mapEnd >= start && mapEnd <= end);
+       boolean mappingOverlapsSequence = (mapStart >= start
+               && mapStart <= end) || (mapEnd >= start && mapEnd <= end);
        if (mappingOverlapsSequence)
        {
          return true;
    {
      SequenceI dssFrom = fromSeq.getDatasetSequence() == null ? fromSeq
              : fromSeq.getDatasetSequence();
-     SequenceI dssTo = toSeq.getDatasetSequence() == null ? toSeq : toSeq
-             .getDatasetSequence();
+     SequenceI dssTo = toSeq.getDatasetSequence() == null ? toSeq
+             : toSeq.getDatasetSequence();
  
      for (SequenceToSequenceMapping mapping : mappings)
      {
@@@ -143,9 -143,8 +143,8 @@@ public class Alignment implements Align
     */
    public static AlignmentI createAlignment(CigarArray compactAlignment)
    {
-     throw new Error(
-             MessageManager
-                     .getString("error.alignment_cigararray_not_implemented"));
+     throw new Error(MessageManager
+             .getString("error.alignment_cigararray_not_implemented"));
      // this(compactAlignment.refCigars);
    }
  
      return AlignmentUtils.getSequencesByName(this);
    }
  
    @Override
    public SequenceI getSequenceAt(int i)
    {
        return;
      }
      // remove annotation very quickly
-     AlignmentAnnotation[] t, todelete = new AlignmentAnnotation[annotations.length], tokeep = new AlignmentAnnotation[annotations.length];
+     AlignmentAnnotation[] t,
+             todelete = new AlignmentAnnotation[annotations.length],
+             tokeep = new AlignmentAnnotation[annotations.length];
      int i, p, k;
      if (gp == null)
      {
        sqname = sq.getName();
        if (sqname.equals(token) // exact match
                || (b && // allow imperfect matches - case varies
-               (sqname.equalsIgnoreCase(token))))
+                       (sqname.equalsIgnoreCase(token))))
        {
          return getSequenceAt(i);
        }
      return -1;
    }
  
    @Override
    public int getHeight()
    {
              }
              if (dbr.getMap().getTo().getDatasetSequence() != null)
              {
-               throw new Error(
-                       "Implementation error: Map.getTo() for dbref " + dbr
-                               + " from " + curDs.getName()
-                               + " is not a dataset sequence.");
+               throw new Error("Implementation error: Map.getTo() for dbref "
+                       + dbr + " from " + curDs.getName()
+                       + " is not a dataset sequence.");
              }
              // we recurse to add all forward references to dataset sequences via
              // DBRefs/etc
        current = getSequenceAt(i);
        // This should really be a sequence method
        ends[i * 2] = current.findIndex(current.getStart());
-       ends[i * 2 + 1] = current.findIndex(current.getStart()
-               + current.getLength());
+       ends[i * 2 + 1] = current
+               .findIndex(current.getStart() + current.getLength());
        boolean hitres = false;
        for (int j = 0, rs = 0, ssiz = current.getLength(); j < ssiz; j++)
        {
    {
      // TODO JAL-1270 needs test coverage
      // currently tested for use in jalview.gui.SequenceFetcher
 -    boolean samegap = toappend.getGapCharacter() == getGapCharacter();
      char oldc = toappend.getGapCharacter();
 +    boolean samegap = oldc == getGapCharacter();
      boolean hashidden = toappend.getHiddenSequences() != null
              && toappend.getHiddenSequences().hiddenSequences != null;
      // get all sequences including any hidden ones
-     List<SequenceI> sqs = (hashidden) ? toappend.getHiddenSequences()
-             .getFullAlignment().getSequences() : toappend.getSequences();
+     List<SequenceI> sqs = (hashidden)
+             ? toappend.getHiddenSequences().getFullAlignment()
+                     .getSequences()
+             : toappend.getSequences();
      if (sqs != null)
      {
        // avoid self append deadlock by
          {
            if (!samegap)
            {
 -            char[] oldseq = addedsq.getSequence();
 -            for (int c = 0; c < oldseq.length; c++)
 -            {
 -              if (oldseq[c] == oldc)
 -              {
 -                oldseq[c] = gapCharacter;
 -              }
 -            }
 +            addedsq.replace(oldc, gapCharacter);
            }
            toappendsq.add(addedsq);
          }
              if (ourval instanceof String)
              {
                // append strings
-               this.setProperty(k, ((String) ourval) + "; "
-                       + ((String) toapprop));
+               this.setProperty(k,
+                       ((String) ourval) + "; " + ((String) toapprop));
              }
              else
              {
      {
        for (AlignmentAnnotation a : alignmentAnnotation)
        {
-         if (a.getCalcId() == calcId
-                 || (a.getCalcId() != null && calcId != null && a
-                         .getCalcId().equals(calcId)))
+         if (a.getCalcId() == calcId || (a.getCalcId() != null
+                 && calcId != null && a.getCalcId().equals(calcId)))
          {
            aa.add(a);
          }
      ArrayList<AlignmentAnnotation> aa = new ArrayList<>();
      for (AlignmentAnnotation ann : getAlignmentAnnotation())
      {
-       if ((calcId == null || (ann.getCalcId() != null && ann.getCalcId()
-               .equals(calcId)))
-               && (seq == null || (ann.sequenceRef != null && ann.sequenceRef == seq))
-               && (label == null || (ann.label != null && ann.label
-                       .equals(label))))
+       if ((calcId == null || (ann.getCalcId() != null
+               && ann.getCalcId().equals(calcId)))
+               && (seq == null || (ann.sequenceRef != null
+                       && ann.sequenceRef == seq))
+               && (label == null
+                       || (ann.label != null && ann.label.equals(label))))
        {
          aa.add(ann);
        }
@@@ -96,13 -96,14 +96,13 @@@ public class AlignmentAnnotatio
     * Updates the _rnasecstr field Determines the positions that base pair and
     * the positions of helices based on secondary structure from a Stockholm file
     * 
 -   * @param RNAannot
 +   * @param rnaAnnotation
     */
 -  private void _updateRnaSecStr(CharSequence RNAannot)
 +  private void _updateRnaSecStr(CharSequence rnaAnnotation)
    {
      try
      {
 -      bps = Rna.getModeleBP(RNAannot);
 -      _rnasecstr = Rna.getBasePairs(bps);
 +      _rnasecstr = Rna.getHelixMap(rnaAnnotation);
        invalidrnastruc = -1;
      } catch (WUSSParseException px)
      {
      {
        return;
      }
 -    Rna.HelixMap(_rnasecstr);
 -    // setRNAStruc(RNAannot);
  
      if (_rnasecstr != null && _rnasecstr.length > 0)
      {
      }
    }
  
 -  // JBPNote: what does this do ?
 -  public void ConcenStru(CharSequence RNAannot) throws WUSSParseException
 -  {
 -    bps = Rna.getModeleBP(RNAannot);
 -  }
 -
    /**
     * Creates a new AlignmentAnnotation object.
     * 
          firstChar = annotations[i].displayCharacter.charAt(0);
          // check to see if it looks like a sequence or is secondary structure
          // labelling.
-         if (annotations[i].secondaryStructure != ' '
-                 && !hasIcons
-                 &&
-                 // Uncomment to only catch case where
-                 // displayCharacter==secondary
-                 // Structure
-                 // to correctly redisplay SS annotation imported from Stockholm,
-                 // exported to JalviewXML and read back in again.
-                 // &&
-                 // annotations[i].displayCharacter.charAt(0)==annotations[i].secondaryStructure
-                 firstChar != ' '
-                 && firstChar != '$'
-                 && firstChar != 0xCE
-                 && firstChar != '('
-                 && firstChar != '['
-                 && firstChar != '>'
-                 && firstChar != '{'
-                 && firstChar != 'A'
-                 && firstChar != 'B'
-                 && firstChar != 'C'
-                 && firstChar != 'D'
-                 && firstChar != 'E'
-                 && firstChar != 'F'
-                 && firstChar != 'G'
-                 && firstChar != 'H'
-                 && firstChar != 'I'
-                 && firstChar != 'J'
-                 && firstChar != 'K'
-                 && firstChar != 'L'
-                 && firstChar != 'M'
-                 && firstChar != 'N'
-                 && firstChar != 'O'
-                 && firstChar != 'P'
-                 && firstChar != 'Q'
-                 && firstChar != 'R'
-                 && firstChar != 'S'
-                 && firstChar != 'T'
-                 && firstChar != 'U'
-                 && firstChar != 'V'
-                 && firstChar != 'W'
-                 && firstChar != 'X'
-                 && firstChar != 'Y'
-                 && firstChar != 'Z'
+         if (annotations[i].secondaryStructure != ' ' && !hasIcons &&
+         // Uncomment to only catch case where
+         // displayCharacter==secondary
+         // Structure
+         // to correctly redisplay SS annotation imported from Stockholm,
+         // exported to JalviewXML and read back in again.
+         // &&
+         // annotations[i].displayCharacter.charAt(0)==annotations[i].secondaryStructure
+                 firstChar != ' ' && firstChar != '$' && firstChar != 0xCE
+                 && firstChar != '(' && firstChar != '[' && firstChar != '>'
+                 && firstChar != '{' && firstChar != 'A' && firstChar != 'B'
+                 && firstChar != 'C' && firstChar != 'D' && firstChar != 'E'
+                 && firstChar != 'F' && firstChar != 'G' && firstChar != 'H'
+                 && firstChar != 'I' && firstChar != 'J' && firstChar != 'K'
+                 && firstChar != 'L' && firstChar != 'M' && firstChar != 'N'
+                 && firstChar != 'O' && firstChar != 'P' && firstChar != 'Q'
+                 && firstChar != 'R' && firstChar != 'S' && firstChar != 'T'
+                 && firstChar != 'U' && firstChar != 'V' && firstChar != 'W'
+                 && firstChar != 'X' && firstChar != 'Y' && firstChar != 'Z'
                  && firstChar != '-'
                  && firstChar < jalview.schemes.ResidueProperties.aaIndex.length)
          {
      {
        return ((index + offset < 0) || (index + offset) >= max
                || annotations[index + offset] == null
-               || (annotations[index + offset].secondaryStructure <= ' ') ? ' '
-               : annotations[index + offset].displayCharacter == null
-                       || annotations[index + offset].displayCharacter
-                               .length() == 0 ? annotations[index + offset].secondaryStructure
-                       : annotations[index + offset].displayCharacter
-                               .charAt(0));
+               || (annotations[index + offset].secondaryStructure <= ' ')
+                       ? ' '
+                       : annotations[index + offset].displayCharacter == null
+                               || annotations[index
+                                       + offset].displayCharacter
+                                               .length() == 0
+                                                       ? annotations[index
+                                                               + offset].secondaryStructure
+                                                       : annotations[index
+                                                               + offset].displayCharacter
+                                                                       .charAt(0));
      }
  
      @Override
  
        for (int i = offset; i < mx; i++)
        {
-         string[i] = (annotations[i] == null || (annotations[i].secondaryStructure <= 32)) ? ' '
-                 : (annotations[i].displayCharacter == null
-                         || annotations[i].displayCharacter.length() == 0 ? annotations[i].secondaryStructure
-                         : annotations[i].displayCharacter.charAt(0));
+         string[i] = (annotations[i] == null
+                 || (annotations[i].secondaryStructure <= 32))
+                         ? ' '
+                         : (annotations[i].displayCharacter == null
+                                 || annotations[i].displayCharacter
+                                         .length() == 0
+                                                 ? annotations[i].secondaryStructure
+                                                 : annotations[i].displayCharacter
+                                                         .charAt(0));
        }
        return new String(string);
      }
      Annotation[] temp = new Annotation[endRes - startRes + 1];
      if (startRes < annotations.length)
      {
-       System.arraycopy(annotations, startRes, temp, 0, endRes - startRes
-               + 1);
+       System.arraycopy(annotations, startRes, temp, 0,
+               endRes - startRes + 1);
      }
      if (sequenceRef != null)
      {
        {
          if (i + 1 < iSize)
          {
-           System.arraycopy(annotations, i + 1, annotations, i, iSize - i
-                   - 1);
+           System.arraycopy(annotations, i + 1, annotations, i,
+                   iSize - i - 1);
          }
          iSize--;
        }
      {
        if (sequenceRef != null)
        {
-         boolean rIsDs = sequenceRef.getDatasetSequence() == null, tIsDs = sequenceI
-                 .getDatasetSequence() == null;
+         boolean rIsDs = sequenceRef.getDatasetSequence() == null,
+                 tIsDs = sequenceI.getDatasetSequence() == null;
          if (sequenceRef != sequenceI
-                 && (rIsDs && !tIsDs && sequenceRef != sequenceI
-                         .getDatasetSequence())
-                 && (!rIsDs && tIsDs && sequenceRef.getDatasetSequence() != sequenceI)
-                 && (!rIsDs && !tIsDs && sequenceRef.getDatasetSequence() != sequenceI
-                         .getDatasetSequence())
+                 && (rIsDs && !tIsDs
+                         && sequenceRef != sequenceI.getDatasetSequence())
+                 && (!rIsDs && tIsDs
+                         && sequenceRef.getDatasetSequence() != sequenceI)
+                 && (!rIsDs && !tIsDs
+                         && sequenceRef.getDatasetSequence() != sequenceI
+                                 .getDatasetSequence())
                  && !sequenceRef.equals(sequenceI))
          {
            // if sequenceRef isn't intersecting with sequenceI
        throw new Error(
                "liftOver currently not implemented for transfer of annotation between different types of seqeunce");
      }
-     boolean mapIsTo = (sp2sq != null) ? (sp2sq.getTo() == sq || sp2sq
-             .getTo() == sq.getDatasetSequence()) : false;
+     boolean mapIsTo = (sp2sq != null)
+             ? (sp2sq.getTo() == sq
+                     || sp2sq.getTo() == sq.getDatasetSequence())
+             : false;
  
      // TODO build a better annotation element map and get rid of annotations[]
      Map<Integer, Annotation> mapForsq = new HashMap<Integer, Annotation>();
        {
          for (Entry<Integer, Annotation> ie : sequenceMapping.entrySet())
          {
-           Integer mpos = Integer.valueOf(mapIsTo ? sp2sq
-                   .getMappedPosition(ie.getKey()) : sp2sq.getPosition(ie
-                   .getKey()));
+           Integer mpos = Integer
+                   .valueOf(mapIsTo ? sp2sq.getMappedPosition(ie.getKey())
+                           : sp2sq.getPosition(ie.getKey()));
            if (mpos >= sq.getStart() && mpos <= sq.getEnd())
            {
              mapForsq.put(mpos, ie.getValue());
         * up to and excluding the target column; if the count is less
         * than 1, the opening bracket is unmatched, so return its match
         */
-       String closer = String.valueOf(Rna
-               .getMatchingClosingParenthesis(symbol));
+       String closer = String
+               .valueOf(Rna.getMatchingClosingParenthesis(symbol));
        String opener = String.valueOf(symbol);
        int count = 0;
        for (int j = col + 1; j < column; j++)
@@@ -70,7 -70,7 +70,7 @@@ public class BinarySequence extends Seq
      int nores = (isNa) ? ResidueProperties.maxNucleotideIndex
              : ResidueProperties.maxProteinIndex;
  
 -    dbinary = new double[getSequence().length * nores];
 +    dbinary = new double[getLength() * nores];
  
      return nores;
    }
@@@ -88,7 -88,7 +88,7 @@@
    {
      int nores = initMatrixGetNoRes();
      final int[] sindex = getSymbolmatrix();
 -    for (int i = 0; i < getSequence().length; i++)
 +    for (int i = 0; i < getLength(); i++)
      {
        int aanum = nores - 1;
  
    {
      if (isNa != smtrx.isDNA())
      {
-       throw new InvalidSequenceTypeException("matrix "
-               + smtrx.getClass().getCanonicalName()
-               + " is not a valid matrix for "
-               + (isNa ? "nucleotide" : "protein") + "sequences");
+       throw new InvalidSequenceTypeException(
+               "matrix " + smtrx.getClass().getCanonicalName()
+                       + " is not a valid matrix for "
+                       + (isNa ? "nucleotide" : "protein") + "sequences");
      }
      matrixEncode(smtrx.isDNA() ? ResidueProperties.nucleotideIndex
              : ResidueProperties.aaIndex, smtrx.getMatrix());
    {
      int nores = initMatrixGetNoRes();
  
 -    for (int i = 0, iSize = getSequence().length; i < iSize; i++)
 +    for (int i = 0, iSize = getLength(); i < iSize; i++)
      {
        int aanum = nores - 1;
  
@@@ -46,7 -46,7 +46,7 @@@ public class Mappin
      /*
       * The characters of the aligned sequence e.g. "-cGT-ACgTG-"
       */
 -    private final char[] alignedSeq;
 +    private final SequenceI alignedSeq;
  
      /*
       * the sequence start residue
       */
      public AlignedCodonIterator(SequenceI seq, char gapChar)
      {
 -      this.alignedSeq = seq.getSequence();
 +      this.alignedSeq = seq;
        this.start = seq.getStart();
        this.gap = gapChar;
        fromRanges = map.getFromRanges().iterator();
        if (toPosition <= currentToRange[1])
        {
          SequenceI seq = Mapping.this.to;
 -        char pep = seq.getSequence()[toPosition - seq.getStart()];
 +        char pep = seq.getCharAt(toPosition - seq.getStart());
          toPosition++;
          return String.valueOf(pep);
        }
        if (!toRanges.hasNext())
        {
-         throw new NoSuchElementException("Ran out of peptide at position "
-                 + toPosition);
+         throw new NoSuchElementException(
+                 "Ran out of peptide at position " + toPosition);
        }
        currentToRange = toRanges.next();
        toPosition = currentToRange[0];
         * allow for offset e.g. treat pos 8 as 2 if sequence starts at 7
         */
        int truePos = sequencePos - (start - 1);
 -      while (alignedBases < truePos && alignedColumn < alignedSeq.length)
 +      int length = alignedSeq.getLength();
 +      while (alignedBases < truePos && alignedColumn < length)
        {
 -        char c = alignedSeq[alignedColumn++];
 +        char c = alignedSeq.getCharAt(alignedColumn++);
          if (c != gap && !Comparison.isGap(c))
          {
            alignedBases++;
          SequenceFeature[] vf = new SequenceFeature[frange.length / 2];
          for (int i = 0, v = 0; i < frange.length; i += 2, v++)
          {
 -          vf[v] = new SequenceFeature(f);
 -          vf[v].setBegin(frange[i]);
 -          vf[v].setEnd(frange[i + 1]);
 +          vf[v] = new SequenceFeature(f, frange[i], frange[i + 1],
 +                  f.getFeatureGroup(), f.getScore());
            if (frange.length > 2)
            {
              vf[v].setDescription(f.getDescription() + "\nPart " + (v + 1));
          return vf;
        }
      }
 -    if (false) // else
 -    {
 -      int[] word = getWord(f.getBegin());
 -      if (word[0] < word[1])
 -      {
 -        f.setBegin(word[0]);
 -      }
 -      else
 -      {
 -        f.setBegin(word[1]);
 -      }
 -      word = getWord(f.getEnd());
 -      if (word[0] > word[1])
 -      {
 -        f.setEnd(word[0]);
 -      }
 -      else
 -      {
 -        f.setEnd(word[1]);
 -      }
 -    }
 +
      // give up and just return the feature.
      return new SequenceFeature[] { f };
    }
          to[f * 2] = r[0];
          to[f * 2 + 1] = r[1];
        }
-       copy.setMap(new MapList(from, to, map.getFromRatio(), map
-               .getToRatio()));
+       copy.setMap(
+               new MapList(from, to, map.getFromRatio(), map.getToRatio()));
      }
      return copy;
    }
     * @param gapChar
     * @return
     */
-   public Iterator<AlignedCodon> getCodonIterator(SequenceI seq, char gapChar)
+   public Iterator<AlignedCodon> getCodonIterator(SequenceI seq,
+           char gapChar)
    {
      return new AlignedCodonIterator(seq, gapChar);
    }
    @Override
    public String toString()
    {
-     return String.format("%s %s", this.map.toString(), this.to == null ? ""
-             : this.to.getName());
+     return String.format("%s %s", this.map.toString(),
+             this.to == null ? "" : this.to.getName());
    }
  
    /**
@@@ -34,7 -34,7 +34,7 @@@ import java.util.List
  public class SearchResults implements SearchResultsI
  {
  
 -  private List<SearchResultMatchI> matches = new ArrayList<SearchResultMatchI>();
 +  private List<SearchResultMatchI> matches = new ArrayList<>();
  
    /**
     * One match consists of a sequence reference, start and end positions.
     */
    public class Match implements SearchResultMatchI
    {
 -    SequenceI sequence;
 +    final SequenceI sequence;
  
      /**
       * Start position of match in sequence (base 1)
       */
 -    int start;
 +    final int start;
  
      /**
       * End position (inclusive) (base 1)
       */
 -    int end;
 +    final int end;
  
      /**
       * create a Match on a range of sequence. Match always holds region in
        return sb.toString();
      }
  
 -    public void setSequence(SequenceI seq)
 -    {
 -      this.sequence = seq;
 -    }
 -
      /**
       * Hashcode is the hashcode of the matched sequence plus a hash of start and
       * end positions. Match objects that pass the test for equals are guaranteed
          return false;
        }
        SearchResultMatchI m = (SearchResultMatchI) obj;
-       return (sequence == m.getSequence() && start == m.getStart() && end == m
-               .getEnd());
+       return (sequence == m.getSequence() && start == m.getStart()
+               && end == m.getEnd());
      }
    }
  
        m = (Match) _m;
  
        mfound = false;
 -      if (m.sequence == sequence)
 -      {
 -        mfound = true;
 -        // locate aligned position
 -        matchStart = sequence.findIndex(m.start) - 1;
 -        matchEnd = sequence.findIndex(m.end) - 1;
 -      }
 -      else if (m.sequence == sequence.getDatasetSequence())
 +      if (m.sequence == sequence
 +              || m.sequence == sequence.getDatasetSequence())
        {
          mfound = true;
 -        // locate region in local context
          matchStart = sequence.findIndex(m.start) - 1;
 -        matchEnd = sequence.findIndex(m.end) - 1;
 +        matchEnd = m.start == m.end ? matchStart : sequence
 +                .findIndex(m.end) - 1;
        }
 +
        if (mfound)
        {
          if (matchStart <= end && matchEnd >= start)
          else
          {
            // debug
-           // System.err.println("Outwith bounds!" + matchStart+">"+end +"  or "
+           // System.err.println("Outwith bounds!" + matchStart+">"+end +" or "
            // + matchEnd+"<"+start);
          }
        }
      SearchResultsI sr = (SearchResultsI) obj;
      return matches.equals(sr.getResults());
    }
 +
 +  @Override
 +  public void addSearchResults(SearchResultsI toAdd)
 +  {
 +    matches.addAll(toAdd.getResults());
 +  }
  }
@@@ -22,8 -22,6 +22,8 @@@ package jalview.datamodel
  
  import jalview.analysis.AlignSeq;
  import jalview.api.DBRefEntryI;
 +import jalview.datamodel.features.SequenceFeatures;
 +import jalview.datamodel.features.SequenceFeaturesI;
  import jalview.util.Comparison;
  import jalview.util.DBRefUtils;
  import jalview.util.MapList;
@@@ -35,11 -33,8 +35,11 @@@ import java.util.BitSet
  import java.util.Collections;
  import java.util.Enumeration;
  import java.util.List;
 +import java.util.ListIterator;
  import java.util.Vector;
  
 +import com.stevesoft.pat.Regex;
 +
  import fr.orsay.lri.varna.models.rna.RNA;
  
  /**
   */
  public class Sequence extends ASequence implements SequenceI
  {
 +  private static final Regex limitrx = new Regex(
 +          "[/][0-9]{1,}[-][0-9]{1,}$");
 +
 +  private static final Regex endrx = new Regex("[0-9]{1,}$");
 +
    SequenceI datasetSequence;
  
    String name;
     */
    int index = -1;
  
 -  /**
 -   * array of sequence features - may not be null for a valid sequence object
 +  private SequenceFeatures sequenceFeatureStore;
 +
 +  /*
 +   * A cursor holding the approximate current view position to the sequence,
 +   * as determined by findIndex or findPosition or findPositions.
 +   * Using a cursor as a hint allows these methods to be more performant for
 +   * large sequences.
 +   */
 +  private SequenceCursor cursor;
 +
 +  /*
 +   * A number that should be incremented whenever the sequence is edited.
 +   * If the value matches the cursor token, then we can trust the cursor,
 +   * if not then it should be recomputed. 
     */
 -  public SequenceFeature[] sequenceFeatures;
 +  private int changeCount;
  
    /**
     * Creates a new Sequence object.
     */
    public Sequence(String name, String sequence, int start, int end)
    {
 +    this();
      initSeqAndName(name, sequence.toCharArray(), start, end);
    }
  
    public Sequence(String name, char[] sequence, int start, int end)
    {
 +    this();
      initSeqAndName(name, sequence, start, end);
    }
  
      checkValidRange();
    }
  
 -  com.stevesoft.pat.Regex limitrx = new com.stevesoft.pat.Regex(
 -          "[/][0-9]{1,}[-][0-9]{1,}$");
 -
 -  com.stevesoft.pat.Regex endrx = new com.stevesoft.pat.Regex("[0-9]{1,}$");
 -
    void parseId()
    {
      if (name == null)
      {
-       System.err
-               .println("POSSIBLE IMPLEMENTATION ERROR: null sequence name passed to constructor.");
+       System.err.println(
+               "POSSIBLE IMPLEMENTATION ERROR: null sequence name passed to constructor.");
        name = "";
      }
      // Does sequence have the /start-end signature?
    }
  
    /**
 +   * default constructor
 +   */
 +  private Sequence()
 +  {
 +    sequenceFeatureStore = new SequenceFeatures();
 +  }
 +
 +  /**
     * Creates a new Sequence object.
     * 
     * @param name
     */
    public Sequence(SequenceI seq, AlignmentAnnotation[] alAnnotation)
    {
 +    this();
      initSeqFrom(seq, alAnnotation);
 -
    }
  
    /**
    protected void initSeqFrom(SequenceI seq,
            AlignmentAnnotation[] alAnnotation)
    {
 -    {
 -      char[] oseq = seq.getSequence();
 -      initSeqAndName(seq.getName(), Arrays.copyOf(oseq, oseq.length),
 -              seq.getStart(), seq.getEnd());
 -    }
 +    char[] oseq = seq.getSequence(); // returns a copy of the array
 +    initSeqAndName(seq.getName(), oseq, seq.getStart(), seq.getEnd());
 +
      description = seq.getDescription();
      if (seq != datasetSequence)
      {
        setDatasetSequence(seq.getDatasetSequence());
      }
 -    if (datasetSequence == null && seq.getDBRefs() != null)
 +    
 +    /*
 +     * only copy DBRefs and seqfeatures if we really are a dataset sequence
 +     */
 +    if (datasetSequence == null)
      {
 -      // only copy DBRefs and seqfeatures if we really are a dataset sequence
 -      DBRefEntry[] dbr = seq.getDBRefs();
 -      for (int i = 0; i < dbr.length; i++)
 -      {
 -        addDBRef(new DBRefEntry(dbr[i]));
 -      }
 -      if (seq.getSequenceFeatures() != null)
 +      if (seq.getDBRefs() != null)
        {
 -        SequenceFeature[] sf = seq.getSequenceFeatures();
 -        for (int i = 0; i < sf.length; i++)
 +        DBRefEntry[] dbr = seq.getDBRefs();
 +        for (int i = 0; i < dbr.length; i++)
          {
 -          addSequenceFeature(new SequenceFeature(sf[i]));
 +          addDBRef(new DBRefEntry(dbr[i]));
          }
        }
 +
 +      /*
 +       * make copies of any sequence features
 +       */
 +      for (SequenceFeature sf : seq.getSequenceFeatures())
 +      {
 +        addSequenceFeature(new SequenceFeature(sf));
 +      }
      }
 +
      if (seq.getAnnotation() != null)
      {
        AlignmentAnnotation[] sqann = seq.getAnnotation();
    }
  
    @Override
 -  public void setSequenceFeatures(SequenceFeature[] features)
 +  public void setSequenceFeatures(List<SequenceFeature> features)
    {
 -    if (datasetSequence == null)
 -    {
 -      sequenceFeatures = features;
 -    }
 -    else
 +    if (datasetSequence != null)
      {
 -      if (datasetSequence.getSequenceFeatures() != features
 -              && datasetSequence.getSequenceFeatures() != null
 -              && datasetSequence.getSequenceFeatures().length > 0)
 -      {
 -        new Exception(
 -                "Warning: JAL-2046 side effect ? Possible implementation error: overwriting dataset sequence features by setting sequence features on alignment")
 -                        .printStackTrace();
 -      }
        datasetSequence.setSequenceFeatures(features);
 +      return;
      }
 +    sequenceFeatureStore = new SequenceFeatures(features);
    }
  
    @Override
    public synchronized boolean addSequenceFeature(SequenceFeature sf)
    {
 -    if (sequenceFeatures == null && datasetSequence != null)
 -    {
 -      return datasetSequence.addSequenceFeature(sf);
 -    }
 -    if (sequenceFeatures == null)
 +    if (sf.getType() == null)
      {
 -      sequenceFeatures = new SequenceFeature[0];
 +      System.err.println("SequenceFeature type may not be null: "
 +              + sf.toString());
 +      return false;
      }
  
 -    for (int i = 0; i < sequenceFeatures.length; i++)
 +    if (datasetSequence != null)
      {
 -      if (sequenceFeatures[i].equals(sf))
 -      {
 -        return false;
 -      }
 +      return datasetSequence.addSequenceFeature(sf);
      }
  
 -    SequenceFeature[] temp = new SequenceFeature[sequenceFeatures.length
 -            + 1];
 -    System.arraycopy(sequenceFeatures, 0, temp, 0, sequenceFeatures.length);
 -    temp[sequenceFeatures.length] = sf;
 -
 -    sequenceFeatures = temp;
 -    return true;
 +    return sequenceFeatureStore.add(sf);
    }
  
    @Override
    public void deleteFeature(SequenceFeature sf)
    {
 -    if (sequenceFeatures == null)
 -    {
 -      if (datasetSequence != null)
 -      {
 -        datasetSequence.deleteFeature(sf);
 -      }
 -      return;
 -    }
 -
 -    int index = 0;
 -    for (index = 0; index < sequenceFeatures.length; index++)
 -    {
 -      if (sequenceFeatures[index].equals(sf))
 -      {
 -        break;
 -      }
 -    }
 -
 -    if (index == sequenceFeatures.length)
 -    {
 -      return;
 -    }
 -
 -    int sfLength = sequenceFeatures.length;
 -    if (sfLength < 2)
 +    if (datasetSequence != null)
      {
 -      sequenceFeatures = null;
 +      datasetSequence.deleteFeature(sf);
      }
      else
      {
 -      SequenceFeature[] temp = new SequenceFeature[sfLength - 1];
 -      System.arraycopy(sequenceFeatures, 0, temp, 0, index);
 -
 -      if (index < sfLength)
 -      {
 -        System.arraycopy(sequenceFeatures, index + 1, temp, index,
 -                sequenceFeatures.length - index - 1);
 -      }
 -
 -      sequenceFeatures = temp;
 +      sequenceFeatureStore.delete(sf);
      }
    }
  
    /**
 -   * Returns the sequence features (if any), looking first on the sequence, then
 -   * on its dataset sequence, and so on until a non-null value is found (or
 -   * none). This supports retrieval of sequence features stored on the sequence
 -   * (as in the applet) or on the dataset sequence (as in the Desktop version).
 +   * {@inheritDoc}
     * 
     * @return
     */
    @Override
 -  public SequenceFeature[] getSequenceFeatures()
 +  public List<SequenceFeature> getSequenceFeatures()
    {
 -    SequenceFeature[] features = sequenceFeatures;
 -
 -    SequenceI seq = this;
 -    int count = 0; // failsafe against loop in sequence.datasetsequence...
 -    while (features == null && seq.getDatasetSequence() != null
 -            && count++ < 10)
 +    if (datasetSequence != null)
      {
 -      seq = seq.getDatasetSequence();
 -      features = ((Sequence) seq).sequenceFeatures;
 +      return datasetSequence.getSequenceFeatures();
      }
 -    return features;
 +    return sequenceFeatureStore.getAllFeatures();
 +  }
 +
 +  @Override
 +  public SequenceFeaturesI getFeatures()
 +  {
 +    return datasetSequence != null ? datasetSequence.getFeatures()
 +            : sequenceFeatureStore;
    }
  
    @Override
    {
      this.sequence = seq.toCharArray();
      checkValidRange();
 +    sequenceChanged();
    }
  
    @Override
    @Override
    public char[] getSequence()
    {
 -    return sequence;
 +    // return sequence;
 +    return sequence == null ? null : Arrays.copyOf(sequence,
 +            sequence.length);
    }
  
    /*
      return this.description;
    }
  
 -  /*
 -   * (non-Javadoc)
 -   * 
 -   * @see jalview.datamodel.SequenceI#findIndex(int)
 +  /**
 +   * {@inheritDoc}
     */
    @Override
    public int findIndex(int pos)
    {
 -    // returns the alignment position for a residue
 +    /*
 +     * use a valid, hopefully nearby, cursor if available
 +     */
 +    if (isValidCursor(cursor))
 +    {
 +      return findIndex(pos, cursor);
 +    }
 +
      int j = start;
      int i = 0;
 -    // Rely on end being at least as long as the length of the sequence.
 +    int startColumn = 0;
 +
 +    /*
 +     * traverse sequence from the start counting gaps; make a note of
 +     * the column of the first residue to save in the cursor
 +     */
      while ((i < sequence.length) && (j <= end) && (j <= pos))
      {
 -      if (!jalview.util.Comparison.isGap(sequence[i]))
 +      if (!Comparison.isGap(sequence[i]))
        {
 +        if (j == start)
 +        {
 +          startColumn = i;
 +        }
          j++;
        }
        i++;
      }
  
 -    if ((j == end) && (j < pos))
 +    if (j == end && j < pos)
      {
        return end + 1;
      }
 -    else
 +
 +    updateCursor(pos, i, startColumn);
 +    return i;
 +  }
 +
 +  /**
 +   * Updates the cursor to the latest found residue and column position
 +   * 
 +   * @param residuePos
 +   *          (start..)
 +   * @param column
 +   *          (1..)
 +   * @param startColumn
 +   *          column position of the first sequence residue
 +   */
 +  protected void updateCursor(int residuePos, int column, int startColumn)
 +  {
 +    /*
 +     * preserve end residue column provided cursor was valid
 +     */
 +    int endColumn = isValidCursor(cursor) ? cursor.lastColumnPosition : 0;
 +    if (residuePos == this.end)
      {
 -      return i;
 +      endColumn = column;
      }
 +
 +    cursor = new SequenceCursor(this, residuePos, column, startColumn,
 +            endColumn, this.changeCount);
    }
  
 +  /**
 +   * Answers the aligned column position (1..) for the given residue position
 +   * (start..) given a 'hint' of a residue/column location in the neighbourhood.
 +   * The hint may be left of, at, or to the right of the required position.
 +   * 
 +   * @param pos
 +   * @param curs
 +   * @return
 +   */
 +  protected int findIndex(int pos, SequenceCursor curs)
 +  {
 +    if (!isValidCursor(curs))
 +    {
 +      /*
 +       * wrong or invalidated cursor, compute de novo
 +       */
 +      return findIndex(pos);
 +    }
 +
 +    if (curs.residuePosition == pos)
 +    {
 +      return curs.columnPosition;
 +    }
 +
 +    /*
 +     * move left or right to find pos from hint.position
 +     */
 +    int col = curs.columnPosition - 1; // convert from base 1 to 0-based array
 +                                       // index
 +    int newPos = curs.residuePosition;
 +    int delta = newPos > pos ? -1 : 1;
 +
 +    while (newPos != pos)
 +    {
 +      col += delta; // shift one column left or right
 +      if (col < 0 || col == sequence.length)
 +      {
 +        break;
 +      }
 +      if (!Comparison.isGap(sequence[col]))
 +      {
 +        newPos += delta;
 +      }
 +    }
 +
 +    col++; // convert back to base 1
 +    updateCursor(pos, col, curs.firstColumnPosition);
 +
 +    return col;
 +  }
 +
 +  /**
 +   * {@inheritDoc}
 +   */
    @Override
 -  public int findPosition(int i)
 +  public int findPosition(final int column)
    {
 +    /*
 +     * use a valid, hopefully nearby, cursor if available
 +     */
 +    if (isValidCursor(cursor))
 +    {
 +      return findPosition(column + 1, cursor);
 +    }
 +    
 +    // TODO recode this more naturally i.e. count residues only
 +    // as they are found, not 'in anticipation'
 +
 +    /*
 +     * traverse the sequence counting gaps; note the column position
 +     * of the first residue, to save in the cursor
 +     */
 +    int firstResidueColumn = 0;
 +    int lastPosFound = 0;
 +    int lastPosFoundColumn = 0;
 +    int seqlen = sequence.length;
 +
 +    if (seqlen > 0 && !Comparison.isGap(sequence[0]))
 +    {
 +      lastPosFound = start;
 +      lastPosFoundColumn = 0;
 +    }
 +
      int j = 0;
      int pos = start;
 -    int seqlen = sequence.length;
 -    while ((j < i) && (j < seqlen))
 +
 +    while (j < column && j < seqlen)
      {
 -      if (!jalview.util.Comparison.isGap(sequence[j]))
 +      if (!Comparison.isGap(sequence[j]))
        {
 +        lastPosFound = pos;
 +        lastPosFoundColumn = j;
 +        if (pos == this.start)
 +        {
 +          firstResidueColumn = j;
 +        }
          pos++;
        }
 -
        j++;
      }
 +    if (j < seqlen && !Comparison.isGap(sequence[j]))
 +    {
 +      lastPosFound = pos;
 +      lastPosFoundColumn = j;
 +      if (pos == this.start)
 +      {
 +        firstResidueColumn = j;
 +      }
 +    }
 +
 +    /*
 +     * update the cursor to the last residue position found (if any)
 +     * (converting column position to base 1)
 +     */
 +    if (lastPosFound != 0)
 +    {
 +      updateCursor(lastPosFound, lastPosFoundColumn + 1,
 +              firstResidueColumn + 1);
 +    }
  
      return pos;
    }
  
    /**
 +   * Answers true if the given cursor is not null, is for this sequence object,
 +   * and has a token value that matches this object's changeCount, else false.
 +   * This allows us to ignore a cursor as 'stale' if the sequence has been
 +   * modified since the cursor was created.
 +   * 
 +   * @param curs
 +   * @return
 +   */
 +  protected boolean isValidCursor(SequenceCursor curs)
 +  {
 +    if (curs == null || curs.sequence != this || curs.token != changeCount)
 +    {
 +      return false;
 +    }
 +    /*
 +     * sanity check against range
 +     */
 +    if (curs.columnPosition < 0 || curs.columnPosition > sequence.length)
 +    {
 +      return false;
 +    }
 +    if (curs.residuePosition < start || curs.residuePosition > end)
 +    {
 +      return false;
 +    }
 +    return true;
 +  }
 +
 +  /**
 +   * Answers the sequence position (start..) for the given aligned column
 +   * position (1..), given a hint of a cursor in the neighbourhood. The cursor
 +   * may lie left of, at, or to the right of the column position.
 +   * 
 +   * @param col
 +   * @param curs
 +   * @return
 +   */
 +  protected int findPosition(final int col, SequenceCursor curs)
 +  {
 +    if (!isValidCursor(curs))
 +    {
 +      /*
 +       * wrong or invalidated cursor, compute de novo
 +       */
 +      return findPosition(col - 1);// ugh back to base 0
 +    }
 +
 +    if (curs.columnPosition == col)
 +    {
 +      cursor = curs; // in case this method becomes public
 +      return curs.residuePosition; // easy case :-)
 +    }
 +
 +    if (curs.lastColumnPosition > 0 && curs.lastColumnPosition < col)
 +    {
 +      /*
 +       * sequence lies entirely to the left of col
 +       * - return last residue + 1
 +       */
 +      return end + 1;
 +    }
 +
 +    if (curs.firstColumnPosition > 0 && curs.firstColumnPosition > col)
 +    {
 +      /*
 +       * sequence lies entirely to the right of col
 +       * - return first residue
 +       */
 +      return start;
 +    }
 +
 +    // todo could choose closest to col out of column,
 +    // firstColumnPosition, lastColumnPosition as a start point
 +
 +    /*
 +     * move left or right to find pos from cursor position
 +     */
 +    int firstResidueColumn = curs.firstColumnPosition;
 +    int column = curs.columnPosition - 1; // to base 0
 +    int newPos = curs.residuePosition;
 +    int delta = curs.columnPosition > col ? -1 : 1;
 +    boolean gapped = false;
 +    int lastFoundPosition = curs.residuePosition;
 +    int lastFoundPositionColumn = curs.columnPosition;
 +
 +    while (column != col - 1)
 +    {
 +      column += delta; // shift one column left or right
 +      if (column < 0 || column == sequence.length)
 +      {
 +        break;
 +      }
 +      gapped = Comparison.isGap(sequence[column]);
 +      if (!gapped)
 +      {
 +        newPos += delta;
 +        lastFoundPosition = newPos;
 +        lastFoundPositionColumn = column + 1;
 +        if (lastFoundPosition == this.start)
 +        {
 +          firstResidueColumn = column + 1;
 +        }
 +      }
 +    }
 +
 +    if (cursor == null || lastFoundPosition != cursor.residuePosition)
 +    {
 +      updateCursor(lastFoundPosition, lastFoundPositionColumn,
 +              firstResidueColumn);
 +    }
 +
 +    /*
 +     * hack to give position to the right if on a gap
 +     * or beyond the length of the sequence (see JAL-2562)
 +     */
 +    if (delta > 0 && (gapped || column >= sequence.length))
 +    {
 +      newPos++;
 +    }
 +
 +    return newPos;
 +  }
 +
 +  /**
 +   * {@inheritDoc}
 +   */
 +  @Override
 +  public Range findPositions(int fromColumn, int toColumn)
 +  {
 +    if (toColumn < fromColumn || fromColumn < 1)
 +    {
 +      return null;
 +    }
 +
 +    /*
 +     * find the first non-gapped position, if any
 +     */
 +    int firstPosition = 0;
 +    int col = fromColumn - 1;
 +    int length = sequence.length;
 +    while (col < length && col < toColumn)
 +    {
 +      if (!Comparison.isGap(sequence[col]))
 +      {
 +        firstPosition = findPosition(col++);
 +        break;
 +      }
 +      col++;
 +    }
 +
 +    if (firstPosition == 0)
 +    {
 +      return null;
 +    }
 +
 +    /*
 +     * find the last non-gapped position
 +     */
 +    int lastPosition = firstPosition;
 +    while (col < length && col < toColumn)
 +    {
 +      if (!Comparison.isGap(sequence[col++]))
 +      {
 +        lastPosition++;
 +      }
 +    }
 +
 +    return new Range(firstPosition, lastPosition);
 +  }
 +
 +  /**
     * Returns an int array where indices correspond to each residue in the
     * sequence and the element value gives its position in the alignment
     * 
      start = newstart;
      end = newend;
      sequence = tmp;
 +    sequenceChanged();
    }
  
    @Override
      }
  
      sequence = tmp;
 +    sequenceChanged();
    }
  
    @Override
    @Override
    public AlignmentAnnotation[] getAnnotation()
    {
-     return annotation == null ? null : annotation
-             .toArray(new AlignmentAnnotation[annotation.size()]);
+     return annotation == null ? null
+             : annotation
+                     .toArray(new AlignmentAnnotation[annotation.size()]);
    }
  
    @Override
  
    private boolean _isNa;
  
 -  private long _seqhash = 0;
 +  private int _seqhash = 0;
  
    /**
     * Answers false if the sequence is more than 85% nucleotide (ACGTU), else
    {
      if (datasetSequence == null)
      {
-       Sequence dsseq = new Sequence(getName(), AlignSeq.extractGaps(
-               jalview.util.Comparison.GapChars, getSequenceAsString()),
+       Sequence dsseq = new Sequence(getName(),
+               AlignSeq.extractGaps(jalview.util.Comparison.GapChars,
+                       getSequenceAsString()),
                getStart(), getEnd());
  
        datasetSequence = dsseq;
  
        dsseq.setDescription(description);
        // move features and database references onto dataset sequence
 -      dsseq.sequenceFeatures = sequenceFeatures;
 -      sequenceFeatures = null;
 +      dsseq.sequenceFeatureStore = sequenceFeatureStore;
 +      sequenceFeatureStore = null;
        dsseq.dbrefs = dbrefs;
        dbrefs = null;
        // TODO: search and replace any references to this sequence with
        return null;
      }
  
 -    Vector subset = new Vector();
 -    Enumeration e = annotation.elements();
 +    Vector<AlignmentAnnotation> subset = new Vector<AlignmentAnnotation>();
 +    Enumeration<AlignmentAnnotation> e = annotation.elements();
      while (e.hasMoreElements())
      {
 -      AlignmentAnnotation ann = (AlignmentAnnotation) e.nextElement();
 +      AlignmentAnnotation ann = e.nextElement();
        if (ann.label != null && ann.label.equals(label))
        {
          subset.addElement(ann);
      e = subset.elements();
      while (e.hasMoreElements())
      {
 -      anns[i++] = (AlignmentAnnotation) e.nextElement();
 +      anns[i++] = e.nextElement();
      }
      subset.removeAllElements();
      return anns;
      if (entry.getSequenceFeatures() != null)
      {
  
 -      SequenceFeature[] sfs = entry.getSequenceFeatures();
 -      for (int si = 0; si < sfs.length; si++)
 +      List<SequenceFeature> sfs = entry.getSequenceFeatures();
 +      for (SequenceFeature feature : sfs)
        {
-         SequenceFeature sf[] = (mp != null) ? mp.locateFeature(feature)
 -        SequenceFeature sf[] = (mp != null) ? mp.locateFeature(sfs[si])
 -                : new SequenceFeature[]
 -                { new SequenceFeature(sfs[si]) };
 -        if (sf != null && sf.length > 0)
++       SequenceFeature sf[] = (mp != null) ? mp.locateFeature(feature)
 +                : new SequenceFeature[] { new SequenceFeature(feature) };
 +        if (sf != null)
          {
            for (int sfi = 0; sfi < sf.length; sfi++)
            {
      // transfer PDB entries
      if (entry.getAllPDBEntries() != null)
      {
 -      Enumeration e = entry.getAllPDBEntries().elements();
 +      Enumeration<PDBEntry> e = entry.getAllPDBEntries().elements();
        while (e.hasMoreElements())
        {
 -        PDBEntry pdb = (PDBEntry) e.nextElement();
 +        PDBEntry pdb = e.nextElement();
          addPDBId(pdb);
        }
      }
            }
          }
          // whilst it looks like it is a primary ref, we also sanity check type
-         if (DBRefUtils.getCanonicalName(DBRefSource.PDB).equals(
-                 DBRefUtils.getCanonicalName(ref.getSource())))
+         if (DBRefUtils.getCanonicalName(DBRefSource.PDB)
+                 .equals(DBRefUtils.getCanonicalName(ref.getSource())))
          {
            // PDB dbrefs imply there should be a PDBEntry associated
            // TODO: tighten PDB dbrefs
      }
    }
  
 +  /**
 +   * {@inheritDoc}
 +   */
 +  @Override
 +  public List<SequenceFeature> findFeatures(int fromColumn, int toColumn,
 +          String... types)
 +  {
 +    int startPos = findPosition(fromColumn - 1); // convert base 1 to base 0
 +    int endPos = fromColumn == toColumn ? startPos
 +            : findPosition(toColumn - 1);
 +
 +    List<SequenceFeature> result = getFeatures().findFeatures(startPos,
 +            endPos, types);
 +
 +    /*
 +     * if end column is gapped, endPos may be to the right, 
 +     * and we may have included adjacent or enclosing features;
 +     * remove any that are not enclosing, non-contact features
 +     */
 +    if (endPos > this.end || Comparison.isGap(sequence[toColumn - 1]))
 +    {
 +      ListIterator<SequenceFeature> it = result.listIterator();
 +      while (it.hasNext())
 +      {
 +        SequenceFeature sf = it.next();
 +        int sfBegin = sf.getBegin();
 +        int sfEnd = sf.getEnd();
 +        int featureStartColumn = findIndex(sfBegin);
 +        if (featureStartColumn > toColumn)
 +        {
 +          it.remove();
 +        }
 +        else if (featureStartColumn < fromColumn)
 +        {
 +          int featureEndColumn = sfEnd == sfBegin ? featureStartColumn
 +                  : findIndex(sfEnd);
 +          if (featureEndColumn < fromColumn)
 +          {
 +            it.remove();
 +          }
 +          else if (featureEndColumn > toColumn && sf.isContactFeature())
 +          {
 +            /*
 +             * remove an enclosing feature if it is a contact feature
 +             */
 +            it.remove();
 +          }
 +        }
 +      }
 +    }
 +
 +    return result;
 +  }
 +
 +  /**
 +   * Invalidates any stale cursors (forcing recalculation) by incrementing the
 +   * token that has to match the one presented by the cursor
 +   */
 +  @Override
 +  public void sequenceChanged()
 +  {
 +    changeCount++;
 +  }
 +
 +  /**
 +   * {@inheritDoc}
 +   */
 +  @Override
 +  public int replace(char c1, char c2)
 +  {
 +    if (c1 == c2)
 +    {
 +      return 0;
 +    }
 +    int count = 0;
 +    synchronized (sequence)
 +    {
 +      for (int c = 0; c < sequence.length; c++)
 +      {
 +        if (sequence[c] == c1)
 +        {
 +          sequence[c] = c2;
 +          count++;
 +        }
 +      }
 +    }
 +    if (count > 0)
 +    {
 +      sequenceChanged();
 +    }
 +
 +    return count;
 +  }
  }
   */
  package jalview.datamodel;
  
 +import jalview.datamodel.features.FeatureLocationI;
 +
  import java.util.HashMap;
  import java.util.Map;
 +import java.util.Map.Entry;
  import java.util.Vector;
  
  /**
   * @author $author$
   * @version $Revision$
   */
 -public class SequenceFeature
 +public class SequenceFeature implements FeatureLocationI
  {
 +  /*
 +   * score value if none is set; preferably Float.Nan, but see
 +   * JAL-2060 and JAL-2554 for a couple of blockers to that
 +   */
 +  private static final float NO_SCORE = 0f;
 +
    private static final String STATUS = "status";
  
    private static final String STRAND = "STRAND";
     */
    private static final String ATTRIBUTES = "ATTRIBUTES";
  
 -  public int begin;
 +  /*
 +   * type, begin, end, featureGroup, score and contactFeature are final 
 +   * to ensure that the integrity of SequenceFeatures data store 
 +   * can't be broken by direct update of these fields
 +   */
 +  public final String type;
  
 -  public int end;
 +  public final int begin;
  
 -  public float score;
 +  public final int end;
  
 -  public String type;
 +  public final String featureGroup;
 +
 +  public final float score;
 +
 +  private final boolean contactFeature;
  
    public String description;
  
  
    public Vector<String> links;
  
 -  // Feature group can be set from a features file
 -  // as a group of features between STARTGROUP and ENDGROUP markers
 -  public String featureGroup;
 -
 -  public SequenceFeature()
 -  {
 -  }
 -
    /**
     * Constructs a duplicate feature. Note: Uses makes a shallow copy of the
     * otherDetails map, so the new and original SequenceFeature may reference the
     */
    public SequenceFeature(SequenceFeature cpy)
    {
 -    if (cpy != null)
 -    {
 -      begin = cpy.begin;
 -      end = cpy.end;
 -      score = cpy.score;
 -      if (cpy.type != null)
 -      {
 -        type = new String(cpy.type);
 -      }
 -      if (cpy.description != null)
 -      {
 -        description = new String(cpy.description);
 -      }
 -      if (cpy.featureGroup != null)
 -      {
 -        featureGroup = new String(cpy.featureGroup);
 -      }
 -      if (cpy.otherDetails != null)
 -      {
 -        try
 -        {
 -          otherDetails = (Map<String, Object>) ((HashMap<String, Object>) cpy.otherDetails)
 -                  .clone();
 -        } catch (Exception e)
 -        {
 -          // ignore
 -        }
 -      }
 -      if (cpy.links != null && cpy.links.size() > 0)
 -      {
 -        links = new Vector<String>();
 -        for (int i = 0, iSize = cpy.links.size(); i < iSize; i++)
 -        {
 -          links.addElement(cpy.links.elementAt(i));
 -        }
 -      }
 -    }
 +    this(cpy, cpy.getBegin(), cpy.getEnd(), cpy.getFeatureGroup(), cpy
 +            .getScore());
    }
  
    /**
 -   * Constructor including a Status value
 +   * Constructor
     * 
 -   * @param type
 -   * @param desc
 -   * @param status
 -   * @param begin
 -   * @param end
 -   * @param featureGroup
 +   * @param theType
 +   * @param theDesc
 +   * @param theBegin
 +   * @param theEnd
 +   * @param group
     */
 -  public SequenceFeature(String type, String desc, String status, int begin,
 -          int end, String featureGroup)
 +  public SequenceFeature(String theType, String theDesc, int theBegin,
 +          int theEnd, String group)
    {
 -    this(type, desc, begin, end, featureGroup);
 -    setStatus(status);
 +    this(theType, theDesc, theBegin, theEnd, NO_SCORE, group);
    }
  
    /**
 -   * Constructor
 +   * Constructor including a score value
     * 
 -   * @param type
 -   * @param desc
 -   * @param begin
 -   * @param end
 -   * @param featureGroup
 +   * @param theType
 +   * @param theDesc
 +   * @param theBegin
 +   * @param theEnd
 +   * @param theScore
 +   * @param group
     */
 -  SequenceFeature(String type, String desc, int begin, int end,
 -          String featureGroup)
 +  public SequenceFeature(String theType, String theDesc, int theBegin,
 +          int theEnd, float theScore, String group)
    {
 -    this.type = type;
 -    this.description = desc;
 -    this.begin = begin;
 -    this.end = end;
 -    this.featureGroup = featureGroup;
 +    this.type = theType;
 +    this.description = theDesc;
 +    this.begin = theBegin;
 +    this.end = theEnd;
 +    this.featureGroup = group;
 +    this.score = theScore;
 +
 +    /*
 +     * for now, only "Disulfide/disulphide bond" is treated as a contact feature
 +     */
 +    this.contactFeature = "disulfide bond".equalsIgnoreCase(type)
 +            || "disulphide bond".equalsIgnoreCase(type);
    }
  
    /**
 -   * Constructor including a score value
 +   * A copy constructor that allows the value of final fields to be 'modified'
 +   * 
 +   * @param sf
 +   * @param newType
 +   * @param newBegin
 +   * @param newEnd
 +   * @param newGroup
 +   * @param newScore
 +   */
 +  public SequenceFeature(SequenceFeature sf, String newType, int newBegin,
 +          int newEnd, String newGroup, float newScore)
 +  {
 +    this(newType, sf.getDescription(), newBegin, newEnd, newScore,
 +            newGroup);
 +
 +    if (sf.otherDetails != null)
 +    {
 +      otherDetails = new HashMap<String, Object>();
 +      for (Entry<String, Object> entry : sf.otherDetails.entrySet())
 +      {
 +        otherDetails.put(entry.getKey(), entry.getValue());
 +      }
 +    }
 +    if (sf.links != null && sf.links.size() > 0)
 +    {
 +      links = new Vector<String>();
 +      for (int i = 0, iSize = sf.links.size(); i < iSize; i++)
 +      {
 +        links.addElement(sf.links.elementAt(i));
 +      }
 +    }
 +  }
 +
 +  /**
 +   * A copy constructor that allows the value of final fields to be 'modified'
     * 
 -   * @param type
 -   * @param desc
 -   * @param begin
 -   * @param end
 -   * @param score
 -   * @param featureGroup
 +   * @param sf
 +   * @param newBegin
 +   * @param newEnd
 +   * @param newGroup
 +   * @param newScore
     */
 -  public SequenceFeature(String type, String desc, int begin, int end,
 -          float score, String featureGroup)
 +  public SequenceFeature(SequenceFeature sf, int newBegin, int newEnd,
 +          String newGroup, float newScore)
    {
 -    this(type, desc, begin, end, featureGroup);
 -    this.score = score;
 +    this(sf, sf.getType(), newBegin, newEnd, newGroup, newScore);
    }
  
    /**
        return false;
      }
  
-     if (!(type + description + featureGroup + getPhase()).equals(sf.type
-             + sf.description + sf.featureGroup + sf.getPhase()))
+     if (!(type + description + featureGroup + getPhase()).equals(
+             sf.type + sf.description + sf.featureGroup + sf.getPhase()))
      {
        return false;
      }
     * 
     * @return DOCUMENT ME!
     */
 +  @Override
    public int getBegin()
    {
      return begin;
    }
  
 -  public void setBegin(int start)
 -  {
 -    this.begin = start;
 -  }
 -
    /**
     * DOCUMENT ME!
     * 
     * @return DOCUMENT ME!
     */
 +  @Override
    public int getEnd()
    {
      return end;
    }
  
 -  public void setEnd(int end)
 -  {
 -    this.end = end;
 -  }
 -
    /**
     * DOCUMENT ME!
     * 
      return type;
    }
  
 -  public void setType(String type)
 -  {
 -    this.type = type;
 -  }
 -
    /**
     * DOCUMENT ME!
     * 
      return featureGroup;
    }
  
 -  public void setFeatureGroup(String featureGroup)
 -  {
 -    this.featureGroup = featureGroup;
 -  }
 -
    public void addLink(String labelLink)
    {
      if (links == null)
        links = new Vector<String>();
      }
  
 -    links.insertElementAt(labelLink, 0);
 +    if (!links.contains(labelLink))
 +    {
 +      links.insertElementAt(labelLink, 0);
 +    }
    }
  
    public float getScore()
      return score;
    }
  
 -  public void setScore(float value)
 -  {
 -    score = value;
 -  }
 -
    /**
     * Used for getting values which are not in the basic set. eg STRAND, PHASE
     * for GFF file
      return (String) getValue(ATTRIBUTES);
    }
  
 -  public void setPosition(int pos)
 -  {
 -    begin = pos;
 -    end = pos;
 -  }
 -
 -  public int getPosition()
 -  {
 -    return begin;
 -  }
 -
    /**
     * Return 1 for forward strand ('+' in GFF), -1 for reverse strand ('-' in
     * GFF), and 0 for unknown or not (validly) specified
     * positions, rather than ends of a range. Such features may be visualised or
     * reported differently to features on a range.
     */
 +  @Override
    public boolean isContactFeature()
    {
 -    // TODO abstract one day to a FeatureType class
 -    if ("disulfide bond".equalsIgnoreCase(type)
 -            || "disulphide bond".equalsIgnoreCase(type))
 -    {
 -      return true;
 -    }
 -    return false;
 +    return contactFeature;
 +  }
 +
 +  /**
 +   * Answers true if the sequence has zero start and end position
 +   * 
 +   * @return
 +   */
 +  public boolean isNonPositional()
 +  {
 +    return begin == 0 && end == 0;
    }
  }
@@@ -191,13 -191,15 +191,15 @@@ public class EmblEntr
        return null;
      }
      dna.setDescription(description);
-     DBRefEntry retrievedref = new DBRefEntry(sourceDb,
-             getSequenceVersion(), accession);
+     DBRefEntry retrievedref = new DBRefEntry(sourceDb, getSequenceVersion(),
+             accession);
      dna.addDBRef(retrievedref);
      // add map to indicate the sequence is a valid coordinate frame for the
      // dbref
-     retrievedref.setMap(new Mapping(null, new int[] { 1, dna.getLength() },
-             new int[] { 1, dna.getLength() }, 1, 1));
+     retrievedref
+             .setMap(new Mapping(null, new int[]
+             { 1, dna.getLength() }, new int[] { 1, dna.getLength() }, 1,
+                     1));
  
      /*
       * transform EMBL Database refs to canonical form
    {
      if (sequence == null)
      {
-       System.err.println("No sequence was returned for ENA accession "
-               + accession);
+       System.err.println(
+               "No sequence was returned for ENA accession " + accession);
        return null;
      }
      SequenceI dna = new Sequence(sourceDb + "|" + accession,
     *          helper to match xrefs in already retrieved sequences
     */
    void parseCodingFeature(EmblFeature feature, String sourceDb,
-           SequenceI dna, List<SequenceI> peptides, SequenceIdMatcher matcher)
+           SequenceI dna, List<SequenceI> peptides,
+           SequenceIdMatcher matcher)
    {
      boolean isEmblCdna = sourceDb.equals(DBRefSource.EMBLCDS);
  
          if (qname.equals("translation"))
          {
            // remove all spaces (precompiled String.replaceAll(" ", ""))
-           translation = SPACE_PATTERN.matcher(q.getValues()[0]).replaceAll(
-                   "");
+           translation = SPACE_PATTERN.matcher(q.getValues()[0])
+                   .replaceAll("");
          }
          else if (qname.equals("protein_id"))
          {
              codonStart = Integer.parseInt(q.getValues()[0].trim());
            } catch (NumberFormatException e)
            {
-             System.err.println("Invalid codon_start in XML for "
-                     + accession + ": " + e.getMessage());
+             System.err.println("Invalid codon_start in XML for " + accession
+                     + ": " + e.getMessage());
            }
          }
          else if (qname.equals("product"))
        product = matcher.findIdMatch(proteinId);
        if (product == null)
        {
-         product = new Sequence(proteinId, translation, 1, translationLength);
-         product.setDescription(((proteinName.length() == 0) ? "Protein Product from "
-                 + sourceDb
+         product = new Sequence(proteinId, translation, 1,
+                 translationLength);
+         product.setDescription(((proteinName.length() == 0)
+                 ? "Protein Product from " + sourceDb
                  : proteinName));
          peptides.add(product);
          matcher.add(product);
           * workaround until we handle dna location for CDS sequence
           * e.g. location="X53828.1:60..1058" correctly
           */
-         System.err
-                 .println("Implementation Notice: EMBLCDS records not properly supported yet - Making up the CDNA region of this sequence... may be incorrect ("
+         System.err.println(
+                 "Implementation Notice: EMBLCDS records not properly supported yet - Making up the CDNA region of this sequence... may be incorrect ("
                          + sourceDb + ":" + getAccession() + ")");
 -        if (translationLength
 -                * 3 == (1 - codonStart + dna.getSequence().length))
 +        int dnaLength = dna.getLength();
 +        if (translationLength * 3 == (1 - codonStart + dnaLength))
          {
-           System.err
-                   .println("Not allowing for additional stop codon at end of cDNA fragment... !");
+           System.err.println(
+                   "Not allowing for additional stop codon at end of cDNA fragment... !");
            // this might occur for CDS sequences where no features are marked
            exons = new int[] { dna.getStart() + (codonStart - 1),
                dna.getEnd() };
-           dnaToProteinMapping = new Mapping(product, exons, new int[] { 1,
-               translationLength }, 3, 1);
+           dnaToProteinMapping = new Mapping(product, exons,
+                   new int[]
+                   { 1, translationLength }, 3, 1);
          }
 -        if ((translationLength + 1)
 -                * 3 == (1 - codonStart + dna.getSequence().length))
 +        if ((translationLength + 1) * 3 == (1 - codonStart + dnaLength))
          {
-           System.err
-                   .println("Allowing for additional stop codon at end of cDNA fragment... will probably cause an error in VAMSAs!");
+           System.err.println(
+                   "Allowing for additional stop codon at end of cDNA fragment... will probably cause an error in VAMSAs!");
            exons = new int[] { dna.getStart() + (codonStart - 1),
                dna.getEnd() - 3 };
-           dnaToProteinMapping = new Mapping(product, exons, new int[] { 1,
-               translationLength }, 3, 1);
+           dnaToProteinMapping = new Mapping(product, exons,
+                   new int[]
+                   { 1, translationLength }, 3, 1);
          }
        }
        else
          else
          {
            // final product length truncation check
-           int[] cdsRanges = adjustForProteinLength(translationLength, exons);
-           dnaToProteinMapping = new Mapping(product, cdsRanges, new int[] {
-               1, translationLength }, 3, 1);
+           int[] cdsRanges = adjustForProteinLength(translationLength,
+                   exons);
+           dnaToProteinMapping = new Mapping(product, cdsRanges,
+                   new int[]
+                   { 1, translationLength }, 3, 1);
            if (product != null)
            {
              /*
               * make xref with mapping from protein to EMBL dna
               */
              DBRefEntry proteinToEmblRef = new DBRefEntry(DBRefSource.EMBL,
-                     getSequenceVersion(), proteinId, new Mapping(
-                             dnaToProteinMapping.getMap().getInverse()));
+                     getSequenceVersion(), proteinId,
+                     new Mapping(dnaToProteinMapping.getMap().getInverse()));
              product.addDBRef(proteinToEmblRef);
  
              /*
               * make xref from protein to EMBLCDS; we assume here that the 
               * CDS sequence version is same as dna sequence (?!)
               */
-             MapList proteinToCdsMapList = new MapList(new int[] { 1,
-                 translationLength }, new int[] { 1 + (codonStart - 1),
-                 (codonStart - 1) + 3 * translationLength }, 1, 3);
+             MapList proteinToCdsMapList = new MapList(
+                     new int[]
+                     { 1, translationLength },
+                     new int[]
+                     { 1 + (codonStart - 1),
+                         (codonStart - 1) + 3 * translationLength },
+                     1, 3);
              DBRefEntry proteinToEmblCdsRef = new DBRefEntry(
                      DBRefSource.EMBLCDS, getSequenceVersion(), proteinId,
                      new Mapping(proteinToCdsMapList));
        /*
         * add cds features to dna sequence
         */
 -      for (int xint = 0; exons != null && xint < exons.length; xint += 2)
 +      String cds = feature.getName(); // "CDS"
 +      for (int xint = 0; exons != null && xint < exons.length - 1; xint += 2)
        {
 -        SequenceFeature sf = makeCdsFeature(exons, xint, proteinName,
 -                proteinId, vals, codonStart);
 -        sf.setType(feature.getName()); // "CDS"
 +        int exonStart = exons[xint];
 +        int exonEnd = exons[xint + 1];
 +        int begin = Math.min(exonStart, exonEnd);
 +        int end = Math.max(exonStart, exonEnd);
 +        int exonNumber = xint / 2 + 1;
 +        String desc = String.format("Exon %d for protein '%s' EMBLCDS:%s",
 +                exonNumber, proteinName, proteinId);
 +
 +        SequenceFeature sf = makeCdsFeature(cds, desc, begin, end,
 +                sourceDb, vals);
 +
          sf.setEnaLocation(feature.getLocation());
 -        sf.setFeatureGroup(sourceDb);
 +        boolean forwardStrand = exonStart <= exonEnd;
 +        sf.setStrand(forwardStrand ? "+" : "-");
 +        sf.setPhase(String.valueOf(codonStart - 1));
 +        sf.setValue(FeatureProperties.EXONPOS, exonNumber);
 +        sf.setValue(FeatureProperties.EXONPRODUCT, proteinName);
 +
          dna.addSequenceFeature(sf);
        }
      }
            // Add converse mapping reference
            if (dnaToProteinMapping != null)
            {
-             Mapping pmap = new Mapping(dna, dnaToProteinMapping.getMap()
-                     .getInverse());
+             Mapping pmap = new Mapping(dna,
+                     dnaToProteinMapping.getMap().getInverse());
              pref = new DBRefEntry(sourceDb, getSequenceVersion(),
                      this.getAccession());
              pref.setMap(pmap);
        if (proteinToEmblProteinRef == null)
        {
          // assuming CDSPROTEIN sequence version = dna version (?!)
-         proteinToEmblProteinRef = new DBRefEntry(
-                 DBRefSource.EMBLCDSProduct, getSequenceVersion(), proteinId);
+         proteinToEmblProteinRef = new DBRefEntry(DBRefSource.EMBLCDSProduct,
+                 getSequenceVersion(), proteinId);
        }
        product.addDBRef(proteinToEmblProteinRef);
  
                && dnaToProteinMapping.getTo() != null)
        {
          DBRefEntry dnaToEmblProteinRef = new DBRefEntry(
-                 DBRefSource.EMBLCDSProduct, getSequenceVersion(), proteinId);
+                 DBRefSource.EMBLCDSProduct, getSequenceVersion(),
+                 proteinId);
          dnaToEmblProteinRef.setMap(dnaToProteinMapping);
          dnaToProteinMapping.setMappedFromId(proteinId);
          dna.addDBRef(dnaToEmblProteinRef);
    /**
     * Helper method to construct a SequenceFeature for one cds range
     * 
 -   * @param exons
 -   *          array of cds [start, end, ...] positions
 -   * @param exonStartIndex
 -   *          offset into the exons array
 -   * @param proteinName
 -   * @param proteinAccessionId
 +   * @param type
 +   *          feature type ("CDS")
 +   * @param desc
 +   *          description
 +   * @param begin
 +   *          start position
 +   * @param end
 +   *          end position
 +   * @param group
 +   *          feature group
     * @param vals
     *          map of 'miscellaneous values' for feature
 -   * @param codonStart
 -   *          codon start position for CDS (1/2/3, normally 1)
     * @return
     */
 -  protected SequenceFeature makeCdsFeature(int[] exons, int exonStartIndex,
 -          String proteinName, String proteinAccessionId,
 -          Map<String, String> vals, int codonStart)
 +  protected SequenceFeature makeCdsFeature(String type, String desc,
 +          int begin, int end, String group, Map<String, String> vals)
    {
 -    int exonNumber = exonStartIndex / 2 + 1;
 -    SequenceFeature sf = new SequenceFeature();
 -    sf.setBegin(Math.min(exons[exonStartIndex], exons[exonStartIndex + 1]));
 -    sf.setEnd(Math.max(exons[exonStartIndex], exons[exonStartIndex + 1]));
 -    sf.setDescription(String.format("Exon %d for protein '%s' EMBLCDS:%s",
 -            exonNumber, proteinName, proteinAccessionId));
 -    sf.setPhase(String.valueOf(codonStart - 1));
 -    sf.setStrand(
 -            exons[exonStartIndex] <= exons[exonStartIndex + 1] ? "+" : "-");
 -    sf.setValue(FeatureProperties.EXONPOS, exonNumber);
 -    sf.setValue(FeatureProperties.EXONPRODUCT, proteinName);
 +    SequenceFeature sf = new SequenceFeature(type, desc, begin, end, group);
      if (!vals.isEmpty())
      {
        StringBuilder sb = new StringBuilder();
        return listToArray(ranges);
      } catch (ParseException e)
      {
-       Cache.log.warn(String.format(
-               "Not parsing inexact CDS location %s in ENA %s",
-               feature.location, this.accession));
+       Cache.log.warn(
+               String.format("Not parsing inexact CDS location %s in ENA %s",
+                       feature.location, this.accession));
        return new int[] {};
      }
    }
@@@ -26,7 -26,6 +26,7 @@@ import jalview.datamodel.AlignmentI
  import jalview.datamodel.Sequence;
  import jalview.datamodel.SequenceFeature;
  import jalview.datamodel.SequenceI;
 +import jalview.datamodel.features.SequenceFeatures;
  import jalview.io.gff.SequenceOntologyFactory;
  import jalview.io.gff.SequenceOntologyI;
  import jalview.schemes.FeatureColour;
@@@ -110,7 -109,8 +110,8 @@@ public class EnsemblGene extends Ensemb
     * <li>resolves an external identifier by looking up xref-ed gene ids</li>
     * <li>fetches the gene sequence</li>
     * <li>fetches features on the sequence</li>
-    * <li>identifies "transcript" features whose Parent is the requested gene</li>
+    * <li>identifies "transcript" features whose Parent is the requested
+    * gene</li>
     * <li>fetches the transcript sequence for each transcript</li>
     * <li>makes a mapping from the gene to each transcript</li>
     * <li>copies features from gene to transcript sequences</li>
     */
    protected void clearGeneFeatures(SequenceI gene)
    {
 -    SequenceFeature[] sfs = gene.getSequenceFeatures();
 -    if (sfs != null)
 +    /*
 +     * Note we include NMD_transcript_variant here because it behaves like 
 +     * 'transcript' in Ensembl, although strictly speaking it is not 
 +     * (it is a sub-type of sequence_variant)    
 +     */
 +    String[] soTerms = new String[] {
 +        SequenceOntologyI.NMD_TRANSCRIPT_VARIANT,
 +        SequenceOntologyI.TRANSCRIPT, SequenceOntologyI.EXON,
 +        SequenceOntologyI.CDS };
 +    List<SequenceFeature> sfs = gene.getFeatures().getFeaturesByOntology(
 +            soTerms);
 +    for (SequenceFeature sf : sfs)
      {
 -      SequenceOntologyI so = SequenceOntologyFactory.getInstance();
 -      List<SequenceFeature> filtered = new ArrayList<SequenceFeature>();
 -      for (SequenceFeature sf : sfs)
 -      {
 -        String type = sf.getType();
 -        if (!isTranscript(type) && !so.isA(type, SequenceOntologyI.EXON)
 -                && !so.isA(type, SequenceOntologyI.CDS))
 -        {
 -          filtered.add(sf);
 -        }
 -      }
 -      gene.setSequenceFeatures(
 -              filtered.toArray(new SequenceFeature[filtered.size()]));
 +      gene.deleteFeature(sf);
      }
    }
  
     *          the parent gene sequence, with features
     * @return
     */
-   SequenceI makeTranscript(SequenceFeature transcriptFeature,
-           AlignmentI al, SequenceI gene)
+   SequenceI makeTranscript(SequenceFeature transcriptFeature, AlignmentI al,
+           SequenceI gene)
    {
      String accId = getTranscriptId(transcriptFeature);
      if (accId == null)
      {
        splices = findFeatures(gene, SequenceOntologyI.CDS, parentId);
      }
 +    SequenceFeatures.sortFeatures(splices, true);
  
      int transcriptLength = 0;
      final char[] geneChars = gene.getSequence();
        mappedFrom.add(new int[] { sf.getBegin(), sf.getEnd() });
      }
  
-     Sequence transcript = new Sequence(accId, seqChars, 1, transcriptLength);
+     Sequence transcript = new Sequence(accId, seqChars, 1,
+             transcriptLength);
  
      /*
       * Ensembl has gene name as transcript Name
      mapTo.add(new int[] { 1, transcriptLength });
      MapList mapping = new MapList(mappedFrom, mapTo, 1, 1);
      EnsemblCdna cdna = new EnsemblCdna(getDomain());
 -    cdna.transferFeatures(gene.getSequenceFeatures(),
 +    cdna.transferFeatures(gene.getFeatures().getPositionalFeatures(),
              transcript.getDatasetSequence(), mapping, parentId);
  
      /*
      List<SequenceFeature> transcriptFeatures = new ArrayList<SequenceFeature>();
  
      String parentIdentifier = GENE_PREFIX + accId;
 -    SequenceFeature[] sfs = geneSequence.getSequenceFeatures();
 +    // todo optimise here by transcript type!
 +    List<SequenceFeature> sfs = geneSequence.getFeatures()
 +            .getPositionalFeatures();
  
 -    if (sfs != null)
 +    for (SequenceFeature sf : sfs)
      {
 -      for (SequenceFeature sf : sfs)
 +      if (isTranscript(sf.getType()))
        {
 -        if (isTranscript(sf.getType()))
 +        String parent = (String) sf.getValue(PARENT);
 +        if (parentIdentifier.equals(parent))
          {
 -          String parent = (String) sf.getValue(PARENT);
 -          if (parentIdentifier.equals(parent))
 -          {
 -            transcriptFeatures.add(sf);
 -          }
 +          transcriptFeatures.add(sf);
          }
        }
      }
        @Override
        public boolean isFeatureDisplayed(String type)
        {
-         return (so.isA(type, SequenceOntologyI.EXON) || so.isA(type,
-                 SequenceOntologyI.SEQUENCE_VARIANT));
+         return (so.isA(type, SequenceOntologyI.EXON)
+                 || so.isA(type, SequenceOntologyI.SEQUENCE_VARIANT));
        }
  
        @Override
@@@ -30,7 -30,6 +30,7 @@@ import jalview.datamodel.DBRefSource
  import jalview.datamodel.Mapping;
  import jalview.datamodel.SequenceFeature;
  import jalview.datamodel.SequenceI;
 +import jalview.datamodel.features.SequenceFeatures;
  import jalview.exceptions.JalviewException;
  import jalview.io.FastaFile;
  import jalview.io.FileParse;
@@@ -38,8 -37,8 +38,8 @@@ import jalview.io.gff.SequenceOntologyF
  import jalview.io.gff.SequenceOntologyI;
  import jalview.util.Comparison;
  import jalview.util.DBRefUtils;
 +import jalview.util.IntRangeComparator;
  import jalview.util.MapList;
 -import jalview.util.RangeComparator;
  
  import java.io.IOException;
  import java.net.MalformedURLException;
@@@ -47,9 -46,8 +47,9 @@@ import java.net.URL
  import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.Collections;
 -import java.util.Comparator;
  import java.util.List;
 +import java.util.Map;
 +import java.util.Map.Entry;
  
  /**
   * Base class for Ensembl sequence fetchers
@@@ -139,8 -137,8 +139,8 @@@ public abstract class EnsemblSeqProxy e
  
      // danger: accession separator used as a regex here, a string elsewhere
      // in this case it is ok (it is just a space), but (e.g.) '\' would not be
-     List<String> allIds = Arrays.asList(query
-             .split(getAccessionSeparator()));
+     List<String> allIds = Arrays
+             .asList(query.split(getAccessionSeparator()));
      AlignmentI alignment = null;
      inProgress = true;
  
        }
      } catch (IOException e)
      {
-       System.err.println("Error transferring Ensembl features: "
-               + e.getMessage());
+       System.err.println(
+               "Error transferring Ensembl features: " + e.getMessage());
      }
    }
  
        proteinSeq.createDatasetSequence();
        querySeq.createDatasetSequence();
  
-       MapList mapList = AlignmentUtils
-               .mapCdsToProtein(querySeq, proteinSeq);
+       MapList mapList = AlignmentUtils.mapCdsToProtein(querySeq,
+               proteinSeq);
        if (mapList != null)
        {
          // clunky: ensure Uniprot xref if we have one is on mapped sequence
                  getEnsemblDataVersion(), proteinSeq.getName(), map);
          querySeq.getDatasetSequence().addDBRef(dbr);
          DBRefEntry[] uprots = DBRefUtils.selectRefs(ds.getDBRefs(),
-                 new String[] { DBRefSource.UNIPROT });
+                 new String[]
+                 { DBRefSource.UNIPROT });
          DBRefEntry[] upxrefs = DBRefUtils.selectRefs(querySeq.getDBRefs(),
-                 new String[] { DBRefSource.UNIPROT });
+                 new String[]
+                 { DBRefSource.UNIPROT });
          if (uprots != null)
          {
            for (DBRefEntry up : uprots)
  
                if (upx.size() > 1)
                {
-                 Cache.log
-                         .warn("Implementation issue - multiple uniprot acc on product sequence.");
+                 Cache.log.warn(
+                         "Implementation issue - multiple uniprot acc on product sequence.");
                }
              }
              else
           * copy exon features to protein, compute peptide variants from dna 
           * variants and add as features on the protein sequence ta-da
           */
-         AlignmentUtils
-                 .computeProteinFeatures(querySeq, proteinSeq, mapList);
+         AlignmentUtils.computeProteinFeatures(querySeq, proteinSeq,
+                 mapList);
        }
      } catch (Exception e)
      {
      /*
       * and add a reference to itself
       */
-     DBRefEntry self = new DBRefEntry(getDbSource(),
-             getEnsemblDataVersion(), seq.getName());
+     DBRefEntry self = new DBRefEntry(getDbSource(), getEnsemblDataVersion(),
+             seq.getName());
      seq.addDBRef(self);
    }
  
     * @throws JalviewException
     * @throws IOException
     */
-   protected AlignmentI fetchSequences(List<String> ids, AlignmentI alignment)
-           throws JalviewException, IOException
+   protected AlignmentI fetchSequences(List<String> ids,
+           AlignmentI alignment) throws JalviewException, IOException
    {
      if (!isEnsemblAvailable())
      {
      FastaFile fr = new FastaFile(fp);
      if (fr.hasWarningMessage())
      {
-       System.out.println(String.format(
-               "Warning when retrieving %d ids %s\n%s", ids.size(),
-               ids.toString(), fr.getWarningMessage()));
+       System.out.println(
+               String.format("Warning when retrieving %d ids %s\n%s",
+                       ids.size(), ids.toString(), fr.getWarningMessage()));
      }
      else if (fr.getSeqs().size() != ids.size())
      {
        System.out.println(String.format(
-               "Only retrieved %d sequences for %d query strings", fr
-                       .getSeqs().size(), ids.size()));
+               "Only retrieved %d sequences for %d query strings",
+               fr.getSeqs().size(), ids.size()));
      }
  
      if (fr.getSeqs().size() == 1 && fr.getSeqs().get(0).getLength() == 0)
      urlstring.append("?type=").append(getSourceEnsemblType().getType());
      urlstring.append(("&Accept=text/x-fasta"));
  
 +    Map<String, String> params = getAdditionalParameters();
 +    if (params != null)
 +    {
 +      for (Entry<String, String> entry : params.entrySet())
 +      {
 +        urlstring.append("&").append(entry.getKey()).append("=")
 +                .append(entry.getValue());
 +      }
 +    }
 +
      URL url = new URL(urlstring.toString());
      return url;
    }
  
    /**
 +   * Override this method to add any additional x=y URL parameters needed
 +   * 
 +   * @return
 +   */
 +  protected Map<String, String> getAdditionalParameters()
 +  {
 +    return null;
 +  }
 +
 +  /**
     * A sequence/id POST request currently allows up to 50 queries
     * 
     * @see http://rest.ensembl.org/documentation/info/sequence_id_post
    protected MapList getGenomicRangesFromFeatures(SequenceI sourceSequence,
            String accId, int start)
    {
 -    SequenceFeature[] sfs = sourceSequence.getSequenceFeatures();
 -    if (sfs == null)
 +    // SequenceFeature[] sfs = sourceSequence.getSequenceFeatures();
 +    List<SequenceFeature> sfs = sourceSequence.getFeatures()
 +            .getPositionalFeatures();
 +    if (sfs.isEmpty())
      {
        return null;
      }
          if (directionSet && strand != direction)
          {
            // abort - mix of forward and backward
-           System.err.println("Error: forward and backward strand for "
-                   + accId);
+           System.err.println(
+                   "Error: forward and backward strand for " + accId);
            return null;
          }
          direction = strand;
       * a final sort is needed since Ensembl returns CDS sorted within source
       * (havana / ensembl_havana)
       */
 -    Collections.sort(regions, new RangeComparator(direction == 1));
 +    Collections.sort(regions, direction == 1 ? IntRangeComparator.ASCENDING
 +            : IntRangeComparator.DESCENDING);
  
-     List<int[]> to = Arrays.asList(new int[] { start,
-         start + mappedLength - 1 });
+     List<int[]> to = Arrays
+             .asList(new int[]
+             { start, start + mappedLength - 1 });
  
      return new MapList(regions, to, 1, 1);
    }
  
      if (mappedRange != null)
      {
 -      SequenceFeature copy = new SequenceFeature(sf);
 -      copy.setBegin(Math.min(mappedRange[0], mappedRange[1]));
 -      copy.setEnd(Math.max(mappedRange[0], mappedRange[1]));
 -      if (".".equals(copy.getFeatureGroup()))
 +      String group = sf.getFeatureGroup();
 +      if (".".equals(group))
        {
 -        copy.setFeatureGroup(getDbSource());
 +        group = getDbSource();
        }
 +      int newBegin = Math.min(mappedRange[0], mappedRange[1]);
 +      int newEnd = Math.max(mappedRange[0], mappedRange[1]);
 +      SequenceFeature copy = new SequenceFeature(sf, newBegin, newEnd,
 +              group, sf.getScore());
        targetSequence.addSequenceFeature(copy);
  
        /*
         * for sequence_variant on reverse strand, have to convert the allele
         * values to their complements
         */
-       if (!forwardStrand
-               && SequenceOntologyFactory.getInstance().isA(sf.getType(),
-                       SequenceOntologyI.SEQUENCE_VARIANT))
+       if (!forwardStrand && SequenceOntologyFactory.getInstance()
+               .isA(sf.getType(), SequenceOntologyI.SEQUENCE_VARIANT))
        {
          reverseComplementAlleles(copy);
        }
        return false;
      }
  
 -    // long start = System.currentTimeMillis();
 -    SequenceFeature[] sfs = sourceSequence.getSequenceFeatures();
 +    long start = System.currentTimeMillis();
 +    // SequenceFeature[] sfs = sourceSequence.getSequenceFeatures();
 +    List<SequenceFeature> sfs = sourceSequence.getFeatures()
 +            .getPositionalFeatures();
      MapList mapping = getGenomicRangesFromFeatures(sourceSequence,
              accessionId, targetSequence.getStart());
      if (mapping == null)
  
      boolean result = transferFeatures(sfs, targetSequence, mapping,
              accessionId);
 -    // System.out.println("transferFeatures (" + (sfs.length) + " --> "
 -    // + targetSequence.getSequenceFeatures().length + ") to "
 -    // + targetSequence.getName()
 -    // + " took " + (System.currentTimeMillis() - start) + "ms");
 +    System.out.println("transferFeatures (" + (sfs.size()) + " --> "
 +            + targetSequence.getFeatures().getFeatureCount(true) + ") to "
 +            + targetSequence.getName() + " took "
 +            + (System.currentTimeMillis() - start) + "ms");
      return result;
    }
  
     * converted using the mapping. Features which do not overlap are ignored.
     * Features whose parent is not the specified identifier are also ignored.
     * 
 -   * @param features
 +   * @param sfs
     * @param targetSequence
     * @param mapping
     * @param parentId
     * @return
     */
 -  protected boolean transferFeatures(SequenceFeature[] features,
 +  protected boolean transferFeatures(List<SequenceFeature> sfs,
            SequenceI targetSequence, MapList mapping, String parentId)
    {
      final boolean forwardStrand = mapping.isFromForwardStrand();
       * position descending if reverse strand) so as to add them in
       * 'forwards' order to the target sequence
       */
 -    sortFeatures(features, forwardStrand);
 +    SequenceFeatures.sortFeatures(sfs, forwardStrand);
  
      boolean transferred = false;
 -    for (SequenceFeature sf : features)
 +    for (SequenceFeature sf : sfs)
      {
        if (retainFeature(sf, parentId))
        {
    }
  
    /**
 -   * Sort features by start position ascending (if on forward strand), or end
 -   * position descending (if on reverse strand)
 -   * 
 -   * @param features
 -   * @param forwardStrand
 -   */
 -  protected static void sortFeatures(SequenceFeature[] features,
 -          final boolean forwardStrand)
 -  {
 -    Arrays.sort(features, new Comparator<SequenceFeature>()
 -    {
 -      @Override
 -      public int compare(SequenceFeature o1, SequenceFeature o2)
 -      {
 -        if (forwardStrand)
 -        {
 -          return Integer.compare(o1.getBegin(), o2.getBegin());
 -        }
 -        else
 -        {
 -          return Integer.compare(o2.getEnd(), o1.getEnd());
 -        }
 -      }
 -    });
 -  }
 -
 -  /**
     * Answers true if the feature type is one we want to keep for the sequence.
     * Some features are only retrieved in order to identify the sequence range,
     * and may then be discarded as redundant information (e.g. "CDS" feature for
  
    /**
     * Returns a (possibly empty) list of features on the sequence which have the
 -   * specified sequence ontology type (or a sub-type of it), and the given
 +   * specified sequence ontology term (or a sub-type of it), and the given
     * identifier as parent
     * 
     * @param sequence
 -   * @param type
 +   * @param term
     * @param parentId
     * @return
     */
    protected List<SequenceFeature> findFeatures(SequenceI sequence,
 -          String type, String parentId)
 +          String term, String parentId)
    {
      List<SequenceFeature> result = new ArrayList<SequenceFeature>();
  
 -    SequenceFeature[] sfs = sequence.getSequenceFeatures();
 -    if (sfs != null)
 +    List<SequenceFeature> sfs = sequence.getFeatures()
 +            .getFeaturesByOntology(term);
 +    for (SequenceFeature sf : sfs)
      {
 -      SequenceOntologyI so = SequenceOntologyFactory.getInstance();
 -      for (SequenceFeature sf : sfs)
 +      String parent = (String) sf.getValue(PARENT);
 +      if (parent != null && parent.equals(parentId))
        {
 -        if (so.isA(sf.getType(), type))
 -        {
 -          String parent = (String) sf.getValue(PARENT);
 -          if (parent.equals(parentId))
 -          {
 -            result.add(sf);
 -          }
 -        }
 +        result.add(sf);
        }
      }
 +
      return result;
    }
  
@@@ -105,8 -105,9 +105,9 @@@ public class JmolParser extends Structu
        // }
        // ;
        // instead, we distinguish .cif from non-.cif by filename
-       setStructureFileType(getDataName().toLowerCase().endsWith(".cif") ? PDBEntry.Type.MMCIF
-               .toString() : "PDB");
+       setStructureFileType(getDataName().toLowerCase().endsWith(".cif")
+               ? PDBEntry.Type.MMCIF.toString()
+               : "PDB");
  
        transformJmolModelToJalview(jmolModel.ms);
      }
        {
          throw new Error(MessageManager.formatMessage(
                  "error.jmol_version_not_compatible_with_jalview_version",
-                 new String[] { JmolViewer.getJmolVersion() }), x);
+                 new String[]
+                 { JmolViewer.getJmolVersion() }), x);
        }
      }
      return viewer;
        }
      } catch (OutOfMemoryError er)
      {
-       System.out
-               .println("OUT OF MEMORY LOADING TRANSFORMING JMOL MODEL TO JALVIEW MODEL");
-       throw new IOException(
-               MessageManager
-                       .getString("exception.outofmemory_loading_mmcif_file"));
+       System.out.println(
+               "OUT OF MEMORY LOADING TRANSFORMING JMOL MODEL TO JALVIEW MODEL");
+       throw new IOException(MessageManager
+               .getString("exception.outofmemory_loading_mmcif_file"));
      }
    }
  
          curAtom.number = atom.getAtomNumber();
          curAtom.resName = atom.getGroup3(true);
          curAtom.resNumber = atom.getResno();
-         curAtom.occupancy = ms.occupancies != null ? ms.occupancies[atom
-                 .getIndex()] : Float.valueOf(atom.getOccupancy100());
+         curAtom.occupancy = ms.occupancies != null
+                 ? ms.occupancies[atom.getIndex()]
+                 : Float.valueOf(atom.getOccupancy100());
          String fmt = new Format("%4i").form(curAtom.resNumber);
          curAtom.resNumIns = (fmt + curAtom.insCode);
          curAtom.tfactor = atom.getBfactor100() / 100f;
            HashMap<String, org.jmol.modelset.Atom> chainTerMap)
    {
      // System.out.println("Atom: " + curAtom.getAtomNumber()
-     // + "   Last atom index " + curAtom.group.lastAtomIndex);
+     // + " Last atom index " + curAtom.group.lastAtomIndex);
      if (chainTerMap == null || prevAtom == null)
      {
        return true;
          {
            return false;
          }
-         if ((curAtom.getResno() - chainTerMap.get(curAtomChId).getResno()) < 5)
+         if ((curAtom.getResno()
+                 - chainTerMap.get(curAtomChId).getResno()) < 5)
          {
            chainTerMap.put(curAtomChId, curAtom);
            return true;
        {
          return false;
        }
-       if ((curAtom.getResno() - chainTerMap.get(curAtomChId).getResno()) < 5)
+       if ((curAtom.getResno()
+               - chainTerMap.get(curAtomChId).getResno()) < 5)
        {
          chainTerMap.put(curAtomChId, curAtom);
          return true;
        return false;
      }
      // HETATM with resNum jump > 2
-     return !(curAtom.isHetero() && ((curAtom.getResno() - prevAtom
-             .getResno()) > 2));
+     return !(curAtom.isHetero()
+             && ((curAtom.getResno() - prevAtom.getResno()) > 2));
    }
  
    private void createAnnotation(SequenceI sequence, PDBChain chain,
            SequenceI sq, char[] secstr, char[] secstrcode, String chainId,
            int firstResNum)
    {
 -    char[] seq = sq.getSequence();
 +    int length = sq.getLength();
      boolean ssFound = false;
 -    Annotation asecstr[] = new Annotation[seq.length + firstResNum - 1];
 -    for (int p = 0; p < seq.length; p++)
 +    Annotation asecstr[] = new Annotation[length + firstResNum - 1];
 +    for (int p = 0; p < length; p++)
      {
        if (secstr[p] >= 'A' && secstr[p] <= 'z')
        {
     * @param secstr
     * @param secstrcode
     */
-   protected void setSecondaryStructure(STR proteinStructureSubType,
-           int pos, char[] secstr, char[] secstrcode)
+   protected void setSecondaryStructure(STR proteinStructureSubType, int pos,
+           char[] secstr, char[] secstrcode)
    {
      switch (proteinStructureSubType)
      {
    @Override
    public void notifyCallback(CBK cbType, Object[] data)
    {
-     String strInfo = (data == null || data[1] == null ? null : data[1]
-             .toString());
+     String strInfo = (data == null || data[1] == null ? null
+             : data[1].toString());
      switch (cbType)
      {
      case ECHO:
     * Not implemented - returns null
     */
    @Override
-   public float[][][] functionXYZ(String functionName, int nx, int ny, int nz)
+   public float[][][] functionXYZ(String functionName, int nx, int ny,
+           int nz)
    {
      return null;
    }
      return predictSecondaryStructure;
    }
  
-   public void setPredictSecondaryStructure(boolean predictSecondaryStructure)
+   public void setPredictSecondaryStructure(
+           boolean predictSecondaryStructure)
    {
      this.predictSecondaryStructure = predictSecondaryStructure;
    }
@@@ -1,6 -1,26 +1,26 @@@
+ /*
+  * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
+  * Copyright (C) $$Year-Rel$$ The Jalview Authors
+  * 
+  * This file is part of Jalview.
+  * 
+  * Jalview is free software: you can redistribute it and/or
+  * modify it under the terms of the GNU General Public License 
+  * as published by the Free Software Foundation, either version 3
+  * of the License, or (at your option) any later version.
+  *  
+  * Jalview is distributed in the hope that it will be useful, but 
+  * WITHOUT ANY WARRANTY; without even the implied warranty 
+  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
+  * PURPOSE.  See the GNU General Public License for more details.
+  * 
+  * You should have received a copy of the GNU General Public License
+  * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
+  * The Jalview Authors are detailed in the 'AUTHORS' file.
+  */
  package jalview.ext.rbvi.chimera;
  
 -import jalview.util.RangeComparator;
 +import jalview.util.IntRangeComparator;
  
  import java.util.ArrayList;
  import java.util.Collections;
@@@ -26,7 -46,7 +46,7 @@@ import java.util.TreeMap
   * </ul>
   * 
   * <pre>
-  * @see http://www.cgl.ucsf.edu/chimera/current/docs/UsersGuide/midas/frameatom_spec.html
+  * &#64;see http://www.cgl.ucsf.edu/chimera/current/docs/UsersGuide/midas/frameatom_spec.html
   * </pre>
   */
  public class AtomSpecModel
          /*
           * sort ranges into ascending start position order
           */
 -        Collections.sort(rangeList, new RangeComparator(true));
 +        Collections.sort(rangeList, IntRangeComparator.ASCENDING);
  
          int start = rangeList.isEmpty() ? 0 : rangeList.get(0)[0];
          int end = rangeList.isEmpty() ? 0 : rangeList.get(0)[1];
@@@ -269,8 -269,8 +269,8 @@@ public class ChimeraCommand
              // final colour range
              if (lastColour != null)
              {
-               addColourRange(colourMap, lastColour, pdbfnum, startPos, lastPos,
-                       lastChain);
+               addColourRange(colourMap, lastColour, pdbfnum, startPos,
+                       lastPos, lastChain);
              }
              // break;
            }
     * @return
     */
    public static StructureMappingcommandSet getSetAttributeCommandsForFeatures(
-           StructureSelectionManager ssm, String[] files,
-           SequenceI[][] seqs, AlignmentViewPanel viewPanel)
+           StructureSelectionManager ssm, String[] files, SequenceI[][] seqs,
+           AlignmentViewPanel viewPanel)
    {
      Map<String, Map<Object, AtomSpecModel>> featureMap = buildFeaturesMap(
              ssm, files, seqs, viewPanel);
     * @return
     */
    protected static Map<String, Map<Object, AtomSpecModel>> buildFeaturesMap(
-           StructureSelectionManager ssm, String[] files,
-           SequenceI[][] seqs, AlignmentViewPanel viewPanel)
+           StructureSelectionManager ssm, String[] files, SequenceI[][] seqs,
+           AlignmentViewPanel viewPanel)
    {
      Map<String, Map<Object, AtomSpecModel>> theMap = new LinkedHashMap<String, Map<Object, AtomSpecModel>>();
  
            StructureMapping mapping, SequenceI seq,
            Map<String, Map<Object, AtomSpecModel>> theMap, int modelNumber)
    {
 -    SequenceFeature[] sfs = seq.getSequenceFeatures();
 -    if (sfs == null)
 -    {
 -      return;
 -    }
 -
 +    List<SequenceFeature> sfs = seq.getFeatures().getPositionalFeatures(
 +            visibleFeatures.toArray(new String[visibleFeatures.size()]));
      for (SequenceFeature sf : sfs)
      {
        String type = sf.getType();
         */
        boolean isFromViewer = JalviewChimeraBinding.CHIMERA_FEATURE_GROUP
                .equals(sf.getFeatureGroup());
 -      if (isFromViewer || !visibleFeatures.contains(type))
 +      if (isFromViewer)
        {
          continue;
        }
          }
          for (int[] range : mappedRanges)
          {
-           addColourRange(featureValues, value, modelNumber, range[0], range[1],
-                   mapping.getChain());
+           addColourRange(featureValues, value, modelNumber, range[0],
+                   range[1], mapping.getChain());
          }
        }
      }
     * to an underscore.
     * 
     * @param featureType
-    * @return <pre>
-    * @see https://www.cgl.ucsf.edu/chimera/current/docs/UsersGuide/midas/setattr.html
-    * </pre>
+    * @return
+    * 
+    *         <pre>
+    * &#64;see https://www.cgl.ucsf.edu/chimera/current/docs/UsersGuide/midas/setattr.html
+    *         </pre>
     */
    protected static String makeAttributeName(String featureType)
    {
@@@ -234,8 -234,8 +234,8 @@@ public class AlignFrame extends GAlignF
     * @param height
     *          height of frame.
     */
-   public AlignFrame(AlignmentI al, HiddenColumns hiddenColumns,
-           int width, int height)
+   public AlignFrame(AlignmentI al, HiddenColumns hiddenColumns, int width,
+           int height)
    {
      this(al, hiddenColumns, width, height, null);
    }
     * @param sequenceSetId
     *          (may be null)
     */
-   public AlignFrame(AlignmentI al, HiddenColumns hiddenColumns,
-           int width, int height, String sequenceSetId)
+   public AlignFrame(AlignmentI al, HiddenColumns hiddenColumns, int width,
+           int height, String sequenceSetId)
    {
      this(al, hiddenColumns, width, height, sequenceSetId, null);
    }
     * @param viewId
     *          (may be null)
     */
-   public AlignFrame(AlignmentI al, HiddenColumns hiddenColumns,
-           int width, int height, String sequenceSetId, String viewId)
+   public AlignFrame(AlignmentI al, HiddenColumns hiddenColumns, int width,
+           int height, String sequenceSetId, String viewId)
    {
      setSize(width, height);
  
        public void keyPressed(KeyEvent evt)
        {
          if (viewport.cursorMode
-                 && ((evt.getKeyCode() >= KeyEvent.VK_0 && evt.getKeyCode() <= KeyEvent.VK_9) || (evt
-                         .getKeyCode() >= KeyEvent.VK_NUMPAD0 && evt
-                         .getKeyCode() <= KeyEvent.VK_NUMPAD9))
+                 && ((evt.getKeyCode() >= KeyEvent.VK_0
+                         && evt.getKeyCode() <= KeyEvent.VK_9)
+                         || (evt.getKeyCode() >= KeyEvent.VK_NUMPAD0
+                                 && evt.getKeyCode() <= KeyEvent.VK_NUMPAD9))
                  && Character.isDigit(evt.getKeyChar()))
          {
            alignPanel.getSeqPanel().numberPressed(evt.getKeyChar());
          case KeyEvent.VK_LEFT:
            if (evt.isAltDown() || !viewport.cursorMode)
            {
-             slideSequences(false, alignPanel.getSeqPanel().getKeyboardNo1());
+             slideSequences(false,
+                     alignPanel.getSeqPanel().getKeyboardNo1());
            }
            else
            {
          case KeyEvent.VK_SPACE:
            if (viewport.cursorMode)
            {
-             alignPanel.getSeqPanel().insertGapAtCursor(
-                     evt.isControlDown() || evt.isShiftDown()
-                             || evt.isAltDown());
+             alignPanel.getSeqPanel().insertGapAtCursor(evt.isControlDown()
+                     || evt.isShiftDown() || evt.isAltDown());
            }
            break;
  
            }
            else
            {
-             alignPanel.getSeqPanel().deleteGapAtCursor(
-                     evt.isControlDown() || evt.isShiftDown()
-                             || evt.isAltDown());
+             alignPanel.getSeqPanel().deleteGapAtCursor(evt.isControlDown()
+                     || evt.isShiftDown() || evt.isAltDown());
            }
  
            break;
  
          case KeyEvent.VK_F2:
            viewport.cursorMode = !viewport.cursorMode;
-           statusBar.setText(MessageManager.formatMessage(
-                   "label.keyboard_editing_mode",
-                   new String[] { (viewport.cursorMode ? "on" : "off") }));
+           statusBar.setText(MessageManager
+                   .formatMessage("label.keyboard_editing_mode", new String[]
+                   { (viewport.cursorMode ? "on" : "off") }));
            if (viewport.cursorMode)
            {
              alignPanel.getSeqPanel().seqCanvas.cursorX = vpRanges
          case KeyEvent.VK_LEFT:
            if (evt.isAltDown() || !viewport.cursorMode)
            {
-             viewport.firePropertyChange("alignment", null, viewport
-                     .getAlignment().getSequences());
+             viewport.firePropertyChange("alignment", null,
+                     viewport.getAlignment().getSequences());
            }
            break;
  
          case KeyEvent.VK_RIGHT:
            if (evt.isAltDown() || !viewport.cursorMode)
            {
-             viewport.firePropertyChange("alignment", null, viewport
-                     .getAlignment().getSequences());
+             viewport.firePropertyChange("alignment", null,
+                     viewport.getAlignment().getSequences());
            }
            break;
          }
                      @Override
                      public void run()
                      {
-                       System.err
-                               .println("Rebuild WS Menu for service change");
+                       System.err.println(
+                               "Rebuild WS Menu for service change");
                        BuildWebServiceMenu();
                      }
  
      showReverse.setVisible(nucleotide);
      showReverseComplement.setVisible(nucleotide);
      conservationMenuItem.setEnabled(!nucleotide);
-     modifyConservation.setEnabled(!nucleotide
-             && conservationMenuItem.isSelected());
+     modifyConservation
+             .setEnabled(!nucleotide && conservationMenuItem.isSelected());
      showGroupConservation.setEnabled(!nucleotide);
  
-     showComplementMenuItem.setText(nucleotide ? MessageManager
-             .getString("label.protein") : MessageManager
-             .getString("label.nucleotide"));
+     showComplementMenuItem
+             .setText(nucleotide ? MessageManager.getString("label.protein")
+                     : MessageManager.getString("label.nucleotide"));
    }
  
    /**
          Desktop.instance.closeAssociatedWindows();
  
          FileLoader loader = new FileLoader();
-         DataSourceType protocol = fileName.startsWith("http:") ? DataSourceType.URL
+         DataSourceType protocol = fileName.startsWith("http:")
+                 ? DataSourceType.URL
                  : DataSourceType.FILE;
          loader.LoadFile(viewport, fileName, protocol, currentFileFormat);
        }
          Rectangle bounds = this.getBounds();
  
          FileLoader loader = new FileLoader();
-         DataSourceType protocol = fileName.startsWith("http:") ? DataSourceType.URL
+         DataSourceType protocol = fileName.startsWith("http:")
+                 ? DataSourceType.URL
                  : DataSourceType.FILE;
          AlignFrame newframe = loader.LoadFileWaitTillLoaded(fileName,
                  protocol, currentFileFormat);
    @Override
    public void addFromText_actionPerformed(ActionEvent e)
    {
-     Desktop.instance.inputTextboxMenuItem_actionPerformed(viewport
-             .getAlignPanel());
+     Desktop.instance
+             .inputTextboxMenuItem_actionPerformed(viewport.getAlignPanel());
    }
  
    @Override
    @Override
    public void saveAs_actionPerformed(ActionEvent e)
    {
-     String format = currentFileFormat == null ? null : currentFileFormat
-             .getName();
-     JalviewFileChooser chooser = JalviewFileChooser.forWrite(
-             Cache.getProperty("LAST_DIRECTORY"), format);
+     String format = currentFileFormat == null ? null
+             : currentFileFormat.getName();
+     JalviewFileChooser chooser = JalviewFileChooser
+             .forWrite(Cache.getProperty("LAST_DIRECTORY"), format);
  
      chooser.setFileView(new JalviewFileView());
-     chooser.setDialogTitle(MessageManager
-             .getString("label.save_alignment_to_file"));
+     chooser.setDialogTitle(
+             MessageManager.getString("label.save_alignment_to_file"));
      chooser.setToolTipText(MessageManager.getString("action.save"));
  
      int value = chooser.showSaveDialog(this);
        currentFileFormat = chooser.getSelectedFormat();
        while (currentFileFormat == null)
        {
-         JvOptionPane
-                 .showInternalMessageDialog(
-                         Desktop.desktop,
-                         MessageManager
-                                 .getString("label.select_file_format_before_saving"),
-                         MessageManager
-                                 .getString("label.file_format_not_specified"),
-                         JvOptionPane.WARNING_MESSAGE);
+         JvOptionPane.showInternalMessageDialog(Desktop.desktop,
+                 MessageManager.getString(
+                         "label.select_file_format_before_saving"),
+                 MessageManager.getString("label.file_format_not_specified"),
+                 JvOptionPane.WARNING_MESSAGE);
          currentFileFormat = chooser.getSelectedFormat();
          value = chooser.showSaveDialog(this);
          if (value != JalviewFileChooser.APPROVE_OPTION)
  
        if (shortName.indexOf(java.io.File.separatorChar) > -1)
        {
-         shortName = shortName.substring(shortName
-                 .lastIndexOf(java.io.File.separatorChar) + 1);
+         shortName = shortName.substring(
+                 shortName.lastIndexOf(java.io.File.separatorChar) + 1);
        }
  
        success = new Jalview2XML().saveAlignment(this, file, shortName);
  
        statusBar.setText(MessageManager.formatMessage(
-               "label.successfully_saved_to_file_in_format", new Object[] {
-                   fileName, format }));
+               "label.successfully_saved_to_file_in_format", new Object[]
+               { fileName, format }));
  
      }
      else
        }
        FormatAdapter f = new FormatAdapter(alignPanel,
                exportData.getSettings());
-       String output = f.formatSequences(
-               format,
-               exportData.getAlignment(), // class cast exceptions will
+       String output = f.formatSequences(format, exportData.getAlignment(), // class
+                                                                            // cast
+                                                                            // exceptions
+                                                                            // will
                // occur in the distant future
                exportData.getOmitHidden(), exportData.getStartEndPostions(),
-               f.getCacheSuffixDefault(format), viewport.getAlignment()
-                       .getHiddenColumns());
+               f.getCacheSuffixDefault(format),
+               viewport.getAlignment().getHiddenColumns());
  
        if (output == null)
        {
            out.close();
            this.setTitle(file);
            statusBar.setText(MessageManager.formatMessage(
-                   "label.successfully_saved_to_file_in_format",
-                   new Object[] { fileName, format.getName() }));
+                   "label.successfully_saved_to_file_in_format", new Object[]
+                   { fileName, format.getName() }));
          } catch (Exception ex)
          {
            success = false;
      if (!success)
      {
        JvOptionPane.showInternalMessageDialog(this, MessageManager
-               .formatMessage("label.couldnt_save_file",
-                       new Object[] { fileName }), MessageManager
-               .getString("label.error_saving_file"),
+               .formatMessage("label.couldnt_save_file", new Object[]
+               { fileName }),
+               MessageManager.getString("label.error_saving_file"),
                JvOptionPane.WARNING_MESSAGE);
      }
  
    @Override
    protected void outputText_actionPerformed(ActionEvent e)
    {
-     FileFormatI fileFormat = FileFormats.getInstance().forName(
-             e.getActionCommand());
+     FileFormatI fileFormat = FileFormats.getInstance()
+             .forName(e.getActionCommand());
      AlignmentExportData exportData = getAlignmentForExport(fileFormat,
              viewport, null);
      if (exportData.getSettings().isCancelled())
        cap.setText(new FormatAdapter(alignPanel, exportData.getSettings())
                .formatSequences(format, exportData.getAlignment(),
                        exportData.getOmitHidden(),
-  exportData
-                               .getStartEndPostions(), viewport
-                               .getAlignment().getHiddenColumns()));
-       Desktop.addInternalFrame(cap, MessageManager.formatMessage(
-               "label.alignment_output_command",
-               new Object[] { e.getActionCommand() }), 600, 500);
+                       exportData.getStartEndPostions(),
+                       viewport.getAlignment().getHiddenColumns()));
+       Desktop.addInternalFrame(cap, MessageManager
+               .formatMessage("label.alignment_output_command", new Object[]
+               { e.getActionCommand() }), 600, 500);
      } catch (OutOfMemoryError oom)
      {
-       new OOMWarning("Outputting alignment as " + e.getActionCommand(), oom);
+       new OOMWarning("Outputting alignment as " + e.getActionCommand(),
+               oom);
        cap.dispose();
      }
  
      JalviewFileChooser chooser = new JalviewFileChooser(
              jalview.bin.Cache.getProperty("LAST_DIRECTORY"));
      chooser.setFileView(new JalviewFileView());
-     chooser.setDialogTitle(MessageManager
-             .getString("label.load_jalview_annotations"));
-     chooser.setToolTipText(MessageManager
-             .getString("label.load_jalview_annotations"));
+     chooser.setDialogTitle(
+             MessageManager.getString("label.load_jalview_annotations"));
+     chooser.setToolTipText(
+             MessageManager.getString("label.load_jalview_annotations"));
  
      int value = chooser.showOpenDialog(null);
  
      {
        undoMenuItem.setEnabled(true);
        CommandI command = viewport.getHistoryList().peek();
-       undoMenuItem.setText(MessageManager.formatMessage(
-               "label.undo_command",
-               new Object[] { command.getDescription() }));
+       undoMenuItem.setText(MessageManager
+               .formatMessage("label.undo_command", new Object[]
+               { command.getDescription() }));
      }
      else
      {
        redoMenuItem.setEnabled(true);
  
        CommandI command = viewport.getRedoList().peek();
-       redoMenuItem.setText(MessageManager.formatMessage(
-               "label.redo_command",
-               new Object[] { command.getDescription() }));
+       redoMenuItem.setText(MessageManager
+               .formatMessage("label.redo_command", new Object[]
+               { command.getDescription() }));
      }
      else
      {
      {
        if (originalSource != viewport)
        {
-         Cache.log
-                 .warn("Implementation worry: mismatch of viewport origin for undo");
+         Cache.log.warn(
+                 "Implementation worry: mismatch of viewport origin for undo");
        }
        originalSource.updateHiddenColumns();
        // originalSource.hasHiddenColumns = (viewport.getColumnSelection() !=
        // && viewport.getColumnSelection().getHiddenColumns() != null &&
        // viewport.getColumnSelection()
        // .getHiddenColumns().size() > 0);
-       originalSource.firePropertyChange("alignment", null, originalSource
-               .getAlignment().getSequences());
+       originalSource.firePropertyChange("alignment", null,
+               originalSource.getAlignment().getSequences());
      }
    }
  
  
        if (originalSource != viewport)
        {
-         Cache.log
-                 .warn("Implementation worry: mismatch of viewport origin for redo");
+         Cache.log.warn(
+                 "Implementation worry: mismatch of viewport origin for redo");
        }
        originalSource.updateHiddenColumns();
        // originalSource.hasHiddenColumns = (viewport.getColumnSelection() !=
        // && viewport.getColumnSelection().getHiddenColumns() != null &&
        // viewport.getColumnSelection()
        // .getHiddenColumns().size() > 0);
-       originalSource.firePropertyChange("alignment", null, originalSource
-               .getAlignment().getSequences());
+       originalSource.firePropertyChange("alignment", null,
+               originalSource.getAlignment().getSequences());
      }
    }
  
      {
        EditCommand editCommand = (EditCommand) command;
        al = editCommand.getAlignment();
-       List<Component> comps = PaintRefresher.components.get(viewport
-               .getSequenceSetId());
+       List<Component> comps = PaintRefresher.components
+               .get(viewport.getSequenceSetId());
  
        for (Component comp : comps)
        {
      List<SequenceI> sg = new ArrayList<>();
      if (viewport.cursorMode)
      {
-       sg.add(viewport.getAlignment().getSequenceAt(
-               alignPanel.getSeqPanel().seqCanvas.cursorY));
+       sg.add(viewport.getAlignment()
+               .getSequenceAt(alignPanel.getSeqPanel().seqCanvas.cursorY));
      }
      else if (viewport.getSelectionGroup() != null
              && viewport.getSelectionGroup().getSize() != viewport
                      .getAlignment().getHeight())
      {
-       sg = viewport.getSelectionGroup().getSequences(
-               viewport.getHiddenRepSequences());
+       sg = viewport.getSelectionGroup()
+               .getSequences(viewport.getHiddenRepSequences());
      }
  
      if (sg.size() < 1)
      SlideSequencesCommand ssc;
      if (right)
      {
-       ssc = new SlideSequencesCommand("Slide Sequences", seqs2, seqs1,
-               size, viewport.getGapCharacter());
+       ssc = new SlideSequencesCommand("Slide Sequences", seqs2, seqs1, size,
+               viewport.getGapCharacter());
      }
      else
      {
-       ssc = new SlideSequencesCommand("Slide Sequences", seqs1, seqs2,
-               size, viewport.getGapCharacter());
+       ssc = new SlideSequencesCommand("Slide Sequences", seqs1, seqs2, size,
+               viewport.getGapCharacter());
      }
  
      int groupAdjustment = 0;
      if (!inSplitFrame && historyList != null && historyList.size() > 0
              && historyList.peek() instanceof SlideSequencesCommand)
      {
-       appendHistoryItem = ssc
-               .appendSlideCommand((SlideSequencesCommand) historyList
-                       .peek());
+       appendHistoryItem = ssc.appendSlideCommand(
+               (SlideSequencesCommand) historyList.peek());
      }
  
      if (!appendHistoryItem)
        Toolkit.getDefaultToolkit().getSystemClipboard()
                .setContents(new StringSelection(""), null);
  
-       Toolkit.getDefaultToolkit().getSystemClipboard()
-               .setContents(ss, Desktop.instance);
+       Toolkit.getDefaultToolkit().getSystemClipboard().setContents(ss,
+               Desktop.instance);
      } catch (OutOfMemoryError er)
      {
        new OOMWarning("copying region", er);
        {
          if (region[0] >= hiddenOffset && region[1] <= hiddenCutoff)
          {
-           hiddenColumns.add(new int[] { region[0] - hiddenOffset,
-               region[1] - hiddenOffset });
+           hiddenColumns
+                   .add(new int[]
+                   { region[0] - hiddenOffset, region[1] - hiddenOffset });
          }
        }
      }
      Desktop.jalviewClipboard = new Object[] { seqs,
          viewport.getAlignment().getDataset(), hiddenColumns };
      statusBar.setText(MessageManager.formatMessage(
-             "label.copied_sequences_to_clipboard", new Object[] { Integer
-                     .valueOf(seqs.length).toString() }));
+             "label.copied_sequences_to_clipboard", new Object[]
+             { Integer.valueOf(seqs.length).toString() }));
    }
  
    /**
            {
              // copy and derive new dataset sequence
              sequences[i] = sequences[i].deriveSequence();
-             alignment.getDataset().addSequence(
-                     sequences[i].getDatasetSequence());
+             alignment.getDataset()
+                     .addSequence(sequences[i].getDatasetSequence());
              // TODO: avoid creation of duplicate dataset sequences with a
              // 'contains' method using SequenceI.equals()/SequenceI.contains()
            }
              annotationAdded = true;
              if (alann[i].sequenceRef == null && !alann[i].autoCalculated)
              {
-               AlignmentAnnotation newann = new AlignmentAnnotation(alann[i]);
+               AlignmentAnnotation newann = new AlignmentAnnotation(
+                       alann[i]);
                if (newann.graphGroup > -1)
                {
                  if (newGraphGroups.size() <= newann.graphGroup
                          || newGraphGroups.get(newann.graphGroup) == null)
                  {
-                   for (int q = newGraphGroups.size(); q <= newann.graphGroup; q++)
+                   for (int q = newGraphGroups
+                           .size(); q <= newann.graphGroup; q++)
                    {
                      newGraphGroups.add(q, null);
                    }
-                   newGraphGroups.set(newann.graphGroup, new Integer(
-                           ++fgroup));
+                   newGraphGroups.set(newann.graphGroup,
+                           new Integer(++fgroup));
                  }
                  newann.graphGroup = newGraphGroups.get(newann.graphGroup)
                          .intValue();
          //
          addHistoryItem(new EditCommand(
                  MessageManager.getString("label.add_sequences"),
-                 Action.PASTE, sequences, 0, alignment.getWidth(), alignment));
+                 Action.PASTE, sequences, 0, alignment.getWidth(),
+                 alignment));
        }
        // Add any annotations attached to sequences
        for (int i = 0; i < sequences.length; i++)
                  if (newGraphGroups.size() <= newann.graphGroup
                          || newGraphGroups.get(newann.graphGroup) == null)
                  {
-                   for (int q = newGraphGroups.size(); q <= newann.graphGroup; q++)
+                   for (int q = newGraphGroups
+                           .size(); q <= newann.graphGroup; q++)
                    {
                      newGraphGroups.add(q, null);
                    }
-                   newGraphGroups.set(newann.graphGroup, new Integer(
-                           ++fgroup));
+                   newGraphGroups.set(newann.graphGroup,
+                           new Integer(++fgroup));
                  }
                  newann.graphGroup = newGraphGroups.get(newann.graphGroup)
                          .intValue();
              // was
              // duplicated
              // earlier
-             alignment
-                     .setAnnotationIndex(sequences[i].getAnnotation()[a], a);
+             alignment.setAnnotationIndex(sequences[i].getAnnotation()[a],
+                     a);
            }
          }
        }
          // >>>This is a fix for the moment, until a better solution is
          // found!!<<<
          af.alignPanel.getSeqPanel().seqCanvas.getFeatureRenderer()
-                 .transferSettings(
-                         alignPanel.getSeqPanel().seqCanvas
-                                 .getFeatureRenderer());
+                 .transferSettings(alignPanel.getSeqPanel().seqCanvas
+                         .getFeatureRenderer());
  
          // TODO: maintain provenance of an alignment, rather than just make the
          // title a concatenation of operations.
    {
      try
      {
-       AlignmentI alignment = AlignmentUtils.expandContext(getViewport()
-               .getAlignment(), -1);
+       AlignmentI alignment = AlignmentUtils
+               .expandContext(getViewport().getAlignment(), -1);
        AlignFrame af = new AlignFrame(alignment, DEFAULT_WIDTH,
                DEFAULT_HEIGHT);
        String newtitle = new String("Flanking alignment");
        // >>>This is a fix for the moment, until a better solution is
        // found!!<<<
        af.alignPanel.getSeqPanel().seqCanvas.getFeatureRenderer()
-               .transferSettings(
-                       alignPanel.getSeqPanel().seqCanvas
-                               .getFeatureRenderer());
+               .transferSettings(alignPanel.getSeqPanel().seqCanvas
+                       .getFeatureRenderer());
  
        // TODO: maintain provenance of an alignment, rather than just make the
        // title a concatenation of operations.
       */
      if (sg.getSize() == viewport.getAlignment().getHeight())
      {
-       boolean isEntireAlignWidth = (((sg.getEndRes() - sg.getStartRes()) + 1) == viewport
-               .getAlignment().getWidth()) ? true : false;
+       boolean isEntireAlignWidth = (((sg.getEndRes() - sg.getStartRes())
+               + 1) == viewport.getAlignment().getWidth()) ? true : false;
        if (isEntireAlignWidth)
        {
          int confirm = JvOptionPane.showConfirmDialog(this,
      viewport.sendSelection();
      viewport.getAlignment().deleteGroup(sg);
  
-     viewport.firePropertyChange("alignment", null, viewport.getAlignment()
-             .getSequences());
+     viewport.firePropertyChange("alignment", null,
+             viewport.getAlignment().getSequences());
      if (viewport.getAlignment().getHeight() < 1)
      {
        try
      viewport.setSelectionGroup(null);
      viewport.getColumnSelection().clear();
      viewport.setSelectionGroup(null);
 -    alignPanel.getSeqPanel().seqCanvas.highlightSearchResults(null);
      alignPanel.getIdPanel().getIdCanvas().searchResults = null;
      // JAL-2034 - should delegate to
      // alignPanel to decide if overview needs
        SequenceI[] seqs;
        if (viewport.getSelectionGroup() != null)
        {
-         seqs = viewport.getSelectionGroup().getSequencesAsArray(
-                 viewport.getHiddenRepSequences());
+         seqs = viewport.getSelectionGroup()
+                 .getSequencesAsArray(viewport.getHiddenRepSequences());
        }
        else
        {
                  column, viewport.getAlignment());
        }
  
-       statusBar.setText(MessageManager.formatMessage(
-               "label.removed_columns",
-               new String[] { Integer.valueOf(trimRegion.getSize())
-                       .toString() }));
+       statusBar.setText(MessageManager
+               .formatMessage("label.removed_columns", new String[]
+               { Integer.valueOf(trimRegion.getSize()).toString() }));
  
        addHistoryItem(trimRegion);
  
          }
        }
  
-       viewport.firePropertyChange("alignment", null, viewport
-               .getAlignment().getSequences());
+       viewport.firePropertyChange("alignment", null,
+               viewport.getAlignment().getSequences());
      }
    }
  
      SequenceI[] seqs;
      if (viewport.getSelectionGroup() != null)
      {
-       seqs = viewport.getSelectionGroup().getSequencesAsArray(
-               viewport.getHiddenRepSequences());
+       seqs = viewport.getSelectionGroup()
+               .getSequencesAsArray(viewport.getHiddenRepSequences());
        start = viewport.getSelectionGroup().getStartRes();
        end = viewport.getSelectionGroup().getEndRes();
      }
  
      addHistoryItem(removeGapCols);
  
-     statusBar.setText(MessageManager.formatMessage(
-             "label.removed_empty_columns",
-             new Object[] { Integer.valueOf(removeGapCols.getSize())
-                     .toString() }));
+     statusBar.setText(MessageManager
+             .formatMessage("label.removed_empty_columns", new Object[]
+             { Integer.valueOf(removeGapCols.getSize()).toString() }));
  
      // This is to maintain viewport position on first residue
      // of first sequence
      // if (viewport.hasHiddenColumns)
      // viewport.getColumnSelection().compensateForEdits(shifts);
      vpRanges.setStartRes(seq.findIndex(startRes) - 1);
-     viewport.firePropertyChange("alignment", null, viewport.getAlignment()
-             .getSequences());
+     viewport.firePropertyChange("alignment", null,
+             viewport.getAlignment().getSequences());
  
    }
  
      SequenceI[] seqs;
      if (viewport.getSelectionGroup() != null)
      {
-       seqs = viewport.getSelectionGroup().getSequencesAsArray(
-               viewport.getHiddenRepSequences());
+       seqs = viewport.getSelectionGroup()
+               .getSequencesAsArray(viewport.getHiddenRepSequences());
        start = viewport.getSelectionGroup().getStartRes();
        end = viewport.getSelectionGroup().getEndRes();
      }
  
      vpRanges.setStartRes(seq.findIndex(startRes) - 1);
  
-     viewport.firePropertyChange("alignment", null, viewport.getAlignment()
-             .getSequences());
+     viewport.firePropertyChange("alignment", null,
+             viewport.getAlignment().getSequences());
  
    }
  
    public void padGapsMenuitem_actionPerformed(ActionEvent e)
    {
      viewport.setPadGaps(padGapsMenuitem.isSelected());
-     viewport.firePropertyChange("alignment", null, viewport.getAlignment()
-             .getSequences());
+     viewport.firePropertyChange("alignment", null,
+             viewport.getAlignment().getSequences());
    }
  
    /**
      }
      String newViewName = viewTitle + ((addFirstIndex) ? " " + index : "");
  
-     List<Component> comps = PaintRefresher.components.get(viewport
-             .getSequenceSetId());
+     List<Component> comps = PaintRefresher.components
+             .get(viewport.getSequenceSetId());
  
      List<String> existingNames = getExistingViewNames(comps);
  
        // Hide everything by the current selection - this is a hack - we do the
        // invert and then hide
        // first check that there will be visible columns after the invert.
-       if (viewport.hasSelectedColumns()
-               || (sg != null && sg.getSize() > 0 && sg.getStartRes() <= sg
-                       .getEndRes()))
+       if (viewport.hasSelectedColumns() || (sg != null && sg.getSize() > 0
+               && sg.getStartRes() <= sg.getEndRes()))
        {
          // now invert the sequence set, if required - empty selection implies
          // that no hiding is required.
      editPane.setEditable(false);
      StringBuffer contents = new AlignmentProperties(viewport.getAlignment())
              .formatAsHtml();
-     editPane.setText(MessageManager.formatMessage("label.html_content",
-             new Object[] { contents.toString() }));
+     editPane.setText(
+             MessageManager.formatMessage("label.html_content", new Object[]
+             { contents.toString() }));
      JInternalFrame frame = new JInternalFrame();
      frame.getContentPane().add(new JScrollPane(editPane));
  
-     Desktop.addInternalFrame(frame, MessageManager.formatMessage(
-             "label.alignment_properties", new Object[] { getTitle() }),
-             500, 400);
+     Desktop.addInternalFrame(frame, MessageManager
+             .formatMessage("label.alignment_properties", new Object[]
+             { getTitle() }), 500, 400);
    }
  
    /**
      JInternalFrame frame = new JInternalFrame();
      final OverviewPanel overview = new OverviewPanel(alignPanel);
      frame.setContentPane(overview);
-     Desktop.addInternalFrame(frame, MessageManager.formatMessage(
-             "label.overview_params", new Object[] { this.getTitle() }),
-             true, frame.getWidth(), frame.getHeight(), true, true);
+     Desktop.addInternalFrame(frame, MessageManager
+             .formatMessage("label.overview_params", new Object[]
+             { this.getTitle() }), true, frame.getWidth(), frame.getHeight(),
+             true, true);
      frame.pack();
      frame.setLayer(JLayeredPane.PALETTE_LAYER);
-     frame.addInternalFrameListener(new javax.swing.event.InternalFrameAdapter()
-     {
-       @Override
-       public void internalFrameClosed(
-               javax.swing.event.InternalFrameEvent evt)
-       {
-         overview.dispose();
-         alignPanel.setOverviewPanel(null);
-       };
-     });
+     frame.addInternalFrameListener(
+             new javax.swing.event.InternalFrameAdapter()
+             {
+               @Override
+               public void internalFrameClosed(
+                       javax.swing.event.InternalFrameEvent evt)
+               {
+                 overview.dispose();
+                 alignPanel.setOverviewPanel(null);
+               };
+             });
  
      alignPanel.setOverviewPanel(overview);
    }
    @Override
    protected void modifyPID_actionPerformed()
    {
-     SliderPanel.setPIDSliderSource(alignPanel,
-             viewport.getResidueShading(), alignPanel.getViewName());
+     SliderPanel.setPIDSliderSource(alignPanel, viewport.getResidueShading(),
+             alignPanel.getViewName());
      SliderPanel.showPIDSlider();
    }
  
    public void sortPairwiseMenuItem_actionPerformed(ActionEvent e)
    {
      SequenceI[] oldOrder = viewport.getAlignment().getSequencesArray();
-     AlignmentSorter.sortByPID(viewport.getAlignment(), viewport
-             .getAlignment().getSequenceAt(0));
+     AlignmentSorter.sortByPID(viewport.getAlignment(),
+             viewport.getAlignment().getSequenceAt(0));
      addHistoryItem(new OrderCommand("Pairwise Sort", oldOrder,
              viewport.getAlignment()));
      alignPanel.paintAlignment(true);
    {
      SequenceI[] oldOrder = viewport.getAlignment().getSequencesArray();
      AlignmentSorter.sortByID(viewport.getAlignment());
-     addHistoryItem(new OrderCommand("ID Sort", oldOrder,
-             viewport.getAlignment()));
+     addHistoryItem(
+             new OrderCommand("ID Sort", oldOrder, viewport.getAlignment()));
      alignPanel.paintAlignment(true);
    }
  
      if ((viewport.getSelectionGroup() == null)
              || (viewport.getSelectionGroup().getSize() < 2))
      {
-       JvOptionPane.showInternalMessageDialog(this, MessageManager
-               .getString("label.you_must_select_least_two_sequences"),
+       JvOptionPane.showInternalMessageDialog(this,
+               MessageManager.getString(
+                       "label.you_must_select_least_two_sequences"),
                MessageManager.getString("label.invalid_selection"),
                JvOptionPane.WARNING_MESSAGE);
      }
      viewport.autoCalculateConsensus = autoCalculate.isSelected();
      if (viewport.autoCalculateConsensus)
      {
-       viewport.firePropertyChange("alignment", null, viewport
-               .getAlignment().getSequences());
+       viewport.firePropertyChange("alignment", null,
+               viewport.getAlignment().getSequences());
      }
    }
  
     * @param options
     *          parameters for the distance or similarity calculation
     */
-   void newTreePanel(String type, String modelName, SimilarityParamsI options)
+   void newTreePanel(String type, String modelName,
+           SimilarityParamsI options)
    {
      String frameTitle = "";
      TreePanel tp;
        {
          if (_s.getLength() < sg.getEndRes())
          {
-           JvOptionPane
-                   .showMessageDialog(
-                           Desktop.desktop,
-                           MessageManager
-                                   .getString("label.selected_region_to_tree_may_only_contain_residues_or_gaps"),
-                           MessageManager
-                                   .getString("label.sequences_selection_not_aligned"),
-                           JvOptionPane.WARNING_MESSAGE);
+           JvOptionPane.showMessageDialog(Desktop.desktop,
+                   MessageManager.getString(
+                           "label.selected_region_to_tree_may_only_contain_residues_or_gaps"),
+                   MessageManager.getString(
+                           "label.sequences_selection_not_aligned"),
+                   JvOptionPane.WARNING_MESSAGE);
  
            return;
          }
    public void addSortByOrderMenuItem(String title,
            final AlignmentOrder order)
    {
-     final JMenuItem item = new JMenuItem(MessageManager.formatMessage(
-             "action.by_title_param", new Object[] { title }));
+     final JMenuItem item = new JMenuItem(MessageManager
+             .formatMessage("action.by_title_param", new Object[]
+             { title }));
      sort.add(item);
      item.addActionListener(new java.awt.event.ActionListener()
      {
          // pointers
          AlignmentSorter.sortBy(viewport.getAlignment(), order);
  
-         addHistoryItem(new OrderCommand(order.getName(), oldOrder, viewport
-                 .getAlignment()));
+         addHistoryItem(new OrderCommand(order.getName(), oldOrder,
+                 viewport.getAlignment()));
  
          alignPanel.paintAlignment(true);
        }
        return;
      }
  
-     if (viewport.getAlignment().getAlignmentAnnotation().hashCode() != _annotationScoreVectorHash)
+     if (viewport.getAlignment().getAlignmentAnnotation()
+             .hashCode() != _annotationScoreVectorHash)
      {
        sortByAnnotScore.removeAll();
        // almost certainly a quicker way to do this - but we keep it simple
    {
      sortByTreeMenu.removeAll();
  
-     List<Component> comps = PaintRefresher.components.get(viewport
-             .getSequenceSetId());
+     List<Component> comps = PaintRefresher.components
+             .get(viewport.getSequenceSetId());
      List<TreePanel> treePanels = new ArrayList<>();
      for (Component comp : comps)
      {
      JalviewFileChooser chooser = new JalviewFileChooser(
              jalview.bin.Cache.getProperty("LAST_DIRECTORY"));
      chooser.setFileView(new JalviewFileView());
-     chooser.setDialogTitle(MessageManager
-             .getString("label.select_newick_like_tree_file"));
-     chooser.setToolTipText(MessageManager.getString("label.load_tree_file"));
+     chooser.setDialogTitle(
+             MessageManager.getString("label.select_newick_like_tree_file"));
+     chooser.setToolTipText(
+             MessageManager.getString("label.load_tree_file"));
  
      int value = chooser.showOpenDialog(null);
  
          viewport.setCurrentTree(showNewickTree(fin, filePath).getTree());
        } catch (Exception ex)
        {
-         JvOptionPane
-                 .showMessageDialog(
-                         Desktop.desktop,
-                         ex.getMessage(),
-                         MessageManager
-                                 .getString("label.problem_reading_tree_file"),
-                         JvOptionPane.WARNING_MESSAGE);
+         JvOptionPane.showMessageDialog(Desktop.desktop, ex.getMessage(),
+                 MessageManager.getString("label.problem_reading_tree_file"),
+                 JvOptionPane.WARNING_MESSAGE);
          ex.printStackTrace();
        }
        if (fin != null && fin.hasWarningMessage())
        {
-         JvOptionPane.showMessageDialog(Desktop.desktop, fin
-                 .getWarningMessage(), MessageManager
-                 .getString("label.possible_problem_with_tree_file"),
+         JvOptionPane.showMessageDialog(Desktop.desktop,
+                 fin.getWarningMessage(),
+                 MessageManager
+                         .getString("label.possible_problem_with_tree_file"),
                  JvOptionPane.WARNING_MESSAGE);
        }
      }
                  }
                } catch (Exception e)
                {
-                 Cache.log
-                         .debug("Exception during web service menu building process.",
-                                 e);
+                 Cache.log.debug(
+                         "Exception during web service menu building process.",
+                         e);
                }
              }
            });
            @Override
            public void actionPerformed(ActionEvent e)
            {
-             showProductsFor(af.viewport.getSequenceSelection(), dna, source);
+             showProductsFor(af.viewport.getSequenceSelection(), dna,
+                     source);
            }
          });
          showProducts.add(xtype);
        showProducts.setEnabled(showp);
      } catch (Exception e)
      {
-       Cache.log
-               .warn("canShowProducts threw an exception - please report to help@jalview.org",
-                       e);
+       Cache.log.warn(
+               "canShowProducts threw an exception - please report to help@jalview.org",
+               e);
        return false;
      }
      return showp;
     * @param source
     *          the database to show cross-references for
     */
-   protected void showProductsFor(final SequenceI[] sel,
-           final boolean _odna, final String source)
+   protected void showProductsFor(final SequenceI[] sel, final boolean _odna,
+           final String source)
    {
      new Thread(CrossRefAction.showProductsFor(sel, _odna, source, this))
              .start();
      {
        jalview.bin.Cache.log.error(
                "Exception during translation. Please report this !", ex);
-       final String msg = MessageManager
-               .getString("label.error_when_translating_sequences_submit_bug_report");
+       final String msg = MessageManager.getString(
+               "label.error_when_translating_sequences_submit_bug_report");
        final String errorTitle = MessageManager
                .getString("label.implementation_error")
                + MessageManager.getString("label.translation_failed");
      }
      if (al == null || al.getHeight() == 0)
      {
-       final String msg = MessageManager
-               .getString("label.select_at_least_three_bases_in_at_least_one_sequence_to_cDNA_translation");
+       final String msg = MessageManager.getString(
+               "label.select_at_least_three_bases_in_at_least_one_sequence_to_cDNA_translation");
        final String errorTitle = MessageManager
                .getString("label.translation_failed");
        JvOptionPane.showMessageDialog(Desktop.desktop, msg, errorTitle,
      {
        AlignFrame af = new AlignFrame(al, DEFAULT_WIDTH, DEFAULT_HEIGHT);
        af.setFileFormat(this.currentFileFormat);
-       final String newTitle = MessageManager.formatMessage(
-               "label.translation_of_params",
-               new Object[] { this.getTitle() });
+       final String newTitle = MessageManager
+               .formatMessage("label.translation_of_params", new Object[]
+               { this.getTitle() });
        af.setTitle(newTitle);
        if (Cache.getDefault(Preferences.ENABLE_SPLIT_FRAME, true))
        {
        {
          // check to see if any of these files have names matching sequences in
          // the alignment
-         SequenceIdMatcher idm = new SequenceIdMatcher(viewport
-                 .getAlignment().getSequencesArray());
+         SequenceIdMatcher idm = new SequenceIdMatcher(
+                 viewport.getAlignment().getSequencesArray());
          /**
           * Object[] { String,SequenceI}
           */
          if (filesmatched.size() > 0)
          {
            if (Cache.getDefault("AUTOASSOCIATE_PDBANDSEQS", false)
-                   || JvOptionPane
-                           .showConfirmDialog(
-                                   this,
-                                   MessageManager
-                                           .formatMessage(
-                                                   "label.automatically_associate_structure_files_with_sequences_same_name",
-                                                   new Object[] { Integer
-                                                           .valueOf(
-                                                                   filesmatched
-                                                                           .size())
-                                                           .toString() }),
-                                   MessageManager
-                                           .getString("label.automatically_associate_structure_files_by_name"),
-                                   JvOptionPane.YES_NO_OPTION) == JvOptionPane.YES_OPTION)
+                   || JvOptionPane.showConfirmDialog(this,
+                           MessageManager.formatMessage(
+                                   "label.automatically_associate_structure_files_with_sequences_same_name",
+                                   new Object[]
+                                   { Integer.valueOf(filesmatched.size())
+                                           .toString() }),
+                           MessageManager.getString(
+                                   "label.automatically_associate_structure_files_by_name"),
+                           JvOptionPane.YES_NO_OPTION) == JvOptionPane.YES_OPTION)
  
            {
              for (Object[] fm : filesmatched)
                                  Desktop.instance);
                  if (pe != null)
                  {
-                   System.err.println("Associated file : "
-                           + ((String) fm[0]) + " with "
-                           + toassoc.getDisplayId(true));
+                   System.err.println("Associated file : " + ((String) fm[0])
+                           + " with " + toassoc.getDisplayId(true));
                    assocfiles++;
                  }
                }
          }
          if (filesnotmatched.size() > 0)
          {
-           if (assocfiles > 0
-                   && (Cache.getDefault(
-                           "AUTOASSOCIATE_PDBANDSEQS_IGNOREOTHERS", false) || JvOptionPane
-                           .showConfirmDialog(
-                                   this,
-                                   "<html>"
-                                           + MessageManager
-                                                   .formatMessage(
-                                                           "label.ignore_unmatched_dropped_files_info",
-                                                           new Object[] { Integer
-                                                                   .valueOf(
-                                                                           filesnotmatched
-                                                                                   .size())
-                                                                   .toString() })
-                                           + "</html>",
-                                   MessageManager
-                                           .getString("label.ignore_unmatched_dropped_files"),
-                                   JvOptionPane.YES_NO_OPTION) == JvOptionPane.YES_OPTION))
+           if (assocfiles > 0 && (Cache.getDefault(
+                   "AUTOASSOCIATE_PDBANDSEQS_IGNOREOTHERS", false)
+                   || JvOptionPane.showConfirmDialog(this,
+                           "<html>" + MessageManager.formatMessage(
+                                   "label.ignore_unmatched_dropped_files_info",
+                                   new Object[]
+                                   { Integer.valueOf(filesnotmatched.size())
+                                           .toString() })
+                                   + "</html>",
+                           MessageManager.getString(
+                                   "label.ignore_unmatched_dropped_files"),
+                           JvOptionPane.YES_NO_OPTION) == JvOptionPane.YES_OPTION))
            {
              return;
            }
        // if the file isn't identified, or not positively identified as some
        // other filetype (PFAM is default unidentified alignment file type) then
        // try to parse as annotation.
-       boolean isAnnotation = (format == null || FileFormat.Pfam
-               .equals(format)) ? new AnnotationFile()
-               .annotateAlignmentView(viewport, file, sourceType) : false;
+       boolean isAnnotation = (format == null
+               || FileFormat.Pfam.equals(format))
+                       ? new AnnotationFile().annotateAlignmentView(viewport,
+                               file, sourceType)
+                       : false;
  
        if (!isAnnotation)
        {
              if (tcf.annotateAlignment(viewport.getAlignment(), true))
              {
                buildColourMenu();
-               changeColour(new TCoffeeColourScheme(viewport.getAlignment()));
+               changeColour(
+                       new TCoffeeColourScheme(viewport.getAlignment()));
                isAnnotation = true;
-               statusBar
-                       .setText(MessageManager
-                               .getString("label.successfully_pasted_tcoffee_scores_to_alignment"));
+               statusBar.setText(MessageManager.getString(
+                       "label.successfully_pasted_tcoffee_scores_to_alignment"));
              }
              else
              {
                // some problem - if no warning its probable that the ID matching
                // process didn't work
-               JvOptionPane
-                       .showMessageDialog(
-                               Desktop.desktop,
-                               tcf.getWarningMessage() == null ? MessageManager
-                                       .getString("label.check_file_matches_sequence_ids_alignment")
-                                       : tcf.getWarningMessage(),
-                               MessageManager
-                                       .getString("label.problem_reading_tcoffee_score_file"),
-                               JvOptionPane.WARNING_MESSAGE);
+               JvOptionPane.showMessageDialog(Desktop.desktop,
+                       tcf.getWarningMessage() == null
+                               ? MessageManager.getString(
+                                       "label.check_file_matches_sequence_ids_alignment")
+                               : tcf.getWarningMessage(),
+                       MessageManager.getString(
+                               "label.problem_reading_tcoffee_score_file"),
+                       JvOptionPane.WARNING_MESSAGE);
              }
            }
            else
            }
          } catch (Exception x)
          {
-           Cache.log
-                   .debug("Exception when processing data source as T-COFFEE score file",
-                           x);
+           Cache.log.debug(
+                   "Exception when processing data source as T-COFFEE score file",
+                   x);
            tcf = null;
          }
          if (tcf == null)
            }
            if (FileFormat.ScoreMatrix == format)
            {
-             ScoreMatrixFile sm = new ScoreMatrixFile(new FileParse(file,
-                     sourceType));
+             ScoreMatrixFile sm = new ScoreMatrixFile(
+                     new FileParse(file, sourceType));
              sm.parse();
              // todo: i18n this message
-             statusBar
-                     .setText(MessageManager.formatMessage(
-                             "label.successfully_loaded_matrix",
-                             sm.getMatrixName()));
+             statusBar.setText(MessageManager.formatMessage(
+                     "label.successfully_loaded_matrix",
+                     sm.getMatrixName()));
            }
            else if (FileFormat.Jnet.equals(format))
            {
        }
        new OOMWarning(
                "loading data "
-                       + (sourceType != null ? (sourceType == DataSourceType.PASTE ? "from clipboard."
-                               : "using " + sourceType + " from " + file)
+                       + (sourceType != null
+                               ? (sourceType == DataSourceType.PASTE
+                                       ? "from clipboard."
+                                       : "using " + sourceType + " from "
+                                               + file)
                                : ".")
-                       + (format != null ? "(parsing as '" + format
-                               + "' file)" : ""), oom, Desktop.desktop);
+                       + (format != null
+                               ? "(parsing as '" + format + "' file)"
+                               : ""),
+               oom, Desktop.desktop);
      }
    }
  
      final AlignViewportI peer = viewport.getCodingComplement();
      if (peer != null)
      {
-       AlignFrame linkedAlignFrame = ((AlignViewport) peer).getAlignPanel().alignFrame;
+       AlignFrame linkedAlignFrame = ((AlignViewport) peer)
+               .getAlignPanel().alignFrame;
        if (linkedAlignFrame.tabbedPane.getTabCount() > index)
        {
          linkedAlignFrame.tabbedPane.setSelectedIndex(index);
      // here
      final JMenu rfetch = new JMenu(
              MessageManager.getString("action.fetch_db_references"));
-     rfetch.setToolTipText(MessageManager
-             .getString("label.retrieve_parse_sequence_database_records_alignment_or_selected_sequences"));
+     rfetch.setToolTipText(MessageManager.getString(
+             "label.retrieve_parse_sequence_database_records_alignment_or_selected_sequences"));
      webService.add(rfetch);
  
      final JCheckBoxMenuItem trimrs = new JCheckBoxMenuItem(
              MessageManager.getString("option.trim_retrieved_seqs"));
-     trimrs.setToolTipText(MessageManager
-             .getString("label.trim_retrieved_sequences"));
+     trimrs.setToolTipText(
+             MessageManager.getString("label.trim_retrieved_sequences"));
      trimrs.setSelected(Cache.getDefault("TRIM_FETCHED_DATASET_SEQS", true));
      trimrs.addActionListener(new ActionListener()
      {
      rfetch.add(trimrs);
      JMenuItem fetchr = new JMenuItem(
              MessageManager.getString("label.standard_databases"));
-     fetchr.setToolTipText(MessageManager
-             .getString("label.fetch_embl_uniprot"));
+     fetchr.setToolTipText(
+             MessageManager.getString("label.fetch_embl_uniprot"));
      fetchr.addActionListener(new ActionListener()
      {
  
            {
              boolean isNucleotide = alignPanel.alignFrame.getViewport()
                      .getAlignment().isNucleotide();
-             DBRefFetcher dbRefFetcher = new DBRefFetcher(alignPanel.av
-                     .getSequenceSelection(), alignPanel.alignFrame, null,
+             DBRefFetcher dbRefFetcher = new DBRefFetcher(
+                     alignPanel.av.getSequenceSelection(),
+                     alignPanel.alignFrame, null,
                      alignPanel.alignFrame.featureSettings, isNucleotide);
              dbRefFetcher.addListener(new FetchFinishedListenerI()
              {
                  });
                  fetchr.setToolTipText(JvSwingUtils.wrapTooltip(true,
                          MessageManager.formatMessage(
-                                 "label.fetch_retrieve_from",
-                                 new Object[] { src.getDbName() })));
+                                 "label.fetch_retrieve_from", new Object[]
+                                 { src.getDbName() })));
                  dfetch.add(fetchr);
                  comp++;
                }
                          .toArray(new DbSourceProxy[0]);
                  // fetch all entry
                  DbSourceProxy src = otherdb.get(0);
-                 fetchr = new JMenuItem(MessageManager.formatMessage(
-                         "label.fetch_all_param",
-                         new Object[] { src.getDbSource() }));
+                 fetchr = new JMenuItem(MessageManager
+                         .formatMessage("label.fetch_all_param", new Object[]
+                         { src.getDbSource() }));
                  fetchr.addActionListener(new ActionListener()
                  {
                    @Override
                  fetchr.setToolTipText(JvSwingUtils.wrapTooltip(true,
                          MessageManager.formatMessage(
                                  "label.fetch_retrieve_from_all_sources",
-                                 new Object[] {
-                                     Integer.valueOf(otherdb.size())
-                                             .toString(), src.getDbSource(),
-                                     src.getDbName() })));
+                                 new Object[]
+                                 { Integer.valueOf(otherdb.size())
+                                         .toString(),
+                                     src.getDbSource(), src.getDbName() })));
                  dfetch.add(fetchr);
                  comp++;
                  // and then build the rest of the individual menus
                  ifetch = new JMenu(MessageManager.formatMessage(
-                         "label.source_from_db_source",
-                         new Object[] { src.getDbSource() }));
+                         "label.source_from_db_source", new Object[]
+                         { src.getDbSource() }));
                  icomp = 0;
                  String imname = null;
                  int i = 0;
                  for (DbSourceProxy sproxy : otherdb)
                  {
                    String dbname = sproxy.getDbName();
-                   String sname = dbname.length() > 5 ? dbname.substring(0,
-                           5) + "..." : dbname;
-                   String msname = dbname.length() > 10 ? dbname.substring(
-                           0, 10) + "..." : dbname;
+                   String sname = dbname.length() > 5
+                           ? dbname.substring(0, 5) + "..."
+                           : dbname;
+                   String msname = dbname.length() > 10
+                           ? dbname.substring(0, 10) + "..."
+                           : dbname;
                    if (imname == null)
                    {
-                     imname = MessageManager.formatMessage(
-                             "label.from_msname", new Object[] { sname });
+                     imname = MessageManager
+                             .formatMessage("label.from_msname", new Object[]
+                             { sname });
                    }
                    fetchr = new JMenuItem(msname);
                    final DbSourceProxy[] dassrc = { sproxy };
                      }
  
                    });
-                   fetchr.setToolTipText("<html>"
-                           + MessageManager.formatMessage(
+                   fetchr.setToolTipText(
+                           "<html>" + MessageManager.formatMessage(
                                    "label.fetch_retrieve_from", new Object[]
                                    { dbname }));
                    ifetch.add(fetchr);
     */
    public void setDisplayedView(AlignmentPanel alignmentPanel)
    {
-     if (!viewport.getSequenceSetId().equals(
-             alignmentPanel.av.getSequenceSetId()))
+     if (!viewport.getSequenceSetId()
+             .equals(alignmentPanel.av.getSequenceSetId()))
      {
-       throw new Error(
-               MessageManager
-                       .getString("error.implementation_error_cannot_show_view_alignment_frame"));
+       throw new Error(MessageManager.getString(
+               "error.implementation_error_cannot_show_view_alignment_frame"));
      }
-     if (tabbedPane != null
-             && tabbedPane.getTabCount() > 0
-             && alignPanels.indexOf(alignmentPanel) != tabbedPane
-                     .getSelectedIndex())
+     if (tabbedPane != null && tabbedPane.getTabCount() > 0 && alignPanels
+             .indexOf(alignmentPanel) != tabbedPane.getSelectedIndex())
      {
        tabbedPane.setSelectedIndex(alignPanels.indexOf(alignmentPanel));
      }
        // show a warning dialog no mapped cDNA
        return;
      }
-     AlignmentI cdna = new Alignment(cdnaSeqs.toArray(new SequenceI[cdnaSeqs
-             .size()]));
+     AlignmentI cdna = new Alignment(
+             cdnaSeqs.toArray(new SequenceI[cdnaSeqs.size()]));
      GAlignFrame alignFrame = new AlignFrame(cdna, AlignFrame.DEFAULT_WIDTH,
              AlignFrame.DEFAULT_HEIGHT);
      cdna.alignAs(alignment);
      String newtitle = "cDNA " + MessageManager.getString("label.for") + " "
              + this.title;
-     Desktop.addInternalFrame(alignFrame, newtitle,
-             AlignFrame.DEFAULT_WIDTH, AlignFrame.DEFAULT_HEIGHT);
+     Desktop.addInternalFrame(alignFrame, newtitle, AlignFrame.DEFAULT_WIDTH,
+             AlignFrame.DEFAULT_HEIGHT);
    }
  
    /**
        al = dna.reverseCdna(complement);
        viewport.addAlignment(al, "");
        addHistoryItem(new EditCommand(
-               MessageManager.getString("label.add_sequences"),
-               Action.PASTE, al.getSequencesArray(), 0, al.getWidth(),
+               MessageManager.getString("label.add_sequences"), Action.PASTE,
+               al.getSequencesArray(), 0, al.getWidth(),
                viewport.getAlignment()));
      } catch (Exception ex)
      {
        } catch (Exception ex)
        {
          System.err.println((ex.toString()));
-         JvOptionPane
-                 .showInternalMessageDialog(Desktop.desktop, MessageManager
-                         .getString("label.couldnt_run_groovy_script"),
-                         MessageManager
-                                 .getString("label.groovy_support_failed"),
-                         JvOptionPane.ERROR_MESSAGE);
+         JvOptionPane.showInternalMessageDialog(Desktop.desktop,
+                 MessageManager.getString("label.couldnt_run_groovy_script"),
+                 MessageManager.getString("label.groovy_support_failed"),
+                 JvOptionPane.ERROR_MESSAGE);
        }
      }
      else
    {
      // include key modifier check in case user selects from menu
      avc.markHighlightedColumns(
-             (actionEvent.getModifiers() & ActionEvent.ALT_MASK) != 0,
-             true,
-             (actionEvent.getModifiers() & (ActionEvent.META_MASK | ActionEvent.CTRL_MASK)) != 0);
+             (actionEvent.getModifiers() & ActionEvent.ALT_MASK) != 0, true,
+             (actionEvent.getModifiers() & (ActionEvent.META_MASK
+                     | ActionEvent.CTRL_MASK)) != 0);
    }
  
    /**
      colourMenu.add(textColour);
      colourMenu.addSeparator();
  
-     ColourMenuHelper.addMenuItems(colourMenu, this,
-             viewport.getAlignment(), false);
+     ColourMenuHelper.addMenuItems(colourMenu, this, viewport.getAlignment(),
+             false);
  
      colourMenu.addSeparator();
      colourMenu.add(conservationMenuItem);
@@@ -30,12 -30,10 +30,12 @@@ import jalview.datamodel.SearchResultsI
  import jalview.datamodel.SequenceFeature;
  import jalview.datamodel.SequenceGroup;
  import jalview.datamodel.SequenceI;
 +import jalview.io.HTMLOutput;
  import jalview.jbgui.GAlignmentPanel;
  import jalview.math.AlignmentDimension;
  import jalview.schemes.ResidueProperties;
  import jalview.structure.StructureSelectionManager;
 +import jalview.util.Comparison;
  import jalview.util.MessageManager;
  import jalview.util.Platform;
  import jalview.viewmodel.ViewportListenerI;
@@@ -72,8 -70,7 +72,7 @@@ import javax.swing.SwingUtilities
   * @version $Revision: 1.161 $
   */
  public class AlignmentPanel extends GAlignmentPanel implements
-         AdjustmentListener, Printable, AlignmentViewPanel,
-         ViewportListenerI
+         AdjustmentListener, Printable, AlignmentViewPanel, ViewportListenerI
  {
    public AlignViewport av;
  
          // is initialised
          if (av.getWrapAlignment())
          {
-           int widthInRes = getSeqPanel().seqCanvas
-                   .getWrappedCanvasWidth(getSeqPanel().seqCanvas.getWidth());
+           int widthInRes = getSeqPanel().seqCanvas.getWrappedCanvasWidth(
+                   getSeqPanel().seqCanvas.getWidth());
            vpRanges.setViewportWidth(widthInRes);
          }
          else
                    / av.getCharWidth();
            int heightInSeq = getSeqPanel().seqCanvas.getHeight()
                    / av.getCharHeight();
-           
            vpRanges.setViewportWidth(widthInRes);
            vpRanges.setViewportHeight(heightInSeq);
          }
      // to prevent drawing old image
      FontMetrics fm = getFontMetrics(av.getFont());
  
-     scalePanelHolder.setPreferredSize(new Dimension(10, av.getCharHeight()
-             + fm.getDescent()));
-     idSpaceFillerPanel1.setPreferredSize(new Dimension(10, av
-             .getCharHeight() + fm.getDescent()));
+     scalePanelHolder.setPreferredSize(
+             new Dimension(10, av.getCharHeight() + fm.getDescent()));
+     idSpaceFillerPanel1.setPreferredSize(
+             new Dimension(10, av.getCharHeight() + fm.getDescent()));
  
      getIdPanel().getIdCanvas().gg = null;
      getSeqPanel().seqCanvas.img = null;
    {
      Container c = new Container();
  
-     FontMetrics fm = c.getFontMetrics(new Font(av.font.getName(),
-             Font.ITALIC, av.font.getSize()));
+     FontMetrics fm = c.getFontMetrics(
+             new Font(av.font.getName(), Font.ITALIC, av.font.getSize()));
  
      AlignmentI al = av.getAlignment();
      int i = 0;
        }
      }
  
-     return new Dimension(maxwidth < 0 ? idWidth : Math.min(maxwidth,
-             idWidth), 12);
+     return new Dimension(
+             maxwidth < 0 ? idWidth : Math.min(maxwidth, idWidth), 12);
    }
  
    /**
     */
    public void highlightSearchResults(SearchResultsI results)
    {
 -    scrollToPosition(results);
 -    getSeqPanel().seqCanvas.highlightSearchResults(results);
 -  }
 +    boolean scrolled = scrollToPosition(results, 0, true, false);
  
 -  /**
 -   * Scroll the view to show the position of the highlighted region in results
 -   * (if any) and redraw the overview
 -   * 
 -   * @param results
 -   */
 -  public boolean scrollToPosition(SearchResultsI results)
 -  {
 -    return scrollToPosition(results, 0, true, false);
 +    boolean noFastPaint = scrolled && av.getWrapAlignment();
 +
 +    getSeqPanel().seqCanvas.highlightSearchResults(results, noFastPaint);
    }
  
    /**
    }
  
    /**
 -   * Scroll the view to show the position of the highlighted region in results
 -   * (if any)
 +   * Scrolls the view (if necessary) to show the position of the first
 +   * highlighted region in results (if any). Answers true if the view was
 +   * scrolled, or false if no matched region was found, or it is already
 +   * visible.
     * 
     * @param results
     * @param verticalOffset
     *          - when set, the overview will be recalculated (takes longer)
     * @param centre
     *          if true, try to centre the search results horizontally in the view
 -   * @return false if results were not found
 +   * @return
     */
 -  public boolean scrollToPosition(SearchResultsI results,
 +  protected boolean scrollToPosition(SearchResultsI results,
            int verticalOffset, boolean redrawOverview, boolean centre)
    {
      int startv, endv, starts, ends;
 -    // TODO: properly locate search results in view when large numbers of hidden
 -    // columns exist before highlighted region
 -    // do we need to scroll the panel?
 -    // TODO: tons of nullpointerexceptions raised here.
 -    if (results != null && results.getSize() > 0 && av != null
 -            && av.getAlignment() != null)
 -    {
 -      int seqIndex = av.getAlignment().findIndex(results);
 -      if (seqIndex == -1)
 -      {
 -        return false;
 -      }
 -      SequenceI seq = av.getAlignment().getSequenceAt(seqIndex);
  
 -      int[] r = results.getResults(seq, 0, av.getAlignment().getWidth());
 -      if (r == null)
 -      {
 -        return false;
 -      }
 -      int start = r[0];
 -      int end = r[1];
 +    if (results == null || results.isEmpty() || av == null
 +            || av.getAlignment() == null)
 +    {
 +      return false;
 +    }
 +    int seqIndex = av.getAlignment().findIndex(results);
 +    if (seqIndex == -1)
 +    {
 +      return false;
 +    }
 +    SequenceI seq = av.getAlignment().getSequenceAt(seqIndex);
  
 -      /*
 -       * To centre results, scroll to positions half the visible width
 -       * left/right of the start/end positions
 -       */
 -      if (centre)
 -      {
 -        int offset = (vpRanges.getEndRes() - vpRanges.getStartRes() + 1) / 2
 -                - 1;
 -        start = Math.max(start - offset, 0);
 -        end = end + offset - 1;
 -      }
 -      if (start < 0)
 -      {
 -        return false;
 -      }
 -      if (end == seq.getEnd())
 -      {
 -        return false;
 -      }
 -      if (av.hasHiddenColumns())
 +    int[] r = results.getResults(seq, 0, av.getAlignment().getWidth());
 +    if (r == null)
 +    {
 +      return false;
 +    }
 +    int start = r[0];
 +    int end = r[1];
 +
 +    /*
 +     * To centre results, scroll to positions half the visible width
 +     * left/right of the start/end positions
 +     */
 +    if (centre)
 +    {
 +      int offset = (vpRanges.getEndRes() - vpRanges.getStartRes() + 1) / 2 - 1;
 +      start = Math.max(start - offset, 0);
 +      end = end + offset - 1;
 +    }
 +    if (start < 0)
 +    {
 +      return false;
 +    }
 +    if (end == seq.getEnd())
 +    {
 +      return false;
 +    }
 +
 +    if (av.hasHiddenColumns())
 +    {
 +      HiddenColumns hidden = av.getAlignment().getHiddenColumns();
 +      start = hidden.findColumnPosition(start);
 +      end = hidden.findColumnPosition(end);
 +      if (start == end)
        {
 -        HiddenColumns hidden = av.getAlignment().getHiddenColumns();
 -        start = hidden.findColumnPosition(start);
 -        end = hidden.findColumnPosition(end);
 -        if (start == end)
 +        if (!hidden.isVisible(r[0]))
          {
 -          if (!hidden.isVisible(r[0]))
 -          {
 -            // don't scroll - position isn't visible
 -            return false;
 -          }
 +          // don't scroll - position isn't visible
 +          return false;
          }
        }
 +    }
  
 -      /*
 -       * allow for offset of target sequence (actually scroll to one above it)
 -       */
 -      seqIndex = Math.max(0, seqIndex - verticalOffset);
 +    /*
 +     * allow for offset of target sequence (actually scroll to one above it)
 +     */
 +    seqIndex = Math.max(0, seqIndex - verticalOffset);
 +    boolean scrollNeeded = true;
  
 -      if (!av.getWrapAlignment())
 +    if (!av.getWrapAlignment())
 +    {
 +      if ((startv = vpRanges.getStartRes()) >= start)
        {
 -        if ((startv = vpRanges.getStartRes()) >= start)
 -        {
 -          /*
 -           * Scroll left to make start of search results visible
 -           */
 -          setScrollValues(start, seqIndex);
 -        }
 -        else if ((endv = vpRanges.getEndRes()) <= end)
 -        {
 -          /*
 -           * Scroll right to make end of search results visible
 -           */
 -          setScrollValues(startv + end - endv, seqIndex);
 -        }
 -        else if ((starts = vpRanges.getStartSeq()) > seqIndex)
 -        {
 -          /*
 -           * Scroll up to make start of search results visible
 -           */
 -          setScrollValues(vpRanges.getStartRes(), seqIndex);
 -        }
 -        else if ((ends = vpRanges.getEndSeq()) <= seqIndex)
 -        {
 -          /*
 -           * Scroll down to make end of search results visible
 -           */
 -          setScrollValues(vpRanges.getStartRes(),
 -                  starts + seqIndex - ends + 1);
 -        }
          /*
 -         * Else results are already visible - no need to scroll
 +         * Scroll left to make start of search results visible
           */
 +        setScrollValues(start, seqIndex);
        }
 -      else
 +      else if ((endv = vpRanges.getEndRes()) <= end)
 +      {
 +        /*
 +         * Scroll right to make end of search results visible
 +         */
 +        setScrollValues(startv + end - endv, seqIndex);
 +      }
 +      else if ((starts = vpRanges.getStartSeq()) > seqIndex)
 +      {
 +        /*
 +         * Scroll up to make start of search results visible
 +         */
 +        setScrollValues(vpRanges.getStartRes(), seqIndex);
 +      }
 +      else if ((ends = vpRanges.getEndSeq()) <= seqIndex)
        {
 -        vpRanges.scrollToWrappedVisible(start);
 +        /*
 +         * Scroll down to make end of search results visible
 +         */
 +        setScrollValues(vpRanges.getStartRes(), starts + seqIndex - ends
 +                + 1);
        }
 +      /*
 +       * Else results are already visible - no need to scroll
 +       */
 +      scrollNeeded = false;
 +    }
 +    else
 +    {
 +      scrollNeeded = vpRanges.scrollToWrappedVisible(start);
      }
  
      paintAlignment(redrawOverview);
 -    return true;
 +
 +    return scrollNeeded;
    }
  
    /**
         */
        if (annotationHeight + alignmentHeight > availableHeight)
        {
-         annotationHeight = Math.min(annotationHeight, availableHeight - 2
-                 * rowHeight);
+         annotationHeight = Math.min(annotationHeight,
+                 availableHeight - 2 * rowHeight);
        }
      }
      else
      }
      hscroll.addNotify();
  
-     annotationScroller.setPreferredSize(new Dimension(annotationScroller
-             .getWidth(), annotationHeight));
+     annotationScroller.setPreferredSize(
+             new Dimension(annotationScroller.getWidth(), annotationHeight));
  
      Dimension e = idPanel.getSize();
      alabels.setSize(new Dimension(e.width, annotationHeight));
        else
        {
          int widthInRes = (canvasWidth / av.getCharWidth()) - 1;
-         int heightInSeq = (getSeqPanel().seqCanvas.getHeight() / av
-                 .getCharHeight()) - 1;
+         int heightInSeq = (getSeqPanel().seqCanvas.getHeight()
+                 / av.getCharHeight()) - 1;
  
          vpRanges.setViewportWidth(widthInRes);
          vpRanges.setViewportHeight(heightInSeq);
      repaint();
    }
  
    /**
     * Adjust row/column scrollers to show a visible position in the alignment.
     * 
        if (av.hasHiddenColumns())
        {
          // reset the width to exclude hidden columns
-         width = av.getAlignment().getHiddenColumns().findColumnPosition(width);
+         width = av.getAlignment().getHiddenColumns()
+                 .findColumnPosition(width);
        }
  
        hextent = getSeqPanel().seqCanvas.getWidth() / av.getCharWidth();
        {
          @Override
          public void run()
-       {
+         {
            // When updating scrolling to use ViewportChange events, this code
            // could not be validated and it is not clear if it is now being
            // called. Log warning here in case it is called and unforeseen
            // problems occur
-           Cache.log
-                   .warn("Unexpected path through code: Wrapped jar file opened with wrap alignment set in preferences");
+           Cache.log.warn(
+                   "Unexpected path through code: Wrapped jar file opened with wrap alignment set in preferences");
  
            // scroll to start of panel
            vpRanges.setStartRes(0);
       * Get the horizontal offset to where we draw the sequences.
       * This is idWidth if using a single Graphics context, else zero.
       */
-     final int alignmentGraphicsOffset = idGraphics != alignmentGraphics ? 0 : idWidth;
+     final int alignmentGraphicsOffset = idGraphics != alignmentGraphics ? 0
+             : idWidth;
  
      FontMetrics fm = getFontMetrics(av.getFont());
      int charHeight = av.getCharHeight();
        endSeq = alignmentHeight;
      }
  
-     int pagesHigh = ((alignmentHeight / totalSeq) + 1)
-             * pageHeight;
+     int pagesHigh = ((alignmentHeight / totalSeq) + 1) * pageHeight;
  
      if (av.isShowAnnotation())
      {
      {
        return Printable.NO_SUCH_PAGE;
      }
-     final int alignmentDrawnHeight = (endSeq - startSeq) * charHeight
-             + 3;
+     final int alignmentDrawnHeight = (endSeq - startSeq) * charHeight + 3;
  
      /*
       * draw the Scale at horizontal offset, then reset to top left (0, 0)
        if (av.isRightAlignIds())
        {
          fm = idGraphics.getFontMetrics();
-         xPos = idWidth
-                 - fm.stringWidth(displayId)
-                 - 4;
+         xPos = idWidth - fm.stringWidth(displayId) - 4;
        }
  
        idGraphics.drawString(displayId, xPos,
         * draw the annotations starting at 
         * (idOffset, alignmentHeight) from (0, scaleHeight)
         */
-       alignmentGraphics.translate(alignmentGraphicsOffset, alignmentDrawnHeight);
+       alignmentGraphics.translate(alignmentGraphicsOffset,
+               alignmentDrawnHeight);
        getAnnotationPanel().renderer.drawComponent(getAnnotationPanel(), av,
                alignmentGraphics, -1, startRes, endRes + 1);
      }
                .findColumnPosition(maxwidth) - 1;
      }
  
-     int resWidth = getSeqPanel().seqCanvas.getWrappedCanvasWidth(pwidth
-             - idWidth);
+     int resWidth = getSeqPanel().seqCanvas
+             .getWrappedCanvasWidth(pwidth - idWidth);
  
      int totalHeight = cHeight * (maxwidth / resWidth + 1);
  
        }
        if (labels != null)
        {
-         pg.translate(-3,
-                 ypos + (av.getAlignment().getHeight() * av.getCharHeight()));
+         pg.translate(-3, ypos
+                 + (av.getAlignment().getHeight() * av.getCharHeight()));
  
          pg.setFont(av.getFont());
          labels.drawComponent(pg, idWidth);
-         pg.translate(
-                 +3,
-                 -ypos
-                         - (av.getAlignment().getHeight() * av
-                                 .getCharHeight()));
+         pg.translate(+3, -ypos
+                 - (av.getAlignment().getHeight() * av.getCharHeight()));
        }
  
        ypos += cHeight;
        return calculateIdWidth(-1).width + 4;
      }
      Integer idwidth = null;
-     if (onscreen
-             || (idwidth = Cache.getIntegerProperty("FIGURE_FIXEDIDWIDTH")) == null)
+     if (onscreen || (idwidth = Cache
+             .getIntegerProperty("FIGURE_FIXEDIDWIDTH")) == null)
      {
        int w = getIdPanel().getWidth();
        return (w > 0 ? w : calculateIdWidth().width + 4);
    {
      int boarderBottomOffset = 5;
      long pSessionId = System.currentTimeMillis();
-     headless = (System.getProperty("java.awt.headless") != null && System
-             .getProperty("java.awt.headless").equals("true"));
+     headless = (System.getProperty("java.awt.headless") != null
+             && System.getProperty("java.awt.headless").equals("true"));
      if (alignFrame != null && !headless)
      {
        if (file != null)
        {
-         alignFrame.setProgressBar(MessageManager.formatMessage(
-                 "status.saving_file", new Object[] { type.getLabel() }),
-                 pSessionId);
+         alignFrame.setProgressBar(MessageManager
+                 .formatMessage("status.saving_file", new Object[]
+                 { type.getLabel() }), pSessionId);
        }
      }
      try
          }
  
          im = new jalview.util.ImageMaker(this, type, imageAction,
-                 aDimension.getWidth(), aDimension.getHeight()
-                         + boarderBottomOffset, file, imageTitle,
-                 alignFrame, pSessionId, headless);
+                 aDimension.getWidth(),
+                 aDimension.getHeight() + boarderBottomOffset, file,
+                 imageTitle, alignFrame, pSessionId, headless);
          Graphics graphics = im.getGraphics();
          if (av.getWrapAlignment())
          {
          {
            if (graphics != null)
            {
-             printUnwrapped(aDimension.getWidth(), aDimension.getHeight(),
-                     0, graphics, graphics);
+             printUnwrapped(aDimension.getWidth(), aDimension.getHeight(), 0,
+                     graphics, graphics);
              im.writeImage();
            }
          }
          // this duplicates the calculation in getWrappedHeight but adjusts for
          // offscreen idWith
          width = alignFrame.getWidth() - vscroll.getPreferredSize().width
-                 - alignFrame.getInsets().left
-                 - alignFrame.getInsets().right - getVisibleIdWidth()
-                 + getVisibleIdWidth(false);
+                 - alignFrame.getInsets().left - alignFrame.getInsets().right
+                 - getVisibleIdWidth() + getVisibleIdWidth(false);
        }
        else
        {
      {
        try
        {
 -        int s, sSize = av.getAlignment().getHeight(), res,
 -                alwidth = av.getAlignment().getWidth(), g, gSize, f, fSize,
 -                sy;
 +        int sSize = av.getAlignment().getHeight();
 +        int alwidth = av.getAlignment().getWidth();
          PrintWriter out = new PrintWriter(new FileWriter(imgMapFile));
 -        out.println(jalview.io.HTMLOutput.getImageMapHTML());
 +        out.println(HTMLOutput.getImageMapHTML());
          out.println("<img src=\"" + imageName
                  + "\" border=\"0\" usemap=\"#Map\" >"
                  + "<map name=\"Map\">");
  
 -        for (s = 0; s < sSize; s++)
 +        for (int s = 0; s < sSize; s++)
          {
 -          sy = s * av.getCharHeight() + scaleHeight;
 +          int sy = s * av.getCharHeight() + scaleHeight;
  
            SequenceI seq = av.getAlignment().getSequenceAt(s);
 -          SequenceFeature[] features = seq.getSequenceFeatures();
            SequenceGroup[] groups = av.getAlignment().findAllGroups(seq);
 -          for (res = 0; res < alwidth; res++)
 +          for (int column = 0; column < alwidth; column++)
            {
 -            StringBuilder text = new StringBuilder();
 +            StringBuilder text = new StringBuilder(512);
              String triplet = null;
              if (av.getAlignment().isNucleotide())
              {
 -              triplet = ResidueProperties.nucleotideName
 -                      .get(seq.getCharAt(res) + "");
 +              triplet = ResidueProperties.nucleotideName.get(seq
 +                      .getCharAt(column) + "");
              }
              else
              {
 -              triplet = ResidueProperties.aa2Triplet
 -                      .get(seq.getCharAt(res) + "");
 +              triplet = ResidueProperties.aa2Triplet.get(seq.getCharAt(column)
 +                      + "");
              }
  
              if (triplet == null)
                continue;
              }
  
 -            int alIndex = seq.findPosition(res);
 -            gSize = groups.length;
 -            for (g = 0; g < gSize; g++)
 +            int seqPos = seq.findPosition(column);
 +            int gSize = groups.length;
 +            for (int g = 0; g < gSize; g++)
              {
                if (text.length() < 1)
                {
                  text.append("<area shape=\"rect\" coords=\"")
 -                        .append((idWidth + res * av.getCharWidth()))
 +                        .append((idWidth + column * av.getCharWidth()))
                          .append(",").append(sy).append(",")
 -                        .append((idWidth + (res + 1) * av.getCharWidth()))
 +                        .append((idWidth + (column + 1) * av.getCharWidth()))
                          .append(",").append((av.getCharHeight() + sy))
                          .append("\"").append(" onMouseOver=\"toolTip('")
 -                        .append(alIndex).append(" ").append(triplet);
 +                        .append(seqPos).append(" ").append(triplet);
                }
  
 -              if (groups[g].getStartRes() < res
 -                      && groups[g].getEndRes() > res)
 +              if (groups[g].getStartRes() < column
 +                      && groups[g].getEndRes() > column)
                {
                  text.append("<br><em>").append(groups[g].getName())
                          .append("</em>");
                }
              }
  
 -            if (features != null)
 +            if (text.length() < 1)
              {
 -              if (text.length() < 1)
 -              {
 -                text.append("<area shape=\"rect\" coords=\"")
 -                        .append((idWidth + res * av.getCharWidth()))
 -                        .append(",").append(sy).append(",")
 -                        .append((idWidth + (res + 1) * av.getCharWidth()))
 -                        .append(",").append((av.getCharHeight() + sy))
 -                        .append("\"").append(" onMouseOver=\"toolTip('")
 -                        .append(alIndex).append(" ").append(triplet);
 -              }
 -              fSize = features.length;
 -              for (f = 0; f < fSize; f++)
 +              text.append("<area shape=\"rect\" coords=\"")
 +                      .append((idWidth + column * av.getCharWidth()))
 +                      .append(",").append(sy).append(",")
 +                      .append((idWidth + (column + 1) * av.getCharWidth()))
 +                      .append(",").append((av.getCharHeight() + sy))
 +                      .append("\"").append(" onMouseOver=\"toolTip('")
 +                      .append(seqPos).append(" ").append(triplet);
 +            }
 +            if (!Comparison.isGap(seq.getCharAt(column)))
 +            {
 +              List<SequenceFeature> features = seq.findFeatures(column, column);
 +              for (SequenceFeature sf : features)
                {
 -
 -                if ((features[f].getBegin() <= seq.findPosition(res))
 -                        && (features[f].getEnd() >= seq.findPosition(res)))
 +                if (sf.isContactFeature())
                  {
 -                  if (features[f].isContactFeature())
 -                  {
 -                    if (features[f].getBegin() == seq.findPosition(res)
 -                            || features[f].getEnd() == seq
 -                                    .findPosition(res))
 -                    {
 -                      text.append("<br>").append(features[f].getType())
 -                              .append(" ").append(features[f].getBegin())
 -                              .append(":").append(features[f].getEnd());
 -                    }
 -                  }
 -                  else
 +                  text.append("<br>").append(sf.getType()).append(" ")
 +                          .append(sf.getBegin()).append(":")
 +                          .append(sf.getEnd());
 +                }
 +                else
 +                {
 +                  text.append("<br>");
 +                  text.append(sf.getType());
 +                  String description = sf.getDescription();
 +                  if (description != null
 +                          && !sf.getType().equals(description))
                    {
 -                    text.append("<br>");
 -                    text.append(features[f].getType());
 -                    if (features[f].getDescription() != null && !features[f]
 -                            .getType().equals(features[f].getDescription()))
 -                    {
 -                      text.append(" ").append(features[f].getDescription());
 -                    }
 -
 -                    if (features[f].getValue("status") != null)
 -                    {
 -                      text.append(" (")
 -                              .append(features[f].getValue("status"))
 -                              .append(")");
 -                    }
 +                    description = description.replace("\"", "&quot;");
 +                    text.append(" ").append(description);
                    }
                  }
 -
 +                String status = sf.getStatus();
 +                if (status != null && !"".equals(status))
 +                {
 +                  text.append(" (").append(status).append(")");
 +                }
 +              }
 +              if (text.length() > 1)
 +              {
 +                text.append("')\"; onMouseOut=\"toolTip()\";  href=\"#\">");
 +                out.println(text.toString());
                }
 -            }
 -            if (text.length() > 1)
 -            {
 -              text.append("')\"; onMouseOut=\"toolTip()\";  href=\"#\">");
 -              out.println(text.toString());
              }
            }
          }
     * @param verticalOffset
     *          the number of visible sequences to show above the mapped region
     */
 -  public void scrollToCentre(SearchResultsI sr, int verticalOffset)
 +  protected void scrollToCentre(SearchResultsI sr, int verticalOffset)
    {
      /*
       * To avoid jumpy vertical scrolling (if some sequences are gapped or not
  
    @Override
    /**
-    * Property change event fired when a change is made to the viewport ranges 
+    * Property change event fired when a change is made to the viewport ranges
     * object associated with this alignment panel's viewport
     */
    public void propertyChange(PropertyChangeEvent evt)
@@@ -34,7 -34,6 +34,7 @@@ import java.awt.Color
  import java.awt.FlowLayout;
  import java.awt.event.ActionEvent;
  import java.awt.event.ActionListener;
 +import java.util.List;
  import java.util.Map;
  
  import javax.swing.BorderFactory;
@@@ -95,8 -94,8 +95,8 @@@ public class AnnotationExporter extend
    public void exportAnnotations(AlignmentPanel ap)
    {
      this.ap = ap;
-     annotations = ap.av.isShowAnnotation() ? null : ap.av.getAlignment()
-             .getAlignmentAnnotation();
+     annotations = ap.av.isShowAnnotation() ? null
+             : ap.av.getAlignment().getAlignmentAnnotation();
      wholeView = true;
      startExportAnnotation();
    }
              jalview.bin.Cache.getProperty("LAST_DIRECTORY"));
  
      chooser.setFileView(new JalviewFileView());
-     chooser.setDialogTitle(features ? MessageManager
-             .getString("label.save_features_to_file") : MessageManager
-             .getString("label.save_annotation_to_file"));
+     chooser.setDialogTitle(features
+             ? MessageManager.getString("label.save_features_to_file")
+             : MessageManager.getString("label.save_annotation_to_file"));
      chooser.setToolTipText(MessageManager.getString("action.save"));
  
      int value = chooser.showSaveDialog(this);
              .getString("label.no_features_on_alignment");
      if (features)
      {
 -      Map<String, FeatureColourI> displayedFeatureColours = ap
 -              .getFeatureRenderer().getDisplayedFeatureCols();
        FeaturesFile formatter = new FeaturesFile();
        SequenceI[] sequences = ap.av.getAlignment().getSequencesArray();
        Map<String, FeatureColourI> featureColours = ap.getFeatureRenderer()
                .getDisplayedFeatureCols();
 +      List<String> featureGroups = ap.getFeatureRenderer()
 +              .getDisplayedFeatureGroups();
        boolean includeNonPositional = ap.av.isShowNPFeats();
        if (GFFFormat.isSelected())
        {
 -        text = new FeaturesFile().printGffFormat(
 -                ap.av.getAlignment().getDataset().getSequencesArray(),
 -                displayedFeatureColours, true, ap.av.isShowNPFeats());
 -        text = formatter.printGffFormat(sequences, featureColours, true,
 -                includeNonPositional);
 +        text = formatter.printGffFormat(sequences, featureColours,
 +                featureGroups, includeNonPositional);
        }
        else
        {
 -        text = new FeaturesFile().printJalviewFormat(
 -                ap.av.getAlignment().getDataset().getSequencesArray(),
 -                displayedFeatureColours, true, ap.av.isShowNPFeats()); // ap.av.featuresDisplayed);
 -        text = formatter.printJalviewFormat(sequences, featureColours, true,
 -                includeNonPositional);
 +        text = formatter.printJalviewFormat(sequences, featureColours,
 +                featureGroups, includeNonPositional);
        }
      }
      else
      {
        String text = getFileContents();
        cap.setText(text);
-       Desktop.addInternalFrame(
-               cap,
-               (features ? MessageManager.formatMessage(
-                       "label.features_for_params",
-                       new String[] { ap.alignFrame.getTitle() })
-                       : MessageManager.formatMessage(
-                               "label.annotations_for_params",
-                               new String[] { ap.alignFrame.getTitle() })),
+       Desktop.addInternalFrame(cap, (features ? MessageManager
+               .formatMessage("label.features_for_params", new String[]
+               { ap.alignFrame.getTitle() })
+               : MessageManager.formatMessage("label.annotations_for_params",
+                       new String[]
+                       { ap.alignFrame.getTitle() })),
                600, 500);
      } catch (OutOfMemoryError oom)
      {
        new OOMWarning((features ? MessageManager.formatMessage(
-               "label.generating_features_for_params",
-               new String[] { ap.alignFrame.getTitle() })
+               "label.generating_features_for_params", new String[]
+               { ap.alignFrame.getTitle() })
                : MessageManager.formatMessage(
                        "label.generating_annotations_for_params",
-                       new String[] { ap.alignFrame.getTitle() })), oom);
+                       new String[]
+                       { ap.alignFrame.getTitle() })),
+               oom);
        cap.dispose();
      }
  
@@@ -61,9 -61,9 +61,9 @@@ import fr.orsay.lri.varna.models.annota
  import fr.orsay.lri.varna.models.rna.ModeleBase;
  import fr.orsay.lri.varna.models.rna.RNA;
  
- public class AppVarna extends JInternalFrame implements SelectionListener,
-         SecondaryStructureListener, InterfaceVARNASelectionListener,
-         VamsasSource
+ public class AppVarna extends JInternalFrame
+         implements SelectionListener, SecondaryStructureListener,
+         InterfaceVARNASelectionListener, VamsasSource
  {
    private static final byte[] PAIRS = new byte[] { '(', ')', '[', ']', '{',
        '}', '<', '>' };
    {
      this(ap);
  
-     String sname = aa.sequenceRef == null ? "secondary structure (alignment)"
+     String sname = aa.sequenceRef == null
+             ? "secondary structure (alignment)"
              : seq.getName() + " structure";
      String theTitle = sname
-             + (aa.sequenceRef == null ? " trimmed to " + seq.getName() : "");
+             + (aa.sequenceRef == null ? " trimmed to " + seq.getName()
+                     : "");
      theTitle = MessageManager.formatMessage("label.varna_params",
-             new String[] { theTitle });
+             new String[]
+             { theTitle });
      setTitle(theTitle);
  
      String gappedTitle = sname + " (with gaps)";
      addModel(gappedModel, gappedTitle);
  
      String trimmedTitle = "trimmed " + sname;
-     RnaModel trimmedModel = new RnaModel(trimmedTitle, aa, seq, null, false);
+     RnaModel trimmedModel = new RnaModel(trimmedTitle, aa, seq, null,
+             false);
      addModel(trimmedModel, trimmedTitle);
      vab.setSelectedIndex(0);
    }
          end = shift.shift(end);
        }
        selectionHighlighter.highlightRegion(rna, start, end);
-       selectionHighlighter.getLastHighlight().setOutlineColor(
-               seqsel.getOutlineColour());
+       selectionHighlighter.getLastHighlight()
+               .setOutlineColor(seqsel.getOutlineColour());
        // TODO - translate column markings to positions on structure if present.
        vab.updateSelectedRNA(rna);
      }
    }
  
    @Override
-   public void onSelectionChanged(BaseList arg0, BaseList arg1, BaseList arg2)
+   public void onSelectionChanged(BaseList arg0, BaseList arg1,
+           BaseList arg2)
    {
      // TODO translate selected regions in VARNA to a selection on the
      // alignpanel.
    {
      if (!model.ann.isValidStruc())
      {
-       throw new IllegalArgumentException("Invalid RNA structure annotation");
+       throw new IllegalArgumentException(
+               "Invalid RNA structure annotation");
      }
  
      /*
      ShiftList offset = new ShiftList();
      int ofstart = -1;
      int sleng = seq.getLength();
  
      for (int i = 0; i < sleng; i++)
      {
 -      if (Comparison.isGap(seqChars[i]))
 +      if (Comparison.isGap(seq.getCharAt(i)))
        {
          if (ofstart == -1)
          {
    {
      if (!model.ann.isValidStruc())
      {
-       throw new IllegalArgumentException("Invalid RNA structure annotation");
+       throw new IllegalArgumentException(
+               "Invalid RNA structure annotation");
      }
  
      try
@@@ -140,8 -140,8 +140,8 @@@ public class CutAndPasteTransfer extend
  
      chooser.setAcceptAllFileFilterUsed(false);
      chooser.setFileView(new JalviewFileView());
-     chooser.setDialogTitle(MessageManager
-             .getString("label.save_text_to_file"));
+     chooser.setDialogTitle(
+             MessageManager.getString("label.save_text_to_file"));
      chooser.setToolTipText(MessageManager.getString("action.save"));
  
      int value = chooser.showSaveDialog(this);
      {
        try
        {
-         PrintWriter out = new PrintWriter(new FileWriter(
-                 chooser.getSelectedFile()));
+         PrintWriter out = new PrintWriter(
+                 new FileWriter(chooser.getSelectedFile()));
  
          out.print(getText());
          out.close();
  
      try
      {
-       textarea.append((String) contents
-               .getTransferData(DataFlavor.stringFlavor));
+       textarea.append(
+               (String) contents.getTransferData(DataFlavor.stringFlavor));
      } catch (Exception ex)
      {
      }
      }
      if (format == null)
      {
-       System.err.println(MessageManager
-               .getString("label.couldnt_read_data"));
+       System.err
+               .println(MessageManager.getString("label.couldnt_read_data"));
        if (!Jalview.isHeadlessMode())
        {
          JvOptionPane.showInternalMessageDialog(Desktop.desktop,
      {
        JvOptionPane.showInternalMessageDialog(Desktop.desktop, MessageManager
                .formatMessage("label.couldnt_read_pasted_text", new String[]
-               { ex.toString() }), MessageManager
-               .getString("label.error_parsing_text"),
+               { ex.toString() }),
+               MessageManager.getString("label.error_parsing_text"),
                JvOptionPane.WARNING_MESSAGE);
      }
  
      if (al != null && al.hasValidSequence())
      {
-       String title = MessageManager.formatMessage(
-               "label.input_cut_paste_params",
-               new String[] { format.getName() });
+       String title = MessageManager
+               .formatMessage("label.input_cut_paste_params", new String[]
+               { format.getName() });
        FeatureSettingsModelI proxyColourScheme = source
                .getFeatureColourScheme();
  
                    AlignFrame.DEFAULT_WIDTH, AlignFrame.DEFAULT_HEIGHT);
            af.getViewport().setShowSequenceFeatures(showSeqFeatures);
            af.getViewport().setFeaturesDisplayed(fd);
 -          ColourSchemeI cs = ColourSchemeMapper
 -                  .getJalviewColourScheme(colourSchemeName, al);
 +          af.setMenusForViewport();
 +          ColourSchemeI cs = ColourSchemeMapper.getJalviewColourScheme(
 +                  colourSchemeName, al);
            if (cs != null)
            {
              af.changeColour(cs);
  
          try
          {
-           af.setMaximum(jalview.bin.Cache.getDefault("SHOW_FULLSCREEN",
-                   false));
+           af.setMaximum(
+                   jalview.bin.Cache.getDefault("SHOW_FULLSCREEN", false));
          } catch (Exception ex)
          {
          }
      }
      else
      {
-       System.err.println(MessageManager
-               .getString("label.couldnt_read_data"));
+       System.err
+               .println(MessageManager.getString("label.couldnt_read_data"));
        if (!Jalview.isHeadlessMode())
        {
          JvOptionPane.showInternalMessageDialog(Desktop.desktop,
@@@ -31,8 -31,6 +31,8 @@@ import java.awt.Dimension
  import java.awt.FlowLayout;
  import java.awt.event.ActionEvent;
  import java.awt.event.ActionListener;
 +import java.awt.event.FocusAdapter;
 +import java.awt.event.FocusEvent;
  import java.awt.event.MouseAdapter;
  import java.awt.event.MouseEvent;
  
@@@ -119,8 -117,9 +119,9 @@@ public class FeatureColourChooser exten
      this.fr = frender;
      this.type = theType;
      ap = fr.ap;
-     String title = MessageManager.formatMessage(
-             "label.graduated_color_for_params", new String[] { theType });
+     String title = MessageManager
+             .formatMessage("label.graduated_color_for_params", new String[]
+             { theType });
      initDialogFrame(this, true, blocking, title, 480, 185);
  
      slider.addChangeListener(new ChangeListener()
        slider.setEnabled(true);
        slider.setValue((int) (cs.getThreshold() * scaleFactor));
        thresholdValue.setEnabled(true);
-       threshline = new GraphLine((max - min) / 2f, "Threshold", Color.black);
+       threshline = new GraphLine((max - min) / 2f, "Threshold",
+               Color.black);
        threshline.value = cs.getThreshold();
      }
  
          thresholdValue_actionPerformed();
        }
      });
 +    thresholdValue.addFocusListener(new FocusAdapter()
 +    {
 +      @Override
 +      public void focusLost(FocusEvent e)
 +      {
 +        thresholdValue_actionPerformed();
 +      }
 +    });
      slider.setPaintLabels(false);
      slider.setPaintTicks(true);
      slider.setBackground(Color.white);
      slider.setEnabled(false);
      slider.setOpaque(false);
      slider.setPreferredSize(new Dimension(100, 32));
-     slider.setToolTipText(MessageManager
-             .getString("label.adjust_threshold"));
+     slider.setToolTipText(
+             MessageManager.getString("label.adjust_threshold"));
      thresholdValue.setEnabled(false);
      thresholdValue.setColumns(7);
      jPanel3.setBackground(Color.white);
      thresholdIsMin.setBackground(Color.white);
-     thresholdIsMin.setText(MessageManager
-             .getString("label.threshold_minmax"));
+     thresholdIsMin
+             .setText(MessageManager.getString("label.threshold_minmax"));
      thresholdIsMin.setToolTipText(MessageManager
              .getString("label.toggle_absolute_relative_display_threshold"));
      thresholdIsMin.addActionListener(new ActionListener()
      colourByLabel.setBackground(Color.white);
      colourByLabel
              .setText(MessageManager.getString("label.colour_by_label"));
-     colourByLabel
-             .setToolTipText(MessageManager
-                     .getString("label.display_features_same_type_different_label_using_different_colour"));
+     colourByLabel.setToolTipText(MessageManager.getString(
+             "label.display_features_same_type_different_label_using_different_colour"));
      colourByLabel.addActionListener(new ActionListener()
      {
        @Override
        /*
         * todo not yet implemented: visual indication of feature threshold
         */
-       threshline = new GraphLine((max - min) / 2f, "Threshold", Color.black);
+       threshline = new GraphLine((max - min) / 2f, "Threshold",
+               Color.black);
      }
  
      if (hasThreshold)
    {
      if (colourEditor != null)
      {
-       System.err
-               .println("IMPLEMENTATION ISSUE: overwriting action listener for FeatureColourChooser");
+       System.err.println(
+               "IMPLEMENTATION ISSUE: overwriting action listener for FeatureColourChooser");
      }
      colourEditor = graduatedColorEditor;
    }
@@@ -41,7 -41,6 +41,7 @@@ import java.awt.event.ItemEvent
  import java.awt.event.ItemListener;
  import java.awt.event.MouseAdapter;
  import java.awt.event.MouseEvent;
 +import java.util.ArrayList;
  import java.util.Arrays;
  import java.util.Comparator;
  import java.util.List;
@@@ -64,8 -63,8 +64,8 @@@ import javax.swing.event.DocumentListen
   * @author $author$
   * @version $Revision$
   */
- public class FeatureRenderer extends
-         jalview.renderer.seqfeatures.FeatureRenderer
+ public class FeatureRenderer
+         extends jalview.renderer.seqfeatures.FeatureRenderer
  {
    /*
     * defaults for creating a new feature are the last created
      if (!create && features.size() > 1)
      {
        /*
 -       * more than one feature at selected position - add a drop-down
 -       * to choose the feature to amend
 +       * more than one feature at selected position - 
 +       * add a drop-down to choose the feature to amend
 +       * space pad text if necessary to make entries distinct
         */
        gridPanel = new JPanel(new GridLayout(4, 1));
        JPanel choosePanel = new JPanel();
-       choosePanel.add(new JLabel(MessageManager
-               .getString("label.select_feature")
-               + ":"));
+       choosePanel.add(new JLabel(
+               MessageManager.getString("label.select_feature") + ":"));
        final JComboBox<String> overlaps = new JComboBox<String>();
 +      List<String> added = new ArrayList<>();
        for (SequenceFeature sf : features)
        {
 -        String text = sf.getType() + "/" + sf.getBegin() + "-" + sf.getEnd()
 -                + " (" + sf.getFeatureGroup() + ")";
 +        String text = String.format("%s/%d-%d (%s)", sf.getType(),
 +                sf.getBegin(), sf.getEnd(), sf.getFeatureGroup());
 +        while (added.contains(text))
 +        {
 +          text += " ";
 +        }
          overlaps.addItem(text);
 +        added.add(text);
        }
        choosePanel.add(overlaps);
  
              highlight.addResult(sequences.get(0), sf.getBegin(),
                      sf.getEnd());
  
 -            alignPanel.getSeqPanel().seqCanvas
 -                    .highlightSearchResults(highlight);
 -
 +            alignPanel.getSeqPanel().seqCanvas.highlightSearchResults(
 +                    highlight, false);
            }
            FeatureColourI col = getFeatureStyle(name.getText());
            if (col == null)
            {
-             col = new FeatureColour(ColorUtils
-                     .createColourFromName(name.getText()));
+             col = new FeatureColour(
+                     ColorUtils.createColourFromName(name.getText()));
            }
            oldcol = fcol = col;
            updateColourButton(mainPanel, colour, col);
      mainPanel.add(gridPanel, BorderLayout.NORTH);
  
      JPanel descriptionPanel = new JPanel();
-     descriptionPanel.add(new JLabel(MessageManager
-             .getString("label.description:"),
-             JLabel.RIGHT));
+     descriptionPanel.add(new JLabel(
+             MessageManager.getString("label.description:"), JLabel.RIGHT));
      description.setFont(JvSwingUtils.getTextAreaFont());
      description.setLineWrap(true);
      descriptionPanel.add(new JScrollPane(description));
            MessageManager.getString("action.cancel") };
      }
  
-     String title = create ? MessageManager
-             .getString("label.create_new_sequence_features")
+     String title = create
+             ? MessageManager.getString("label.create_new_sequence_features")
              : MessageManager.formatMessage("label.amend_delete_features",
-                     new String[] { sequences.get(0).getName() });
+                     new String[]
+                     { sequences.get(0).getName() });
  
      /*
       * show the dialog
  
      FeaturesFile ffile = new FeaturesFile();
  
 -    String enteredType = name.getText().trim();
 +    final String enteredType = name.getText().trim();
 +    final String enteredGroup = group.getText().trim();
 +    final String enteredDescription = description.getText().replaceAll("\n", " ");
 +
      if (reply == JvOptionPane.OK_OPTION && enteredType.length() > 0)
      {
        /*
        if (useLastDefaults)
        {
          lastFeatureAdded = enteredType;
 -        lastFeatureGroupAdded = group.getText().trim();
 +        lastFeatureGroupAdded = enteredGroup;
          // TODO: determine if the null feature group is valid
          if (lastFeatureGroupAdded.length() < 1)
          {
        {
          /*
           * YES_OPTION corresponds to the Amend button
 -         * need to refresh Feature Settings if type, group or colour changed
 +         * need to refresh Feature Settings if type, group or colour changed;
 +         * note we don't force the feature to be visible - the user has been
 +         * warned if a hidden feature type or group was entered
           */
 -        sf.type = enteredType;
 -        sf.featureGroup = group.getText().trim();
 -        sf.description = description.getText().replaceAll("\n", " ");
 -        boolean refreshSettings = (!featureType.equals(sf.type)
 -                || !featureGroup.equals(sf.featureGroup));
 +        boolean refreshSettings = (!featureType.equals(enteredType) || !featureGroup
 +                .equals(enteredGroup));
          refreshSettings |= (fcol != oldcol);
 -
 -        setColour(sf.type, fcol);
 -
 +        setColour(enteredType, fcol);
 +        int newBegin = sf.begin;
 +        int newEnd = sf.end;
          try
          {
 -          sf.begin = ((Integer) start.getValue()).intValue();
 -          sf.end = ((Integer) end.getValue()).intValue();
 +          newBegin = ((Integer) start.getValue()).intValue();
 +          newEnd = ((Integer) end.getValue()).intValue();
          } catch (NumberFormatException ex)
          {
 +          // JSpinner doesn't accept invalid format data :-)
          }
  
 -        ffile.parseDescriptionHTML(sf, false);
 +        /*
 +         * replace the feature by deleting it and adding a new one
 +         * (to ensure integrity of SequenceFeatures data store)
 +         */
 +        sequences.get(0).deleteFeature(sf);
 +        SequenceFeature newSf = new SequenceFeature(sf, enteredType,
 +                newBegin, newEnd, enteredGroup, sf.getScore());
 +        newSf.setDescription(enteredDescription);
 +        ffile.parseDescriptionHTML(newSf, false);
 +        // amend features dialog only updates one sequence at a time
 +        sequences.get(0).addSequenceFeature(newSf);
 +
          if (refreshSettings)
          {
            featuresAdded();
          for (int i = 0; i < sequences.size(); i++)
          {
            SequenceFeature sf = features.get(i);
 -          sf.type = enteredType;
 -          // fix for JAL-1538 - always set feature group here
 -          sf.featureGroup = group.getText().trim();
 -          sf.description = description.getText().replaceAll("\n", " ");
 -          sequences.get(i).addSequenceFeature(sf);
 -          ffile.parseDescriptionHTML(sf, false);
 +          SequenceFeature sf2 = new SequenceFeature(enteredType,
 +                  enteredDescription, sf.getBegin(), sf.getEnd(),
 +                  enteredGroup);
 +          ffile.parseDescriptionHTML(sf2, false);
 +          sequences.get(i).addSequenceFeature(sf2);
          }
  
          setColour(enteredType, fcol);
      {
        String msg = MessageManager.formatMessage("label.warning_hidden",
                MessageManager.getString("label.group"), group);
-       JvOptionPane.showMessageDialog(panel, msg, "", JvOptionPane.OK_OPTION);
+       JvOptionPane.showMessageDialog(panel, msg, "",
+               JvOptionPane.OK_OPTION);
      }
    }
  
@@@ -23,7 -23,7 +23,7 @@@ package jalview.gui
  import jalview.api.FeatureColourI;
  import jalview.api.FeatureSettingsControllerI;
  import jalview.bin.Cache;
 -import jalview.datamodel.SequenceFeature;
 +import jalview.datamodel.AlignmentI;
  import jalview.datamodel.SequenceI;
  import jalview.gui.Help.HelpId;
  import jalview.io.JalviewFileChooser;
@@@ -97,8 -97,8 +97,8 @@@ import javax.swing.table.AbstractTableM
  import javax.swing.table.TableCellEditor;
  import javax.swing.table.TableCellRenderer;
  
- public class FeatureSettings extends JPanel implements
-         FeatureSettingsControllerI
+ public class FeatureSettings extends JPanel
+         implements FeatureSettingsControllerI
  {
    DasSourceBrowser dassourceBrowser;
  
      this.af = af;
      fr = af.getFeatureRenderer();
      // allow transparency to be recovered
-     transparency.setMaximum(100 - (int) ((originalTransparency = fr
-             .getTransparency()) * 100));
+     transparency.setMaximum(100
+             - (int) ((originalTransparency = fr.getTransparency()) * 100));
  
      try
      {
      }
      frame.setMinimumSize(new Dimension(MIN_WIDTH, MIN_HEIGHT));
  
-     frame.addInternalFrameListener(new javax.swing.event.InternalFrameAdapter()
-     {
-       @Override
-       public void internalFrameClosed(
-               javax.swing.event.InternalFrameEvent evt)
-       {
-         fr.removePropertyChangeListener(change);
-         dassourceBrowser.fs = null;
-       };
-     });
+     frame.addInternalFrameListener(
+             new javax.swing.event.InternalFrameAdapter()
+             {
+               @Override
+               public void internalFrameClosed(
+                       javax.swing.event.InternalFrameEvent evt)
+               {
+                 fr.removePropertyChangeListener(change);
+                 dassourceBrowser.fs = null;
+               };
+             });
      frame.setLayer(JLayeredPane.PALETTE_LAYER);
    }
  
    {
      final FeatureColourI featureColour = (FeatureColourI) typeCol;
  
-     JPopupMenu men = new JPopupMenu(MessageManager.formatMessage(
-             "label.settings_for_param", new String[] { type }));
+     JPopupMenu men = new JPopupMenu(MessageManager
+             .formatMessage("label.settings_for_param", new String[]
+             { type }));
      JMenuItem scr = new JMenuItem(
              MessageManager.getString("label.sort_by_score"));
      men.add(scr);
        @Override
        public void actionPerformed(ActionEvent e)
        {
-         me.af.avc.sortAlignmentByFeatureScore(Arrays
-                 .asList(new String[] { type }));
+         me.af.avc
+                 .sortAlignmentByFeatureScore(Arrays.asList(new String[]
+                 { type }));
        }
  
      });
        @Override
        public void actionPerformed(ActionEvent e)
        {
-         me.af.avc.sortAlignmentByFeatureDensity(Arrays
-                 .asList(new String[] { type }));
+         me.af.avc
+                 .sortAlignmentByFeatureDensity(Arrays.asList(new String[]
+                 { type }));
        }
  
      });
                else
                {
                  // probably the color chooser!
-                 table.setValueAt(
-                         new FeatureColour(colorChooser.getColor()),
+                 table.setValueAt(new FeatureColour(colorChooser.getColor()),
                          selectedRow, 1);
                  table.validate();
                  me.updateFeatureRenderer(
                  false, type);
        }
      });
-     JMenuItem clearCols = new JMenuItem(
-             MessageManager.getString("label.select_columns_not_containing"));
+     JMenuItem clearCols = new JMenuItem(MessageManager
+             .getString("label.select_columns_not_containing"));
      clearCols.addActionListener(new ActionListener()
      {
        @Override
    private boolean handlingUpdate = false;
  
    /**
 -   * contains a float[3] for each feature type string. created by setTableData
 +   * holds {featureCount, totalExtent} for each feature type
     */
    Map<String, float[]> typeWidth = null;
  
    @Override
    synchronized public void discoverAllFeatureData()
    {
 -    Vector<String> allFeatures = new Vector<String>();
 -    Vector<String> allGroups = new Vector<String>();
 -    SequenceFeature[] tmpfeatures;
 -    String group;
 -    for (int i = 0; i < af.getViewport().getAlignment().getHeight(); i++)
 -    {
 -      tmpfeatures = af.getViewport().getAlignment().getSequenceAt(i)
 -              .getSequenceFeatures();
 -      if (tmpfeatures == null)
 -      {
 -        continue;
 -      }
 +    Set<String> allGroups = new HashSet<String>();
 +    AlignmentI alignment = af.getViewport().getAlignment();
  
 -      int index = 0;
 -      while (index < tmpfeatures.length)
 +    for (int i = 0; i < alignment.getHeight(); i++)
 +    {
 +      SequenceI seq = alignment.getSequenceAt(i);
 +      for (String group : seq.getFeatures().getFeatureGroups(true))
        {
 -        if (tmpfeatures[index].begin == 0 && tmpfeatures[index].end == 0)
 +        if (group != null && !allGroups.contains(group))
          {
 -          index++;
 -          continue;
 +          allGroups.add(group);
 +          checkGroupState(group);
          }
 -
 -        if (tmpfeatures[index].getFeatureGroup() != null)
 -        {
 -          group = tmpfeatures[index].featureGroup;
 -          if (!allGroups.contains(group))
 -          {
 -            allGroups.addElement(group);
 -            checkGroupState(group);
 -          }
 -        }
 -
 -        if (!allFeatures.contains(tmpfeatures[index].getType()))
 -        {
 -          allFeatures.addElement(tmpfeatures[index].getType());
 -        }
 -        index++;
        }
      }
  
  
    synchronized void resetTable(String[] groupChanged)
    {
 -    if (resettingTable == true)
 +    if (resettingTable)
      {
        return;
      }
      typeWidth = new Hashtable<String, float[]>();
      // TODO: change avWidth calculation to 'per-sequence' average and use long
      // rather than float
 -    float[] avWidth = null;
 -    SequenceFeature[] tmpfeatures;
 -    String group = null, type;
 -    Vector<String> visibleChecks = new Vector<String>();
 +
 +    Set<String> displayableTypes = new HashSet<String>();
      Set<String> foundGroups = new HashSet<String>();
  
 -    // Find out which features should be visible depending on which groups
 -    // are selected / deselected
 -    // and recompute average width ordering
 +    /*
 +     * determine which feature types may be visible depending on 
 +     * which groups are selected, and recompute average width data
 +     */
      for (int i = 0; i < af.getViewport().getAlignment().getHeight(); i++)
      {
  
 -      tmpfeatures = af.getViewport().getAlignment().getSequenceAt(i)
 -              .getSequenceFeatures();
 -      if (tmpfeatures == null)
 -      {
 -        continue;
 -      }
 +      SequenceI seq = af.getViewport().getAlignment().getSequenceAt(i);
  
 -      int index = 0;
 -      while (index < tmpfeatures.length)
 +      /*
 +       * get the sequence's groups for positional features
 +       * and keep track of which groups are visible
 +       */
 +      Set<String> groups = seq.getFeatures().getFeatureGroups(true);
 +      Set<String> visibleGroups = new HashSet<String>();
 +      for (String group : groups)
        {
 -        group = tmpfeatures[index].featureGroup;
 -        foundGroups.add(group);
 -
 -        if (tmpfeatures[index].begin == 0 && tmpfeatures[index].end == 0)
 -        {
 -          index++;
 -          continue;
 -        }
 -
          if (group == null || checkGroupState(group))
          {
 -          type = tmpfeatures[index].getType();
 -          if (!visibleChecks.contains(type))
 -          {
 -            visibleChecks.addElement(type);
 -          }
 -        }
 -        if (!typeWidth.containsKey(tmpfeatures[index].getType()))
 -        {
 -          typeWidth.put(tmpfeatures[index].getType(),
 -                  avWidth = new float[3]);
 +          visibleGroups.add(group);
          }
 -        else
 -        {
 -          avWidth = typeWidth.get(tmpfeatures[index].getType());
 -        }
 -        avWidth[0]++;
 -        if (tmpfeatures[index].getBegin() > tmpfeatures[index].getEnd())
 -        {
 -          avWidth[1] += 1 + tmpfeatures[index].getBegin()
 -                  - tmpfeatures[index].getEnd();
 -        }
 -        else
 +      }
 +      foundGroups.addAll(groups);
 +
 +      /*
 +       * get distinct feature types for visible groups
 +       * record distinct visible types, and their count and total length
 +       */
 +      Set<String> types = seq.getFeatures().getFeatureTypesForGroups(true,
 +              visibleGroups.toArray(new String[visibleGroups.size()]));
 +      for (String type : types)
 +      {
 +        displayableTypes.add(type);
 +        float[] avWidth = typeWidth.get(type);
 +        if (avWidth == null)
          {
 -          avWidth[1] += 1 + tmpfeatures[index].getEnd()
 -                  - tmpfeatures[index].getBegin();
 +          avWidth = new float[2];
 +          typeWidth.put(type, avWidth);
          }
 -        index++;
 +        // todo this could include features with a non-visible group
 +        // - do we greatly care?
 +        // todo should we include non-displayable features here, and only
 +        // update when features are added?
 +        avWidth[0] += seq.getFeatures().getFeatureCount(true, type);
 +        avWidth[1] += seq.getFeatures().getTotalFeatureLength(type);
        }
      }
  
 -    int fSize = visibleChecks.size();
 -    Object[][] data = new Object[fSize][3];
 +    Object[][] data = new Object[displayableTypes.size()][3];
      int dataIndex = 0;
  
      if (fr.hasRenderOrder())
        List<String> frl = fr.getRenderOrder();
        for (int ro = frl.size() - 1; ro > -1; ro--)
        {
 -        type = frl.get(ro);
 +        String type = frl.get(ro);
  
 -        if (!visibleChecks.contains(type))
 +        if (!displayableTypes.contains(type))
          {
            continue;
          }
  
          data[dataIndex][0] = type;
          data[dataIndex][1] = fr.getFeatureStyle(type);
-         data[dataIndex][2] = new Boolean(af.getViewport()
-                 .getFeaturesDisplayed().isVisible(type));
+         data[dataIndex][2] = new Boolean(
+                 af.getViewport().getFeaturesDisplayed().isVisible(type));
          dataIndex++;
 -        visibleChecks.removeElement(type);
 +        displayableTypes.remove(type);
        }
      }
  
 -    fSize = visibleChecks.size();
 -    for (int i = 0; i < fSize; i++)
 +    /*
 +     * process any extra features belonging only to 
 +     * a group which was just selected
 +     */
 +    while (!displayableTypes.isEmpty())
      {
 -      // These must be extra features belonging to the group
 -      // which was just selected
 -      type = visibleChecks.elementAt(i).toString();
 +      String type = displayableTypes.iterator().next();
        data[dataIndex][0] = type;
  
        data[dataIndex][1] = fr.getFeatureStyle(type);
  
        data[dataIndex][2] = new Boolean(true);
        dataIndex++;
 +      displayableTypes.remove(type);
      }
  
      if (originalData == null)
      table.setModel(new FeatureTableModel(data));
      table.getColumnModel().getColumn(0).setPreferredWidth(200);
  
-     groupPanel.setLayout(new GridLayout(fr.getFeatureGroupsSize() / 4 + 1,
-             4));
+     groupPanel.setLayout(
+             new GridLayout(fr.getFeatureGroupsSize() / 4 + 1, 4));
      pruneGroups(foundGroups);
      groupPanel.validate();
  
      JalviewFileChooser chooser = new JalviewFileChooser("fc",
              "Sequence Feature Colours");
      chooser.setFileView(new JalviewFileView());
-     chooser.setDialogTitle(MessageManager
-             .getString("label.load_feature_colours"));
+     chooser.setDialogTitle(
+             MessageManager.getString("label.load_feature_colours"));
      chooser.setToolTipText(MessageManager.getString("action.load"));
  
      int value = chooser.showOpenDialog(this);
  
        try
        {
-         InputStreamReader in = new InputStreamReader(new FileInputStream(
-                 file), "UTF-8");
+         InputStreamReader in = new InputStreamReader(
+                 new FileInputStream(file), "UTF-8");
  
          JalviewUserColours jucs = JalviewUserColours.unmarshal(in);
  
      JalviewFileChooser chooser = new JalviewFileChooser("fc",
              "Sequence Feature Colours");
      chooser.setFileView(new JalviewFileView());
-     chooser.setDialogTitle(MessageManager
-             .getString("label.save_feature_colours"));
+     chooser.setDialogTitle(
+             MessageManager.getString("label.save_feature_colours"));
      chooser.setToolTipText(MessageManager.getString("action.save"));
  
      int value = chooser.showSaveDialog(this);
              col.setRGB(Format.getHexString(fcol.getMaxColour()));
              col.setMin(fcol.getMin());
              col.setMax(fcol.getMax());
-             col.setMinRGB(jalview.util.Format.getHexString(fcol
-                     .getMinColour()));
+             col.setMinRGB(
+                     jalview.util.Format.getHexString(fcol.getMinColour()));
              col.setAutoScale(fcol.isAutoScaled());
              col.setThreshold(fcol.getThreshold());
              col.setColourByLabel(fcol.isColourByLabel());
-             col.setThreshType(fcol.isAboveThreshold() ? "ABOVE" : (fcol
-                     .isBelowThreshold() ? "BELOW" : "NONE"));
+             col.setThreshType(fcol.isAboveThreshold() ? "ABOVE"
+                     : (fcol.isBelowThreshold() ? "BELOW" : "NONE"));
            }
            ucs.addColour(col);
          }
        }
      });
      sortByDens.setFont(JvSwingUtils.getLabelFont());
-     sortByDens.setText(MessageManager
-             .getString("label.sequence_sort_by_density"));
+     sortByDens.setText(
+             MessageManager.getString("label.sequence_sort_by_density"));
      sortByDens.addActionListener(new ActionListener()
      {
        @Override
      });
  
      transparency.setMaximum(70);
-     transparency.setToolTipText(MessageManager
-             .getString("label.transparency_tip"));
+     transparency.setToolTipText(
+             MessageManager.getString("label.transparency_tip"));
      fetchDAS.setText(MessageManager.getString("label.fetch_das_features"));
      fetchDAS.addActionListener(new ActionListener()
      {
    public void noDasSourceActive()
    {
      complete();
-     JvOptionPane
-             .showInternalConfirmDialog(
-                     Desktop.desktop,
-                     MessageManager
-                             .getString("label.no_das_sources_selected_warn"),
-                     MessageManager
-                             .getString("label.no_das_sources_selected_title"),
-                     JvOptionPane.DEFAULT_OPTION,
-                     JvOptionPane.INFORMATION_MESSAGE);
+     JvOptionPane.showInternalConfirmDialog(Desktop.desktop,
+             MessageManager.getString("label.no_das_sources_selected_warn"),
+             MessageManager.getString("label.no_das_sources_selected_title"),
+             JvOptionPane.DEFAULT_OPTION, JvOptionPane.INFORMATION_MESSAGE);
    }
  
    // ///////////////////////////////////////////////////////////////////////
      }
  
      @Override
-     public Component getTableCellRendererComponent(JTable tbl,
-             Object color, boolean isSelected, boolean hasFocus, int row,
-             int column)
+     public Component getTableCellRendererComponent(JTable tbl, Object color,
+             boolean isSelected, boolean hasFocus, int row, int column)
      {
        FeatureColourI cellColour = (FeatureColourI) color;
        // JLabel comp = new JLabel();
@@@ -1765,8 -1796,8 +1762,8 @@@ class FeatureIcon implements Ico
    }
  }
  
- class ColorEditor extends AbstractCellEditor implements TableCellEditor,
-         ActionListener
+ class ColorEditor extends AbstractCellEditor
+         implements TableCellEditor, ActionListener
  {
    FeatureSettings me;
  
@@@ -101,14 -101,15 +101,15 @@@ public class Finder extends GFinde
      frame = new JInternalFrame();
      frame.setContentPane(this);
      frame.setLayer(JLayeredPane.PALETTE_LAYER);
-     frame.addInternalFrameListener(new javax.swing.event.InternalFrameAdapter()
-     {
-       @Override
-       public void internalFrameClosing(InternalFrameEvent e)
-       {
-         closeAction();
-       }
-     });
+     frame.addInternalFrameListener(
+             new javax.swing.event.InternalFrameAdapter()
+             {
+               @Override
+               public void internalFrameClosing(InternalFrameEvent e)
+               {
+                 closeAction();
+               }
+             });
      addEscapeHandler();
      Desktop.addInternalFrame(frame, MessageManager.getString("label.find"),
              MY_WIDTH, MY_HEIGHT);
     */
    private void addEscapeHandler()
    {
-     getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW).put(
-             KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), "Cancel");
+     getRootPane().getInputMap(JComponent.WHEN_IN_FOCUSED_WINDOW)
+             .put(KeyStroke.getKeyStroke(KeyEvent.VK_ESCAPE, 0), "Cancel");
      getRootPane().getActionMap().put("Cancel", new AbstractAction()
      {
        @Override
      });
    }
  
    /**
     * Performs the 'Find Next' action.
     * 
      for (SearchResultMatchI match : searchResults.getResults())
      {
        seqs.add(match.getSequence().getDatasetSequence());
 -      features.add(new SequenceFeature(searchString, desc, null,
 -              match.getStart(), match.getEnd(), desc));
 +      features.add(new SequenceFeature(searchString, desc,
 +              match
 +              .getStart(), match.getEnd(), desc));
      }
  
      if (ap.getSeqPanel().seqCanvas.getFeatureRenderer().amendFeatures(seqs,
        if (doFindAll)
        {
          // then we report the matches that were found
-         String message = (idMatch.size() > 0) ? "" + idMatch.size()
-                 + " IDs" : "";
+         String message = (idMatch.size() > 0) ? "" + idMatch.size() + " IDs"
+                 : "";
          if (searchResults != null)
          {
            if (idMatch.size() > 0 && searchResults.getSize() > 0)
@@@ -48,8 -48,8 +48,8 @@@ import javax.swing.ToolTipManager
   * @author $author$
   * @version $Revision$
   */
- public class IdPanel extends JPanel implements MouseListener,
-         MouseMotionListener, MouseWheelListener
+ public class IdPanel extends JPanel
+         implements MouseListener, MouseMotionListener, MouseWheelListener
  {
    private IdCanvas idCanvas;
  
      }
  
      if ((av.getSelectionGroup() == null)
-             || (!jalview.util.Platform.isControlDown(e) && !e.isShiftDown() && av
-                     .getSelectionGroup() != null))
+             || (!jalview.util.Platform.isControlDown(e) && !e.isShiftDown()
+                     && av.getSelectionGroup() != null))
      {
        av.setSelectionGroup(new SequenceGroup());
        av.getSelectionGroup().setStartRes(0);
    {
      int seq2 = alignPanel.getSeqPanel().findSeq(e);
      Sequence sq = (Sequence) av.getAlignment().getSequenceAt(seq2);
 -    // build a new links menu based on the current links + any non-positional
 -    // features
 +
 +    /*
 +     *  build a new links menu based on the current links
 +     *  and any non-positional features
 +     */
      List<String> nlinks = Preferences.sequenceUrlLinks.getLinksForMenu();
 -    SequenceFeature sfs[] = sq == null ? null : sq.getSequenceFeatures();
 -    if (sfs != null)
 +    for (SequenceFeature sf : sq.getFeatures().getNonPositionalFeatures())
      {
 -      for (SequenceFeature sf : sfs)
 +      if (sf.links != null)
        {
 -        if (sf.begin == sf.end && sf.begin == 0)
 +        for (String link : sf.links)
          {
 -          if (sf.links != null && sf.links.size() > 0)
 -          {
 -            for (int l = 0, lSize = sf.links.size(); l < lSize; l++)
 -            {
 -              nlinks.add(sf.links.elementAt(l));
 -            }
 -          }
 +          nlinks.add(link);
          }
        }
      }
  
      for (int i = start; i <= end; i++)
      {
-       av.getSelectionGroup().addSequence(
-               av.getAlignment().getSequenceAt(i), i == end);
+       av.getSelectionGroup().addSequence(av.getAlignment().getSequenceAt(i),
+               i == end);
      }
    }
  
@@@ -32,7 -32,6 +32,7 @@@ import jalview.datamodel.AlignmentI
  import jalview.datamodel.GraphLine;
  import jalview.datamodel.PDBEntry;
  import jalview.datamodel.RnaViewerModel;
 +import jalview.datamodel.SequenceFeature;
  import jalview.datamodel.SequenceGroup;
  import jalview.datamodel.SequenceI;
  import jalview.datamodel.StructureViewerModel;
@@@ -361,7 -360,8 +361,8 @@@ public class Jalview2XM
    }
  
    public SeqFref newAlcodMapRef(final String sref,
-           final AlignedCodonFrame _cf, final jalview.datamodel.Mapping _jmap)
+           final AlignedCodonFrame _cf,
+           final jalview.datamodel.Mapping _jmap)
    {
  
      SeqFref fref = new SeqFref(sref, "Codon Frame")
            }
          } catch (Exception x)
          {
-           System.err
-                   .println("IMPLEMENTATION ERROR: Failed to resolve forward reference for sequence "
+           System.err.println(
+                   "IMPLEMENTATION ERROR: Failed to resolve forward reference for sequence "
                            + ref.getSref());
            x.printStackTrace();
            failedtoresolve++;
      }
      if (incompleteSeqs != null && incompleteSeqs.size() > 0)
      {
-       System.err.println("Jalview Project Import: There are "
-               + incompleteSeqs.size()
-               + " sequences which may have incomplete metadata.");
+       System.err.println(
+               "Jalview Project Import: There are " + incompleteSeqs.size()
+                       + " sequences which may have incomplete metadata.");
        if (incompleteSeqs.size() < 10)
        {
          for (SequenceI s : incompleteSeqs.values())
        }
        else
        {
-         System.err
-                 .println("Too many to report. Skipping output of incomplete sequences.");
+         System.err.println(
+                 "Too many to report. Skipping output of incomplete sequences.");
        }
      }
    }
        {
          AlignFrame af = frames.get(i);
          // skip ?
-         if (skipList != null
-                 && skipList
-                         .containsKey(af.getViewport().getSequenceSetId()))
+         if (skipList != null && skipList
+                 .containsKey(af.getViewport().getSequenceSetId()))
          {
            continue;
          }
  
            saveState(apanel, fileName, jout, viewIds);
  
-           String dssid = getDatasetIdRef(af.getViewport().getAlignment()
-                   .getDataset());
+           String dssid = getDatasetIdRef(
+                   af.getViewport().getAlignment().getDataset());
            if (!dsses.containsKey(dssid))
            {
              dsses.put(dssid, af);
  
      if (shortName.indexOf(File.separatorChar) > -1)
      {
-       shortName = shortName.substring(shortName
-               .lastIndexOf(File.separatorChar) + 1);
+       shortName = shortName
+               .substring(shortName.lastIndexOf(File.separatorChar) + 1);
      }
  
      int count = 1;
      object.setVamsasModel(new jalview.schemabinding.version2.VamsasModel());
  
      object.setCreationDate(new java.util.Date(System.currentTimeMillis()));
-     object.setVersion(jalview.bin.Cache.getDefault("VERSION",
-             "Development Build"));
+     object.setVersion(
+             jalview.bin.Cache.getDefault("VERSION", "Development Build"));
  
      /**
       * rjal is full height alignment, jal is actual alignment with full metadata
          if (av.hasHiddenRows())
          {
            // use rjal, contains the full height alignment
-           jseq.setHidden(av.getAlignment().getHiddenSequences()
-                   .isHidden(jds));
+           jseq.setHidden(
+                   av.getAlignment().getHiddenSequences().isHidden(jds));
  
            if (av.isHiddenRepSequence(jds))
            {
  
        // TODO: omit sequence features from each alignment view's XML dump if we
        // are storing dataset
 -      if (jds.getSequenceFeatures() != null)
 +      List<jalview.datamodel.SequenceFeature> sfs = jds
 +              .getSequenceFeatures();
 +      for (SequenceFeature sf : sfs)
        {
 -        jalview.datamodel.SequenceFeature[] sf = jds.getSequenceFeatures();
 -        int index = 0;
 -        while (index < sf.length)
 -        {
 -          Features features = new Features();
 +        Features features = new Features();
  
 -          features.setBegin(sf[index].getBegin());
 -          features.setEnd(sf[index].getEnd());
 -          features.setDescription(sf[index].getDescription());
 -          features.setType(sf[index].getType());
 -          features.setFeatureGroup(sf[index].getFeatureGroup());
 -          features.setScore(sf[index].getScore());
 -          if (sf[index].links != null)
 +        features.setBegin(sf.getBegin());
 +        features.setEnd(sf.getEnd());
 +        features.setDescription(sf.getDescription());
 +        features.setType(sf.getType());
 +        features.setFeatureGroup(sf.getFeatureGroup());
 +        features.setScore(sf.getScore());
 +        if (sf.links != null)
 +        {
 +          for (int l = 0; l < sf.links.size(); l++)
            {
 -            for (int l = 0; l < sf[index].links.size(); l++)
 -            {
 -              OtherData keyValue = new OtherData();
 -              keyValue.setKey("LINK_" + l);
 -              keyValue.setValue(sf[index].links.elementAt(l).toString());
 -              features.addOtherData(keyValue);
 -            }
 +            OtherData keyValue = new OtherData();
 +            keyValue.setKey("LINK_" + l);
 +            keyValue.setValue(sf.links.elementAt(l).toString());
 +            features.addOtherData(keyValue);
            }
 -          if (sf[index].otherDetails != null)
 +        }
 +        if (sf.otherDetails != null)
 +        {
 +          String key;
 +          Iterator<String> keys = sf.otherDetails.keySet().iterator();
 +          while (keys.hasNext())
            {
 -            String key;
 -            Iterator<String> keys = sf[index].otherDetails.keySet()
 -                    .iterator();
 -            while (keys.hasNext())
 -            {
 -              key = keys.next();
 -              OtherData keyValue = new OtherData();
 -              keyValue.setKey(key);
 -              keyValue.setValue(sf[index].otherDetails.get(key).toString());
 -              features.addOtherData(keyValue);
 -            }
 +            key = keys.next();
 +            OtherData keyValue = new OtherData();
 +            keyValue.setKey(key);
 +            keyValue.setValue(sf.otherDetails.get(key).toString());
 +            features.addOtherData(keyValue);
            }
 -
 -          jseq.addFeatures(features);
 -          index++;
          }
 +
 +        jseq.addFeatures(features);
        }
  
        if (jdatasq.getAllPDBEntries() != null)
              if (frames[f] instanceof StructureViewerBase)
              {
                StructureViewerBase viewFrame = (StructureViewerBase) frames[f];
-               matchedFile = saveStructureState(ap, jds, pdb, entry,
-                       viewIds, matchedFile, viewFrame);
+               matchedFile = saveStructureState(ap, jds, pdb, entry, viewIds,
+                       matchedFile, viewFrame);
                /*
                 * Only store each structure viewer's state once in the project
                 * jar. First time through only (storeDS==false)
                            viewerState.getBytes());
                  } catch (IOException e)
                  {
-                   System.err.println("Error saving viewer state: "
-                           + e.getMessage());
+                   System.err.println(
+                           "Error saving viewer state: " + e.getMessage());
                  }
                }
              }
            {
              AlcodMap alcmap = new AlcodMap();
              alcmap.setDnasq(seqHash(dnas[m]));
-             alcmap.setMapping(createVamsasMapping(pmaps[m], dnas[m], null,
-                     false));
+             alcmap.setMapping(
+                     createVamsasMapping(pmaps[m], dnas[m], null, false));
              alc.addAlcodMap(alcmap);
              hasMap = true;
            }
          ColourSchemeI colourScheme = sg.getColourScheme();
          if (colourScheme != null)
          {
-           ResidueShaderI groupColourScheme = sg
-                   .getGroupColourScheme();
+           ResidueShaderI groupColourScheme = sg.getGroupColourScheme();
            if (groupColourScheme.conservationApplied())
            {
              jGroup.setConsThreshold(groupColourScheme.getConservationInc());
  
              if (colourScheme instanceof jalview.schemes.UserColourScheme)
              {
-               jGroup.setColour(setUserColourScheme(colourScheme,
-                       userColours, jms));
+               jGroup.setColour(
+                       setUserColourScheme(colourScheme, userColours, jms));
              }
              else
              {
            }
            else if (colourScheme instanceof jalview.schemes.UserColourScheme)
            {
-             jGroup.setColour(setUserColourScheme(colourScheme,
-                     userColours, jms));
+             jGroup.setColour(
+                     setUserColourScheme(colourScheme, userColours, jms));
            }
            else
            {
        // /////////SAVE VIEWPORT
        Viewport view = new Viewport();
        view.setTitle(ap.alignFrame.getTitle());
-       view.setSequenceSetId(makeHashCode(av.getSequenceSetId(),
-               av.getSequenceSetId()));
+       view.setSequenceSetId(
+               makeHashCode(av.getSequenceSetId(), av.getSequenceSetId()));
        view.setId(av.getViewId());
        if (av.getCodingComplement() != null)
        {
          view.setBgColour(setUserColourScheme(av.getGlobalColourScheme(),
                  userColours, jms));
        }
-       else if (av.getGlobalColourScheme() instanceof jalview.schemes.AnnotationColourGradient)
+       else if (av
+               .getGlobalColourScheme() instanceof jalview.schemes.AnnotationColourGradient)
        {
          AnnotationColours ac = constructAnnotationColours(
                  (jalview.schemes.AnnotationColourGradient) av
        }
        else
        {
-         view.setBgColour(ColourSchemeProperty.getColourName(av
-                 .getGlobalColourScheme()));
+         view.setBgColour(ColourSchemeProperty
+                 .getColourName(av.getGlobalColourScheme()));
        }
  
        ResidueShaderI vcs = av.getResidueShading();
                setting.setAutoScale(fcol.isAutoScaled());
                setting.setThreshold(fcol.getThreshold());
                // -1 = No threshold, 0 = Below, 1 = Above
-               setting.setThreshstate(fcol.isAboveThreshold() ? 1 : (fcol
-                       .isBelowThreshold() ? 0 : -1));
+               setting.setThreshstate(fcol.isAboveThreshold() ? 1
+                       : (fcol.isBelowThreshold() ? 0 : -1));
              }
              else
              {
                setting.setColour(fcol.getColour().getRGB());
              }
  
-             setting.setDisplay(av.getFeaturesDisplayed().isVisible(
-                     featureType));
+             setting.setDisplay(
+                     av.getFeaturesDisplayed().isVisible(featureType));
              float rorder = ap.getSeqPanel().seqCanvas.getFeatureRenderer()
                      .getOrder(featureType);
              if (rorder > -1)
            g.setName(grp);
            g.setDisplay(((Boolean) ap.getSeqPanel().seqCanvas
                    .getFeatureRenderer().checkGroupVisibility(grp, false))
-                   .booleanValue());
+                           .booleanValue());
            fs.addGroup(g);
            groupsAdded.addElement(grp);
          }
          }
          else
          {
-           ArrayList<int[]> hiddenRegions = hidden
-                   .getHiddenColumnsCopy();
+           ArrayList<int[]> hiddenRegions = hidden.getHiddenColumnsCopy();
            for (int[] region : hiddenRegions)
            {
              HiddenColumns hc = new HiddenColumns();
          System.out.println("Writing jar entry " + fileName);
          JarEntry entry = new JarEntry(fileName);
          jout.putNextEntry(entry);
-         PrintWriter pout = new PrintWriter(new OutputStreamWriter(jout,
-                 UTF_8));
+         PrintWriter pout = new PrintWriter(
+                 new OutputStreamWriter(jout, UTF_8));
          Marshaller marshaller = new Marshaller(pout);
          marshaller.marshal(object);
          pout.flush();
        final PDBEntry pdbentry = bindingModel.getPdbEntry(peid);
        final String pdbId = pdbentry.getId();
        if (!pdbId.equals(entry.getId())
-               && !(entry.getId().length() > 4 && entry.getId()
-                       .toLowerCase().startsWith(pdbId.toLowerCase())))
+               && !(entry.getId().length() > 4 && entry.getId().toLowerCase()
+                       .startsWith(pdbId.toLowerCase())))
        {
          /*
           * not interested in a binding to a different PDB entry here
        }
        else if (!matchedFile.equals(pdbentry.getFile()))
        {
-         Cache.log
-                 .warn("Probably lost some PDB-Sequence mappings for this structure file (which apparently has same PDB Entry code): "
+         Cache.log.warn(
+                 "Probably lost some PDB-Sequence mappings for this structure file (which apparently has same PDB Entry code): "
                          + pdbentry.getFile());
        }
        // record the
        // match is ambiguous (e.g.
        // 1QIP==1qipA)
  
-       for (int smap = 0; smap < viewFrame.getBinding().getSequence()[peid].length; smap++)
+       for (int smap = 0; smap < viewFrame.getBinding()
+               .getSequence()[peid].length; smap++)
        {
          // if (jal.findIndex(jmol.jmb.sequence[peid][smap]) > -1)
          if (jds == viewFrame.getBinding().getSequence()[peid][smap])
      ac.setAnnotation(acg.getAnnotation().annotationId);
      if (acg.getBaseColour() instanceof UserColourScheme)
      {
-       ac.setColourScheme(setUserColourScheme(acg.getBaseColour(),
-               userColours, jms));
+       ac.setColourScheme(
+               setUserColourScheme(acg.getBaseColour(), userColours, jms));
      }
      else
      {
-       ac.setColourScheme(ColourSchemeProperty.getColourName(acg.getBaseColour()));
+       ac.setColourScheme(
+               ColourSchemeProperty.getColourName(acg.getBaseColour()));
      }
  
      ac.setMaxColour(acg.getMaxColour().getRGB());
          if (groupIdr == null)
          {
            // make a locally unique String
-           groupRefs.put(
-                   annotation.groupRef,
+           groupRefs.put(annotation.groupRef,
                    groupIdr = ("" + System.currentTimeMillis()
-                           + annotation.groupRef.getName() + groupRefs
-                           .size()));
+                           + annotation.groupRef.getName()
+                           + groupRefs.size()));
          }
          an.setGroupRef(groupIdr.toString());
        }
            }
            if (annotation.annotations[a].displayCharacter != null)
            {
-             ae.setDisplayCharacter(annotation.annotations[a].displayCharacter);
+             ae.setDisplayCharacter(
+                     annotation.annotations[a].displayCharacter);
            }
  
            if (!Float.isNaN(annotation.annotations[a].value))
            ae.setPosition(a);
            if (annotation.annotations[a].secondaryStructure > ' ')
            {
-             ae.setSecondaryStructure(annotation.annotations[a].secondaryStructure
-                     + "");
+             ae.setSecondaryStructure(
+                     annotation.annotations[a].secondaryStructure + "");
            }
  
            if (annotation.annotations[a].colour != null
        // need to be able to recover 1) settings 2) user-defined presets or
        // recreate settings from preset 3) predefined settings provided by
        // service - or settings that can be transferred (or discarded)
-       vCalcIdParam.setParameters(settings.getWsParamFile().replace("\n",
-               "|\\n|"));
+       vCalcIdParam.setParameters(
+               settings.getWsParamFile().replace("\n", "|\\n|"));
        vCalcIdParam.setAutoUpdate(settings.isAutoUpdate());
        // todo - decide if updateImmediately is needed for any projects.
  
        }
      }
      throw new Error(MessageManager.formatMessage(
-             "error.unsupported_version_calcIdparam",
-             new Object[] { calcIdParam.toString() }));
+             "error.unsupported_version_calcIdparam", new Object[]
+             { calcIdParam.toString() }));
    }
  
    /**
  
      for (int i = 0; i < 24; i++)
      {
-       newColours[i] = new java.awt.Color(Integer.parseInt(colours
-               .getUserColourScheme().getColour(i).getRGB(), 16));
+       newColours[i] = new java.awt.Color(Integer.parseInt(
+               colours.getUserColourScheme().getColour(i).getRGB(), 16));
      }
  
      jalview.schemes.UserColourScheme ucs = new jalview.schemes.UserColourScheme(
        newColours = new java.awt.Color[23];
        for (int i = 0; i < 23; i++)
        {
-         newColours[i] = new java.awt.Color(Integer.parseInt(colours
-                 .getUserColourScheme().getColour(i + 24).getRGB(), 16));
+         newColours[i] = new java.awt.Color(Integer.parseInt(
+                 colours.getUserColourScheme().getColour(i + 24).getRGB(),
+                 16));
        }
        ucs.setLowerCaseColours(newColours);
      }
            if (true) // !skipViewport(object))
            {
              _af = loadFromObject(object, file, true, jprovider);
-             if (_af != null
-                     && object.getJalviewModelSequence().getViewportCount() > 0)
+             if (_af != null && object.getJalviewModelSequence()
+                     .getViewportCount() > 0)
              {
                if (af == null)
                {
      {
        ex.printStackTrace();
        errorMessage = "Couldn't locate Jalview XML file : " + file;
-       System.err.println("Exception whilst loading jalview XML file : "
-               + ex + "\n");
+       System.err.println(
+               "Exception whilst loading jalview XML file : " + ex + "\n");
      } catch (Exception ex)
      {
        System.err.println("Parsing as Jalview Version 2 file failed.");
        }
        ex.printStackTrace();
  
-       System.err.println("Exception whilst loading jalview XML file : "
-               + ex + "\n");
+       System.err.println(
+               "Exception whilst loading jalview XML file : " + ex + "\n");
      } catch (OutOfMemoryError e)
      {
        // Don't use the OOM Window here
      {
        if (ds.getCodonFrames() != null)
        {
-         StructureSelectionManager.getStructureSelectionManager(
-                 Desktop.instance).registerMappings(ds.getCodonFrames());
+         StructureSelectionManager
+                 .getStructureSelectionManager(Desktop.instance)
+                 .registerMappings(ds.getCodonFrames());
        }
      }
      if (errorMessage != null)
            @Override
            public void run()
            {
-             JvOptionPane
-                     .showInternalMessageDialog(Desktop.desktop,
-                             finalErrorMessage, "Error "
-                                     + (saving ? "saving" : "loading")
-                                     + " Jalview file",
-                             JvOptionPane.WARNING_MESSAGE);
+             JvOptionPane.showInternalMessageDialog(Desktop.desktop,
+                     finalErrorMessage,
+                     "Error " + (saving ? "saving" : "loading")
+                             + " Jalview file",
+                     JvOptionPane.WARNING_MESSAGE);
            }
          });
        }
            if (tmpSeq.getStart() != jseqs[i].getStart()
                    || tmpSeq.getEnd() != jseqs[i].getEnd())
            {
-             System.err
-                     .println("Warning JAL-2154 regression: updating start/end for sequence "
+             System.err.println(
+                     "Warning JAL-2154 regression: updating start/end for sequence "
                              + tmpSeq.toString() + " to " + jseqs[i]);
            }
          }
      }
      else
      {
-       boolean isdsal = object.getJalviewModelSequence().getViewportCount() == 0;
+       boolean isdsal = object.getJalviewModelSequence()
+               .getViewportCount() == 0;
        if (isdsal)
        {
          // we are importing a dataset record, so
            Features[] features = jseqs[i].getFeatures();
            for (int f = 0; f < features.length; f++)
            {
 -            jalview.datamodel.SequenceFeature sf = new jalview.datamodel.SequenceFeature(
 -                    features[f].getType(), features[f].getDescription(),
 -                    features[f].getStatus(), features[f].getBegin(),
 -                    features[f].getEnd(), features[f].getFeatureGroup());
 -
 -            sf.setScore(features[f].getScore());
 +            SequenceFeature sf = new SequenceFeature(features[f].getType(),
 +                    features[f].getDescription(), features[f].getBegin(),
 +                    features[f].getEnd(), features[f].getScore(),
 +                    features[f].getFeatureGroup());
 +            sf.setStatus(features[f].getStatus());
              for (int od = 0; od < features[f].getOtherDataCount(); od++)
              {
                OtherData keyValue = features[f].getOtherData(od);
          {
            // adds dbrefs to datasequence's set (since Jalview 2.10)
            addDBRefs(
-                   al.getSequenceAt(i).getDatasetSequence() == null ? al.getSequenceAt(i)
+                   al.getSequenceAt(i).getDatasetSequence() == null
+                           ? al.getSequenceAt(i)
                            : al.getSequenceAt(i).getDatasetSequence(),
                    vamsasSeq[i]);
          }
                  }
                }
              }
-             StructureSelectionManager.getStructureSelectionManager(
-                     Desktop.instance).registerPDBEntry(entry);
+             StructureSelectionManager
+                     .getStructureSelectionManager(Desktop.instance)
+                     .registerPDBEntry(entry);
              // adds PDBEntry to datasequence's set (since Jalview 2.10)
              if (al.getSequenceAt(i).getDatasetSequence() != null)
              {
                else
                {
                  // defer to later
-                 frefedSequence.add(newAlcodMapRef(maps[m].getDnasq(), cf,
-                         mapping));
+                 frefedSequence.add(
+                         newAlcodMapRef(maps[m].getDnasq(), cf, mapping));
                }
              }
            }
              annotation.setAutoCalculated(true);
            }
          }
-         if (autoForView
-                 || (annotation.hasAutoCalculated() && annotation
-                         .isAutoCalculated()))
+         if (autoForView || (annotation.hasAutoCalculated()
+                 && annotation.isAutoCalculated()))
          {
            // remove ID - we don't recover annotation from other views for
            // view-specific annotation
  
              anot[anpos] = new jalview.datamodel.Annotation(
  
-             ae[aa].getDisplayCharacter(), ae[aa].getDescription(),
-                     (ae[aa].getSecondaryStructure() == null || ae[aa]
-                             .getSecondaryStructure().length() == 0) ? ' '
-                             : ae[aa].getSecondaryStructure().charAt(0),
+                     ae[aa].getDisplayCharacter(), ae[aa].getDescription(),
+                     (ae[aa].getSecondaryStructure() == null
+                             || ae[aa].getSecondaryStructure().length() == 0)
+                                     ? ' '
+                                     : ae[aa].getSecondaryStructure()
+                                             .charAt(0),
                      ae[aa].getValue()
  
              );
            jaa._linecolour = firstColour;
            if (annotation.getThresholdLine() != null)
            {
-             jaa.setThreshold(new jalview.datamodel.GraphLine(annotation
-                     .getThresholdLine().getValue(), annotation
-                     .getThresholdLine().getLabel(), new java.awt.Color(
-                     annotation.getThresholdLine().getColour())));
+             jaa.setThreshold(new jalview.datamodel.GraphLine(
+                     annotation.getThresholdLine().getValue(),
+                     annotation.getThresholdLine().getLabel(),
+                     new java.awt.Color(
+                             annotation.getThresholdLine().getColour())));
  
            }
            if (autoForView || annotation.isAutoCalculated())
            }
            else
            {
-             cs = ColourSchemeProperty.getColourScheme(al, jGroup.getColour());
+             cs = ColourSchemeProperty.getColourScheme(al,
+                     jGroup.getColour());
            }
          }
          int pidThreshold = jGroup.getPidThreshold();
                  jGroup.getDisplayBoxes(), jGroup.getDisplayText(),
                  jGroup.getColourText(), jGroup.getStart(), jGroup.getEnd());
          sg.getGroupColourScheme().setThreshold(pidThreshold, true);
-         sg.getGroupColourScheme().setConservationInc(jGroup.getConsThreshold());
+         sg.getGroupColourScheme()
+                 .setConservationInc(jGroup.getConsThreshold());
          sg.setOutlineColour(new java.awt.Color(jGroup.getOutlineColour()));
  
          sg.textColour = new java.awt.Color(jGroup.getTextCol1());
          sg.textColour2 = new java.awt.Color(jGroup.getTextCol2());
-         sg.setShowNonconserved(jGroup.hasShowUnconserved() ? jGroup
-                 .isShowUnconserved() : false);
+         sg.setShowNonconserved(
+                 jGroup.hasShowUnconserved() ? jGroup.isShowUnconserved()
+                         : false);
          sg.thresholdTextColour = jGroup.getTextColThreshold();
          if (jGroup.hasShowConsensusHistogram())
          {
          }
          if (jGroup.getConsThreshold() != 0)
          {
-           Conservation c = new Conservation("All", sg.getSequences(null),
-                   0, sg.getWidth() - 1);
+           Conservation c = new Conservation("All", sg.getSequences(null), 0,
+                   sg.getWidth() - 1);
            c.calculate();
            c.verdict(false, 25);
            sg.cs.setConservation(c);
          if (jGroup.getId() != null && groupAnnotRefs.size() > 0)
          {
            // re-instate unique group/annotation row reference
-           List<AlignmentAnnotation> jaal = groupAnnotRefs.get(jGroup
-                   .getId());
+           List<AlignmentAnnotation> jaal = groupAnnotRefs
+                   .get(jGroup.getId());
            if (jaal != null)
            {
              for (AlignmentAnnotation jaa : jaal)
      // to the same sequenceSet. We must modify this id on load
      // so that each load of the file gives a unique id
      String uniqueSeqSetId = view.getSequenceSetId() + uniqueSetSuffix;
-     String viewId = (view.getId() == null ? null : view.getId()
-             + uniqueSetSuffix);
+     String viewId = (view.getId() == null ? null
+             : view.getId() + uniqueSetSuffix);
      AlignFrame af = null;
      AlignViewport av = null;
      // now check to see if we really need to create a new viewport.
        // XML.
        // and then recover its containing af to allow the settings to be applied.
        // TODO: fix for vamsas demo
-       System.err
-               .println("About to recover a viewport for existing alignment: Sequence set ID is "
+       System.err.println(
+               "About to recover a viewport for existing alignment: Sequence set ID is "
                        + uniqueSeqSetId);
        Object seqsetobj = retrieveExistingObj(uniqueSeqSetId);
        if (seqsetobj != null)
          if (seqsetobj instanceof String)
          {
            uniqueSeqSetId = (String) seqsetobj;
-           System.err
-                   .println("Recovered extant sequence set ID mapping for ID : New Sequence set ID is "
+           System.err.println(
+                   "Recovered extant sequence set ID mapping for ID : New Sequence set ID is "
                            + uniqueSeqSetId);
          }
          else
          {
-           System.err
-                   .println("Warning : Collision between sequence set ID string and existing jalview object mapping.");
+           System.err.println(
+                   "Warning : Collision between sequence set ID string and existing jalview object mapping.");
          }
  
        }
       * indicate that annotation colours are applied across all groups (pre
       * Jalview 2.8.1 behaviour)
       */
-     boolean doGroupAnnColour = Jalview2XML.isVersionStringLaterThan(
-             "2.8.1", object.getVersion());
+     boolean doGroupAnnColour = Jalview2XML.isVersionStringLaterThan("2.8.1",
+             object.getVersion());
  
      AlignmentPanel ap = null;
      boolean isnewview = true;
        for (int i = 0; i < jseq.getRnaViewerCount(); i++)
        {
          RnaViewer viewer = jseq.getRnaViewer(i);
-         AppVarna appVarna = findOrCreateVarnaViewer(viewer,
-                 uniqueSetSuffix, ap);
+         AppVarna appVarna = findOrCreateVarnaViewer(viewer, uniqueSetSuffix,
+                 ap);
  
          for (int j = 0; j < viewer.getSecondaryStructureCount(); j++)
          {
            SecondaryStructure ss = viewer.getSecondaryStructure(j);
            SequenceI seq = seqRefIds.get(jseq.getId());
-           AlignmentAnnotation ann = this.annotationIds.get(ss
-                   .getAnnotationId());
+           AlignmentAnnotation ann = this.annotationIds
+                   .get(ss.getAnnotationId());
  
            /*
             * add the structure to the Varna display (with session state copied
      /*
       * viewer not found - make it
       */
-     RnaViewerModel model = new RnaViewerModel(postLoadId,
-             viewer.getTitle(), viewer.getXpos(), viewer.getYpos(),
-             viewer.getWidth(), viewer.getHeight(),
-             viewer.getDividerLocation());
+     RnaViewerModel model = new RnaViewerModel(postLoadId, viewer.getTitle(),
+             viewer.getXpos(), viewer.getYpos(), viewer.getWidth(),
+             viewer.getHeight(), viewer.getDividerLocation());
      AppVarna varna = new AppVarna(model, ap);
  
      return varna;
            // TODO: should check if tp has been manipulated by user - if so its
            // settings shouldn't be modified
            tp.setTitle(tree.getTitle());
-           tp.setBounds(new Rectangle(tree.getXpos(), tree.getYpos(), tree
-                   .getWidth(), tree.getHeight()));
+           tp.setBounds(new Rectangle(tree.getXpos(), tree.getYpos(),
+                   tree.getWidth(), tree.getHeight()));
            tp.av = av; // af.viewport; // TODO: verify 'associate with all
            // views'
            // works still
  
          if (tree.getFontName() != null)
          {
-           tp.setTreeFont(new java.awt.Font(tree.getFontName(), tree
-                   .getFontStyle(), tree.getFontSize()));
+           tp.setTreeFont(new java.awt.Font(tree.getFontName(),
+                   tree.getFontStyle(), tree.getFontSize()));
          }
          else
          {
-           tp.setTreeFont(new java.awt.Font(view.getFontName(), view
-                   .getFontStyle(), tree.getFontSize()));
+           tp.setTreeFont(new java.awt.Font(view.getFontName(),
+                   view.getFontStyle(), tree.getFontSize()));
          }
  
          tp.showPlaceholders(tree.getMarkUnlinked());
              // TODO: NOW: check that this recovers the PDB file correctly.
              String pdbFile = loadPDBFile(jprovider, ids[p].getId(),
                      ids[p].getFile());
-             jalview.datamodel.SequenceI seq = seqRefIds.get(jseqs[i]
-                     .getId() + "");
+             jalview.datamodel.SequenceI seq = seqRefIds
+                     .get(jseqs[i].getId() + "");
              if (sviewid == null)
              {
-               sviewid = "_jalview_pre2_4_" + x + "," + y + "," + width
-                       + "," + height;
+               sviewid = "_jalview_pre2_4_" + x + "," + y + "," + width + ","
+                       + height;
              }
              if (!structureViewers.containsKey(sviewid))
              {
              // linkAlignPanel,superposeWithAlignpanel}} from hash
              StructureViewerModel jmoldat = structureViewers.get(sviewid);
              jmoldat.setAlignWithPanel(jmoldat.isAlignWithPanel()
-                     | (structureState.hasAlignwithAlignPanel() ? structureState
-                             .getAlignwithAlignPanel() : false));
+                     | (structureState.hasAlignwithAlignPanel()
+                             ? structureState.getAlignwithAlignPanel()
+                             : false));
  
              /*
               * Default colour by linked panel to false if not specified (e.g.
               */
              boolean colourWithAlignPanel = jmoldat.isColourWithAlignPanel();
              colourWithAlignPanel |= (structureState
-                     .hasColourwithAlignPanel() ? structureState
-                     .getColourwithAlignPanel() : false);
+                     .hasColourwithAlignPanel()
+                             ? structureState.getColourwithAlignPanel()
+                             : false);
              jmoldat.setColourWithAlignPanel(colourWithAlignPanel);
  
              /*
               * pre-2.7 projects)
               */
              boolean colourByViewer = jmoldat.isColourByViewer();
-             colourByViewer &= structureState.hasColourByJmol() ? structureState
-                     .getColourByJmol() : true;
+             colourByViewer &= structureState.hasColourByJmol()
+                     ? structureState.getColourByJmol()
+                     : true;
              jmoldat.setColourByViewer(colourByViewer);
  
              if (jmoldat.getStateData().length() < structureState
                StructureData seqstrmaps = jmoldat.getFileData().get(mapkey);
                if (seqstrmaps == null)
                {
-                 jmoldat.getFileData().put(
-                         mapkey,
+                 jmoldat.getFileData().put(mapkey,
                          seqstrmaps = jmoldat.new StructureData(pdbFile,
                                  ids[p].getId()));
                }
          createOrLinkStructureViewer(entry, af, ap, jprovider);
        } catch (Exception e)
        {
-         System.err.println("Error loading structure viewer: "
-                 + e.getMessage());
+         System.err.println(
+                 "Error loading structure viewer: " + e.getMessage());
          // failed - try the next one
        }
      }
  
      // TODO use StructureViewer as a factory here, see JAL-1761
      final PDBEntry[] pdbArray = pdbs.toArray(new PDBEntry[pdbs.size()]);
-     final SequenceI[][] seqsArray = allseqs.toArray(new SequenceI[allseqs
-             .size()][]);
+     final SequenceI[][] seqsArray = allseqs
+             .toArray(new SequenceI[allseqs.size()][]);
      String newViewId = viewerData.getKey();
  
      ChimeraViewFrame cvf = new ChimeraViewFrame(chimeraSessionFile,
         */
        histbug += 10;
        int diff = histbug == -1 ? -1 : newFileLoc.indexOf(";", histbug);
-       String val = (diff == -1) ? null : newFileLoc
-               .substring(histbug, diff);
+       String val = (diff == -1) ? null
+               : newFileLoc.substring(histbug, diff);
        if (val != null && val.length() >= 4)
        {
          if (val.contains("e")) // eh? what can it be?
        }
      }
  
-     final String[] pdbf = pdbfilenames.toArray(new String[pdbfilenames
-             .size()]);
+     final String[] pdbf = pdbfilenames
+             .toArray(new String[pdbfilenames.size()]);
      final String[] id = pdbids.toArray(new String[pdbids.size()]);
      final SequenceI[][] sq = seqmaps
              .toArray(new SequenceI[seqmaps.size()][]);
            JalviewStructureDisplayI sview = null;
            try
            {
-             sview = new StructureViewer(alf.alignPanel
-                     .getStructureSelectionManager()).createView(
-                     StructureViewer.ViewerType.JMOL, pdbf, id, sq,
-                     alf.alignPanel, svattrib, fileloc, rect, sviewid);
+             sview = new StructureViewer(
+                     alf.alignPanel.getStructureSelectionManager())
+                             .createView(StructureViewer.ViewerType.JMOL,
+                                     pdbf, id, sq, alf.alignPanel, svattrib,
+                                     fileloc, rect, sviewid);
              addNewStructureViewer(sview);
            } catch (OutOfMemoryError ex)
            {
          /*
           * Post jalview 2.4 schema includes structure view id
           */
-         if (sviewid != null
-                 && ((StructureViewerBase) frame).getViewId()
-                         .equals(sviewid))
+         if (sviewid != null && ((StructureViewerBase) frame).getViewId()
+                 .equals(sviewid))
          {
            comp = (StructureViewerBase) frame;
            break; // break added in 2.9
  
      for (int i = 0; i < JSEQ.length; i++)
      {
-       af.viewport.setSequenceColour(af.viewport.getAlignment()
-               .getSequenceAt(i), new java.awt.Color(JSEQ[i].getColour()));
+       af.viewport.setSequenceColour(
+               af.viewport.getAlignment().getSequenceAt(i),
+               new java.awt.Color(JSEQ[i].getColour()));
      }
  
      if (al.hasSeqrep())
          for (int r = 0; r < JSEQ[s].getHiddenSequencesCount(); r++)
          {
            isRepresentative = true;
-           SequenceI sequenceToHide = al.getSequenceAt(JSEQ[s]
-                   .getHiddenSequences(r));
+           SequenceI sequenceToHide = al
+                   .getSequenceAt(JSEQ[s].getHiddenSequences(r));
            hidden.addSequence(sequenceToHide, false);
            // remove from hiddenSeqs list so we don't try to hide it twice
            hiddenSeqs.remove(sequenceToHide);
          }
        }
  
-       SequenceI[] hseqs = hiddenSeqs.toArray(new SequenceI[hiddenSeqs
-               .size()]);
+       SequenceI[] hseqs = hiddenSeqs
+               .toArray(new SequenceI[hiddenSeqs.size()]);
        af.viewport.hideSequence(hseqs);
  
      }
      af.viewport.setIncrement(view.getConsThreshold());
      af.viewport.setShowJVSuffix(view.getShowFullId());
      af.viewport.setRightAlignIds(view.getRightAlignIds());
-     af.viewport.setFont(
-             new java.awt.Font(view.getFontName(), view.getFontStyle(), view
-                     .getFontSize()), true);
+     af.viewport.setFont(new java.awt.Font(view.getFontName(),
+             view.getFontStyle(), view.getFontSize()), true);
      ViewStyleI vs = af.viewport.getViewStyle();
      vs.setScaleProteinAsCdna(view.isScaleProteinAsCdna());
      af.viewport.setViewStyle(vs);
      af.viewport.setTextColour(new java.awt.Color(view.getTextCol1()));
      af.viewport.setTextColour2(new java.awt.Color(view.getTextCol2()));
      af.viewport.setThresholdTextColour(view.getTextColThreshold());
-     af.viewport.setShowUnconserved(view.hasShowUnconserved() ? view
-             .isShowUnconserved() : false);
+     af.viewport.setShowUnconserved(
+             view.hasShowUnconserved() ? view.isShowUnconserved() : false);
      af.viewport.getRanges().setStartRes(view.getStartRes());
  
      if (view.getViewName() != null)
      af.viewport.setGlobalColourScheme(cs);
      af.viewport.getResidueShading().setThreshold(view.getPidThreshold(),
              view.getIgnoreGapsinConsensus());
-     af.viewport.getResidueShading().setConsensus(
-             af.viewport.getSequenceConsensusHash());
+     af.viewport.getResidueShading()
+             .setConsensus(af.viewport.getSequenceConsensusHash());
      af.viewport.setColourAppliesToAllGroups(false);
  
      if (view.getConservationSelected() && cs != null)
      {
-       af.viewport.getResidueShading().setConservationInc(
-               view.getConsThreshold());
+       af.viewport.getResidueShading()
+               .setConservationInc(view.getConsThreshold());
      }
  
      af.changeColour(cs);
      }
      if (view.hasShowConsensusHistogram())
      {
-       af.viewport.setShowConsensusHistogram(view
-               .getShowConsensusHistogram());
+       af.viewport
+               .setShowConsensusHistogram(view.getShowConsensusHistogram());
      }
      else
      {
        Map<String, FeatureColourI> featureColours = new Hashtable<>();
        Map<String, Float> featureOrder = new Hashtable<>();
  
-       for (int fs = 0; fs < jms.getFeatureSettings().getSettingCount(); fs++)
+       for (int fs = 0; fs < jms.getFeatureSettings()
+               .getSettingCount(); fs++)
        {
          Setting setting = jms.getFeatureSettings().getSetting(fs);
          if (setting.hasMincolour())
          {
-           FeatureColourI gc = setting.hasMin() ? new FeatureColour(
-                   new Color(setting.getMincolour()), new Color(
-                           setting.getColour()), setting.getMin(),
-                   setting.getMax()) : new FeatureColour(new Color(
-                   setting.getMincolour()), new Color(setting.getColour()),
-                   0, 1);
+           FeatureColourI gc = setting.hasMin()
+                   ? new FeatureColour(new Color(setting.getMincolour()),
+                           new Color(setting.getColour()), setting.getMin(),
+                           setting.getMax())
+                   : new FeatureColour(new Color(setting.getMincolour()),
+                           new Color(setting.getColour()), 0, 1);
            if (setting.hasThreshold())
            {
              gc.setThreshold(setting.getThreshold());
          }
          else
          {
-           featureColours.put(setting.getType(), new FeatureColour(
-                   new Color(setting.getColour())));
+           featureColours.put(setting.getType(),
+                   new FeatureColour(new Color(setting.getColour())));
          }
          renderOrder[fs] = setting.getType();
          if (setting.hasOrder())
          }
          else
          {
-           featureOrder.put(setting.getType(), new Float(fs
-                   / jms.getFeatureSettings().getSettingCount()));
+           featureOrder.put(setting.getType(), new Float(
+                   fs / jms.getFeatureSettings().getSettingCount()));
          }
          if (setting.getDisplay())
          {
        // FeatureRendererSettings frs = new FeatureRendererSettings(renderOrder,
        // fgtable, featureColours, jms.getFeatureSettings().hasTransparency() ?
        // jms.getFeatureSettings().getTransparency() : 0.0, featureOrder);
-       FeatureRendererSettings frs = new FeatureRendererSettings(
-               renderOrder, fgtable, featureColours, 1.0f, featureOrder);
+       FeatureRendererSettings frs = new FeatureRendererSettings(renderOrder,
+               fgtable, featureColours, 1.0f, featureOrder);
        af.alignPanel.getSeqPanel().seqCanvas.getFeatureRenderer()
                .transferSettings(frs);
  
      {
        for (int c = 0; c < view.getHiddenColumnsCount(); c++)
        {
-         af.viewport.hideColumns(view.getHiddenColumns(c).getStart(), view
-                 .getHiddenColumns(c).getEnd() // +1
-                 );
+         af.viewport.hideColumns(view.getHiddenColumns(c).getStart(),
+                 view.getHiddenColumns(c).getEnd() // +1
+         );
        }
      }
      if (view.getCalcIdParam() != null)
      /*
       * pre 2.10.2: saved annotationId is AlignmentAnnotation.label
       */
-     if (matchedAnnotation == null && annAlignment.getAlignmentAnnotation() != null)
+     if (matchedAnnotation == null
+             && annAlignment.getAlignmentAnnotation() != null)
      {
        for (int i = 0; i < annAlignment.getAlignmentAnnotation().length; i++)
        {
      }
      if (matchedAnnotation.getThreshold() == null)
      {
-       matchedAnnotation.setThreshold(new GraphLine(viewAnnColour.getThreshold(),
-               "Threshold", Color.black));
+       matchedAnnotation.setThreshold(new GraphLine(
+               viewAnnColour.getThreshold(), "Threshold", Color.black));
      }
  
      AnnotationColourGradient cs = null;
      if (viewAnnColour.getColourScheme().equals("None"))
      {
-       cs = new AnnotationColourGradient(matchedAnnotation, new Color(
-               viewAnnColour.getMinColour()), new Color(
-               viewAnnColour.getMaxColour()),
+       cs = new AnnotationColourGradient(matchedAnnotation,
+               new Color(viewAnnColour.getMinColour()),
+               new Color(viewAnnColour.getMaxColour()),
                viewAnnColour.getAboveThreshold());
      }
      else if (viewAnnColour.getColourScheme().startsWith("ucs"))
      {
-       cs = new AnnotationColourGradient(matchedAnnotation, getUserColourScheme(
-               jms, viewAnnColour.getColourScheme()),
+       cs = new AnnotationColourGradient(matchedAnnotation,
+               getUserColourScheme(jms, viewAnnColour.getColourScheme()),
                viewAnnColour.getAboveThreshold());
      }
      else
        for (JvAnnotRow auan : autoAlan)
        {
          visan.put(auan.template.label
-                 + (auan.template.getCalcId() == null ? "" : "\t"
-                         + auan.template.getCalcId()), auan);
+                 + (auan.template.getCalcId() == null ? ""
+                         : "\t" + auan.template.getCalcId()),
+                 auan);
        }
        int hSize = al.getAlignmentAnnotation().length;
        List<JvAnnotRow> reorder = new ArrayList<>();
        return false;
      }
      String id;
-     if (skipList.containsKey(id = object.getJalviewModelSequence()
-             .getViewport()[0].getSequenceSetId()))
+     if (skipList.containsKey(
+             id = object.getJalviewModelSequence().getViewport()[0]
+                     .getSequenceSetId()))
      {
        if (Cache.log != null && Cache.log.isDebugEnabled())
        {
    private void recoverDatasetFor(SequenceSet vamsasSet, AlignmentI al,
            boolean ignoreUnrefed)
    {
-     jalview.datamodel.AlignmentI ds = getDatasetFor(vamsasSet
-             .getDatasetId());
+     jalview.datamodel.AlignmentI ds = getDatasetFor(
+             vamsasSet.getDatasetId());
      Vector dseqs = null;
      if (ds == null)
      {
          }
          // TODO: merges will never happen if we 'know' we have the real dataset
          // sequence - this should be detected when id==dssid
-         System.err
-                 .println("DEBUG Notice:  Merged dataset sequence (if you see this often, post at http://issues.jalview.org/browse/JAL-1474)"); // ("
+         System.err.println(
+                 "DEBUG Notice:  Merged dataset sequence (if you see this often, post at http://issues.jalview.org/browse/JAL-1474)"); // ("
          // + (pre ? "prepended" : "") + " "
          // + (post ? "appended" : ""));
        }
      {
        DBRef dr = sequence.getDBRef(d);
        jalview.datamodel.DBRefEntry entry = new jalview.datamodel.DBRefEntry(
-               sequence.getDBRef(d).getSource(), sequence.getDBRef(d)
-                       .getVersion(), sequence.getDBRef(d).getAccessionId());
+               sequence.getDBRef(d).getSource(),
+               sequence.getDBRef(d).getVersion(),
+               sequence.getDBRef(d).getAccessionId());
        if (dr.getMapping() != null)
        {
          entry.setMap(addMapping(dr.getMapping()));
        fto[_i] = mf.getStart();
        fto[_i + 1] = mf.getEnd();
      }
-     jalview.datamodel.Mapping jmap = new jalview.datamodel.Mapping(dsto,
-             fr, fto, (int) m.getMapFromUnit(), (int) m.getMapToUnit());
+     jalview.datamodel.Mapping jmap = new jalview.datamodel.Mapping(dsto, fr,
+             fto, (int) m.getMapFromUnit(), (int) m.getMapToUnit());
      if (m.getMappingChoice() != null)
      {
        MappingChoice mc = m.getMappingChoice();
          }
          else
          {
-           System.err
-                   .println("Warning - making up dataset sequence id for DbRef sequence map reference");
+           System.err.println(
+                   "Warning - making up dataset sequence id for DbRef sequence map reference");
            sqid = ((Object) ms).toString(); // make up a new hascode for
            // undefined dataset sequence hash
            // (unlikely to happen)
@@@ -36,7 -36,6 +36,7 @@@ import jalview.binding.Tree
  import jalview.binding.UserColours;
  import jalview.binding.Viewport;
  import jalview.datamodel.PDBEntry;
 +import jalview.datamodel.SequenceFeature;
  import jalview.io.FileFormat;
  import jalview.schemes.ColourSchemeI;
  import jalview.schemes.ColourSchemeProperty;
@@@ -94,8 -93,8 +94,8 @@@ public class Jalview2XML_V
  
      for (int i = 0; i < csize; i++)
      {
-       newColours[i] = new java.awt.Color(Integer.parseInt(colours
-               .getUserColourScheme().getColour(i).getRGB(), 16));
+       newColours[i] = new java.awt.Color(Integer.parseInt(
+               colours.getUserColourScheme().getColour(i).getRGB(), 16));
      }
  
      return new jalview.schemes.UserColourScheme(newColours);
            public void run()
            {
  
-             System.err.println("Couldn't locate Jalview XML file : " + ex
-                     + "\n");
+             System.err.println(
+                     "Couldn't locate Jalview XML file : " + ex + "\n");
              JvOptionPane.showInternalMessageDialog(Desktop.desktop,
                      MessageManager.formatMessage("label.couldnt_locate",
-                             new String[] { file }), MessageManager
-                             .getString("label.url_not_found"),
+                             new String[]
+                             { file }),
+                     MessageManager.getString("label.url_not_found"),
                      JvOptionPane.WARNING_MESSAGE);
            }
          });
              JvOptionPane.showInternalMessageDialog(Desktop.desktop,
                      MessageManager.formatMessage(
                              "label.error_loading_file_params", new String[]
-                             { file }), MessageManager
+                             { file }),
+                     MessageManager
                              .getString("label.error_loading_jalview_file"),
                      JvOptionPane.WARNING_MESSAGE);
            }
          Features[] features = JSEQ[i].getFeatures();
          for (int f = 0; f < features.length; f++)
          {
 -          jalview.datamodel.SequenceFeature sf = new jalview.datamodel.SequenceFeature(
 -                  features[f].getType(), features[f].getDescription(),
 -                  features[f].getStatus(), features[f].getBegin(),
 +          SequenceFeature sf = new SequenceFeature(features[f].getType(),
 +                  features[f].getDescription(), features[f].getBegin(),
                    features[f].getEnd(), null);
 -
 +          sf.setStatus(features[f].getStatus());
            al.getSequenceAt(i).getDatasetSequence().addSequenceFeature(sf);
          }
        }
              }
            }
            al.getSequenceAt(i).getDatasetSequence().addPDBId(entry);
-           StructureSelectionManager.getStructureSelectionManager(
-                   Desktop.instance).registerPDBEntry(entry);
+           StructureSelectionManager
+                   .getStructureSelectionManager(Desktop.instance)
+                   .registerPDBEntry(entry);
          }
  
        }
  
      for (int i = 0; i < JSEQ.length; i++)
      {
-       af.viewport.setSequenceColour(af.viewport.getAlignment()
-               .getSequenceAt(i), new java.awt.Color(JSEQ[i].getColour()));
+       af.viewport.setSequenceColour(
+               af.viewport.getAlignment().getSequenceAt(i),
+               new java.awt.Color(JSEQ[i].getColour()));
      }
  
      // af.changeColour() );
            }
            else
            {
-             cs = ColourSchemeProperty.getColourScheme(al, groups[i].getColour());
+             cs = ColourSchemeProperty.getColourScheme(al,
+                     groups[i].getColour());
            }
          }
          int pidThreshold = groups[i].getPidThreshold();
                  groups[i].getStart(), groups[i].getEnd());
          sg.getGroupColourScheme().setThreshold(pidThreshold, true);
  
-         sg.setOutlineColour(new java.awt.Color(groups[i].getOutlineColour()));
+         sg.setOutlineColour(
+                 new java.awt.Color(groups[i].getOutlineColour()));
  
          if (groups[i].getConsThreshold() != 0)
          {
-           Conservation c = new Conservation("All", sg.getSequences(null),
-                   0, sg.getWidth() - 1);
+           Conservation c = new Conservation("All", sg.getSequences(null), 0,
+                   sg.getWidth() - 1);
            c.calculate();
            c.verdict(false, 25);
            sg.cs.setConservation(c);
      af.viewport.setColourText(view.getShowColourText());
      af.viewport.setConservationSelected(view.getConservationSelected());
      af.viewport.setShowJVSuffix(view.getShowFullId());
-     af.viewport.setFont(
-             new java.awt.Font(view.getFontName(), view.getFontStyle(), view
-                     .getFontSize()), true);
+     af.viewport.setFont(new java.awt.Font(view.getFontName(),
+             view.getFontStyle(), view.getFontSize()), true);
  
      af.viewport.setRenderGaps(view.getRenderGaps());
      af.viewport.setWrapAlignment(view.getWrapAlignment());
        // }
      }
  
-     af.viewport.getResidueShading().setThreshold(
-             view.getPidThreshold(), true);
-     af.viewport.getResidueShading().setConsensus(
-             af.viewport.getSequenceConsensusHash());
+     af.viewport.getResidueShading().setThreshold(view.getPidThreshold(),
+             true);
+     af.viewport.getResidueShading()
+             .setConsensus(af.viewport.getSequenceConsensusHash());
      af.viewport.setColourAppliesToAllGroups(false);
      af.alignPanel.updateLayout();
      af.changeColour(cs);
      if (view.getConservationSelected() && cs != null)
      {
-       af.viewport.getResidueShading().setConservationInc(
-               view.getConsThreshold());
+       af.viewport.getResidueShading()
+               .setConservationInc(view.getConsThreshold());
      }
  
      af.viewport.setColourAppliesToAllGroups(true);
        Hashtable featureColours = new Hashtable();
        String[] renderOrder = new String[jms.getFeatureSettings()
                .getSettingCount()];
-       for (int fs = 0; fs < jms.getFeatureSettings().getSettingCount(); fs++)
+       for (int fs = 0; fs < jms.getFeatureSettings()
+               .getSettingCount(); fs++)
        {
          Setting setting = jms.getFeatureSettings().getSetting(fs);
  
                    new Integer(setting.getColour()));
          }
        }
-       FeatureRendererSettings frs = new FeatureRendererSettings(
-               renderOrder, new Hashtable(), featureColours, 1.0f, null);
+       FeatureRendererSettings frs = new FeatureRendererSettings(renderOrder,
+               new Hashtable(), featureColours, 1.0f, null);
        af.alignPanel.getSeqPanel().seqCanvas.getFeatureRenderer()
                .transferSettings(frs);
      }
  
            if (tree.getFontName() != null)
            {
-             tp.setTreeFont(new java.awt.Font(tree.getFontName(), tree
-                     .getFontStyle(), tree.getFontSize()));
+             tp.setTreeFont(new java.awt.Font(tree.getFontName(),
+                     tree.getFontStyle(), tree.getFontSize()));
            }
            else
            {
-             tp.setTreeFont(new java.awt.Font(view.getFontName(), view
-                     .getFontStyle(), tree.getFontSize()));
+             tp.setTreeFont(new java.awt.Font(view.getFontName(),
+                     view.getFontStyle(), tree.getFontSize()));
            }
  
            tp.showPlaceholders(tree.getMarkUnlinked());
@@@ -180,7 -180,8 +180,8 @@@ public class PopupMenu extends JPopupMe
     * @param seq
     *          DOCUMENT ME!
     */
-   public PopupMenu(final AlignmentPanel ap, Sequence seq, List<String> links)
+   public PopupMenu(final AlignmentPanel ap, Sequence seq,
+           List<String> links)
    {
      this(ap, seq, links, null);
    }
       * 'reference annotations' that may be added to the alignment. First for the
       * currently selected sequence (if there is one):
       */
-     final List<SequenceI> selectedSequence = (seq == null ? Collections
-             .<SequenceI> emptyList() : Arrays.asList(seq));
+     final List<SequenceI> selectedSequence = (seq == null
+             ? Collections.<SequenceI> emptyList()
+             : Arrays.asList(seq));
      buildAnnotationTypesMenus(seqShowAnnotationsMenu,
              seqHideAnnotationsMenu, selectedSequence);
      configureReferenceAnnotationsMenu(seqAddReferenceAnnotations,
      /*
       * And repeat for the current selection group (if there is one):
       */
-     final List<SequenceI> selectedGroup = (ap.av.getSelectionGroup() == null ? Collections
-             .<SequenceI> emptyList() : ap.av.getSelectionGroup()
-             .getSequences());
+     final List<SequenceI> selectedGroup = (ap.av.getSelectionGroup() == null
+             ? Collections.<SequenceI> emptyList()
+             : ap.av.getSelectionGroup().getSequences());
      buildAnnotationTypesMenus(groupShowAnnotationsMenu,
              groupHideAnnotationsMenu, selectedGroup);
      configureReferenceAnnotationsMenu(groupAddReferenceAnnotations,
        sequenceMenu.setText(sequence.getName());
        if (seq == ap.av.getAlignment().getSeqrep())
        {
-         makeReferenceSeq.setText(MessageManager
-                 .getString("action.unmark_as_reference"));
+         makeReferenceSeq.setText(
+                 MessageManager.getString("action.unmark_as_reference"));
        }
        else
        {
-         makeReferenceSeq.setText(MessageManager
-                 .getString("action.set_as_reference"));
+         makeReferenceSeq.setText(
+                 MessageManager.getString("action.set_as_reference"));
        }
  
        if (!ap.av.getAlignment().isNucleotide())
                 */
                menuItem = new JMenuItem();
                menuItem.setText(MessageManager.formatMessage(
-                       "label.2d_rna_structure_line",
-                       new Object[] { aa.label }));
+                       "label.2d_rna_structure_line", new Object[]
+                       { aa.label }));
                menuItem.addActionListener(new ActionListener()
                {
                  @Override
                // TODO: make rnastrucF a bit more nice
                menuItem = new JMenuItem();
                menuItem.setText(MessageManager.formatMessage(
-                       "label.2d_rna_sequence_name",
-                       new Object[] { seq.getName() }));
+                       "label.2d_rna_sequence_name", new Object[]
+                       { seq.getName() }));
                menuItem.addActionListener(new ActionListener()
                {
                  @Override
        if (ap.av.getSelectionGroup() != null
                && ap.av.getSelectionGroup().getSize() > 1)
        {
-         menuItem = new JMenuItem(MessageManager.formatMessage(
-                 "label.represent_group_with",
-                 new Object[] { seq.getName() }));
+         menuItem = new JMenuItem(MessageManager
+                 .formatMessage("label.represent_group_with", new Object[]
+                 { seq.getName() }));
          menuItem.addActionListener(new ActionListener()
          {
            @Override
      }
  
      SequenceGroup sg = ap.av.getSelectionGroup();
-     boolean isDefinedGroup = (sg != null) ? ap.av.getAlignment()
-             .getGroups().contains(sg) : false;
+     boolean isDefinedGroup = (sg != null)
+             ? ap.av.getAlignment().getGroups().contains(sg)
+             : false;
  
      if (sg != null && sg.getSize() > 0)
      {
          buildGroupURLMenu(sg, groupLinks);
        }
        // Add a 'show all structures' for the current selection
-       Hashtable<String, PDBEntry> pdbe = new Hashtable<String, PDBEntry>(), reppdb = new Hashtable<String, PDBEntry>();
+       Hashtable<String, PDBEntry> pdbe = new Hashtable<String, PDBEntry>(),
+               reppdb = new Hashtable<String, PDBEntry>();
        SequenceI sqass = null;
        for (SequenceI sq : ap.av.getSequenceSelection())
        {
        }
        if (pdbe.size() > 0)
        {
-         final PDBEntry[] pe = pdbe.values().toArray(
-                 new PDBEntry[pdbe.size()]), pr = reppdb.values().toArray(
-                 new PDBEntry[reppdb.size()]);
+         final PDBEntry[] pe = pdbe.values()
+                 .toArray(new PDBEntry[pdbe.size()]),
+                 pr = reppdb.values().toArray(new PDBEntry[reppdb.size()]);
          final JMenuItem gpdbview, rpdbview;
        }
      }
      showMenu.removeAll();
      hideMenu.removeAll();
  
-     final List<String> all = Arrays.asList(new String[] { MessageManager
-             .getString("label.all") });
-     addAnnotationTypeToShowHide(showMenu, forSequences, "", all, true, true);
+     final List<String> all = Arrays
+             .asList(new String[]
+             { MessageManager.getString("label.all") });
+     addAnnotationTypeToShowHide(showMenu, forSequences, "", all, true,
+             true);
      addAnnotationTypeToShowHide(hideMenu, forSequences, "", all, true,
              false);
      showMenu.addSeparator();
      for (int sq = 0; sq < seqs.length; sq++)
      {
  
-       int start = seqs[sq].findPosition(sg.getStartRes()), end = seqs[sq]
-               .findPosition(sg.getEndRes());
+       int start = seqs[sq].findPosition(sg.getStartRes()),
+               end = seqs[sq].findPosition(sg.getEndRes());
        // just collect ids from dataset sequence
        // TODO: check if IDs collected from selecton group intersects with the
        // current selection, too
  
            if (((String[]) sarray[1])[sq] == null)
            {
-             if (!dbr[d].hasMap()
-                     || (dbr[d].getMap().locateMappedRange(start, end) != null))
+             if (!dbr[d].hasMap() || (dbr[d].getMap()
+                     .locateMappedRange(start, end) != null))
              {
                ((String[]) sarray[1])[sq] = dbr[d].getAccessionId();
                ((int[]) sarray[0])[0]++;
          int type = urlLink.getGroupURLType() & 3;
          // first two bits ofurlLink type bitfield are sequenceids and sequences
          // TODO: FUTURE: ensure the groupURL menu structure can be generalised
-         addshowLink(linkMenus[type], label
-                 + (((type & 1) == 1) ? ("("
-                         + (usingNames ? "Names" : ltarget) + ")") : ""),
+         addshowLink(linkMenus[type],
+                 label + (((type & 1) == 1)
+                         ? ("(" + (usingNames ? "Names" : ltarget) + ")")
+                         : ""),
                  urlLink, urlset);
          addMenu = true;
        }
      }
    }
  
-   private void addshowLinks(JMenu linkMenu, Collection<List<String>> linkset)
+   private void addshowLinks(JMenu linkMenu,
+           Collection<List<String>> linkset)
    {
      for (List<String> linkstrset : linkset)
      {
    private void addshowLink(JMenu linkMenu, String label, final String url)
    {
      JMenuItem item = new JMenuItem(label);
-     item.setToolTipText(MessageManager.formatMessage(
-             "label.open_url_param", new Object[] { url }));
+     item.setToolTipText(MessageManager.formatMessage("label.open_url_param",
+             new Object[]
+             { url }));
      item.addActionListener(new ActionListener()
      {
        @Override
            final GroupUrlLink urlgenerator, final Object[] urlstub)
    {
      JMenuItem item = new JMenuItem(label);
-     item.setToolTipText(MessageManager.formatMessage(
-             "label.open_url_seqs_param",
-             new Object[] { urlgenerator.getUrl_prefix(),
+     item.setToolTipText(MessageManager
+             .formatMessage("label.open_url_seqs_param", new Object[]
+             { urlgenerator.getUrl_prefix(),
                  urlgenerator.getNumberInvolved(urlstub) }));
      // TODO: put in info about what is being sent.
      item.addActionListener(new ActionListener()
        }
      });
      sequenceMenu.setText(MessageManager.getString("label.sequence"));
-     sequenceName.setText(MessageManager
-             .getString("label.edit_name_description"));
+     sequenceName.setText(
+             MessageManager.getString("label.edit_name_description"));
      sequenceName.addActionListener(new ActionListener()
      {
        @Override
          sequenceName_actionPerformed();
        }
      });
-     chooseAnnotations.setText(MessageManager
-             .getString("action.choose_annotations"));
+     chooseAnnotations
+             .setText(MessageManager.getString("action.choose_annotations"));
      chooseAnnotations.addActionListener(new ActionListener()
      {
        @Override
          chooseAnnotations_actionPerformed(e);
        }
      });
-     sequenceDetails.setText(MessageManager
-             .getString("label.sequence_details"));
+     sequenceDetails
+             .setText(MessageManager.getString("label.sequence_details"));
      sequenceDetails.addActionListener(new ActionListener()
      {
        @Override
          sequenceDetails_actionPerformed();
        }
      });
-     sequenceSelDetails.setText(MessageManager
-             .getString("label.sequence_details"));
+     sequenceSelDetails
+             .setText(MessageManager.getString("label.sequence_details"));
      sequenceSelDetails.addActionListener(new ActionListener()
      {
        @Override
          unGroupMenuItem_actionPerformed();
        }
      });
-     createGroupMenuItem.setText(MessageManager
-             .getString("action.create_group"));
+     createGroupMenuItem
+             .setText(MessageManager.getString("action.create_group"));
      createGroupMenuItem.addActionListener(new ActionListener()
      {
        @Override
          showColourText_actionPerformed();
        }
      });
-     displayNonconserved.setText(MessageManager
-             .getString("label.show_non_conserved"));
+     displayNonconserved
+             .setText(MessageManager.getString("label.show_non_conserved"));
      displayNonconserved.setState(true);
      displayNonconserved.addActionListener(new ActionListener()
      {
          changeCase(e);
        }
      });
-     outputMenu.setText(MessageManager.getString("label.out_to_textbox")
-             + "...");
-     seqShowAnnotationsMenu.setText(MessageManager
-             .getString("label.show_annotations"));
-     seqHideAnnotationsMenu.setText(MessageManager
-             .getString("label.hide_annotations"));
-     groupShowAnnotationsMenu.setText(MessageManager
-             .getString("label.show_annotations"));
-     groupHideAnnotationsMenu.setText(MessageManager
-             .getString("label.hide_annotations"));
-     sequenceFeature.setText(MessageManager
-             .getString("label.create_sequence_feature"));
+     outputMenu.setText(
+             MessageManager.getString("label.out_to_textbox") + "...");
+     seqShowAnnotationsMenu
+             .setText(MessageManager.getString("label.show_annotations"));
+     seqHideAnnotationsMenu
+             .setText(MessageManager.getString("label.hide_annotations"));
+     groupShowAnnotationsMenu
+             .setText(MessageManager.getString("label.show_annotations"));
+     groupHideAnnotationsMenu
+             .setText(MessageManager.getString("label.hide_annotations"));
+     sequenceFeature.setText(
+             MessageManager.getString("label.create_sequence_feature"));
      sequenceFeature.addActionListener(new ActionListener()
      {
        @Override
        }
      });
      jMenu1.setText(MessageManager.getString("label.group"));
-     pdbStructureDialog.setText(MessageManager
-             .getString("label.show_pdbstruct_dialog"));
+     pdbStructureDialog.setText(
+             MessageManager.getString("label.show_pdbstruct_dialog"));
      pdbStructureDialog.addActionListener(new ActionListener()
      {
        @Override
        }
      });
  
-     rnaStructureMenu.setText(MessageManager
-             .getString("label.view_rna_structure"));
+     rnaStructureMenu
+             .setText(MessageManager.getString("label.view_rna_structure"));
  
      // colStructureMenu.setText("Colour By Structure");
-     editSequence.setText(MessageManager.getString("label.edit_sequence")
-             + "...");
+     editSequence.setText(
+             MessageManager.getString("label.edit_sequence") + "...");
      editSequence.addActionListener(new ActionListener()
      {
        @Override
          editSequence_actionPerformed(actionEvent);
        }
      });
-     makeReferenceSeq.setText(MessageManager
-             .getString("label.mark_as_representative"));
+     makeReferenceSeq.setText(
+             MessageManager.getString("label.mark_as_representative"));
      makeReferenceSeq.addActionListener(new ActionListener()
      {
  
  
        }
      });
-     hideInsertions.setText(MessageManager
-             .getString("label.hide_insertions"));
+     hideInsertions
+             .setText(MessageManager.getString("label.hide_insertions"));
      hideInsertions.addActionListener(new ActionListener()
      {
  
      jMenu1.add(outline);
      jMenu1.add(displayNonconserved);
    }
-   
    /**
     * Constructs the entries for the colour menu
     */
        }
      });
  
-     abovePIDColour.setText(MessageManager
-             .getString("label.above_identity_threshold"));
+     abovePIDColour.setText(
+             MessageManager.getString("label.above_identity_threshold"));
      abovePIDColour.addActionListener(new ActionListener()
      {
        @Override
        }
      });
  
-     modifyPID.setText(MessageManager
-             .getString("label.modify_identity_threshold"));
+     modifyPID.setText(
+             MessageManager.getString("label.modify_identity_threshold"));
      modifyPID.addActionListener(new ActionListener()
      {
        @Override
        }
      });
  
-     conservationMenuItem.setText(MessageManager
-             .getString("action.by_conservation"));
+     conservationMenuItem
+             .setText(MessageManager.getString("action.by_conservation"));
      conservationMenuItem.addActionListener(new ActionListener()
      {
        @Override
        public void actionPerformed(ActionEvent e)
        {
-         conservationMenuItem_actionPerformed(conservationMenuItem
-                 .isSelected());
+         conservationMenuItem_actionPerformed(
+                 conservationMenuItem.isSelected());
        }
      });
  
        // int threshold = SliderPanel.setPIDSliderSource(ap, sg.cs, getGroup()
        // .getName());
        // sg.cs.setThreshold(threshold, ap.av.isIgnoreGapsConsensus());
-       SliderPanel.setPIDSliderSource(ap, sg.cs, getGroup()
-               .getName());
+       SliderPanel.setPIDSliderSource(ap, sg.cs, getGroup().getName());
        SliderPanel.showPIDSlider();
      }
    }
      SortedMap<String, String> tipEntries = new TreeMap<String, String>();
      final Map<SequenceI, List<AlignmentAnnotation>> candidates = new LinkedHashMap<SequenceI, List<AlignmentAnnotation>>();
      AlignmentI al = this.ap.av.getAlignment();
-     AlignmentUtils.findAddableReferenceAnnotations(forSequences,
-             tipEntries, candidates, al);
+     AlignmentUtils.findAddableReferenceAnnotations(forSequences, tipEntries,
+             candidates, al);
      if (!candidates.isEmpty())
      {
        StringBuilder tooltip = new StringBuilder(64);
      if (ap.av.getSelectionGroup() != null)
      {
        // mark just the columns in the selection group to be hidden
-       inserts.set(ap.av.getSelectionGroup().getStartRes(), ap.av
-               .getSelectionGroup().getEndRes() + 1);
+       inserts.set(ap.av.getSelectionGroup().getStartRes(),
+               ap.av.getSelectionGroup().getEndRes() + 1);
  
        // and clear that part of the mask
        mask.andNot(inserts);
      StringBuilder contents = new StringBuilder(128);
      for (SequenceI seq : sequences)
      {
-       contents.append("<p><h2>"
-               + MessageManager
-                       .formatMessage(
-                               "label.create_sequence_details_report_annotation_for",
-                               new Object[] { seq.getDisplayId(true) })
-               + "</h2></p><p>");
-       new SequenceAnnotationReport(null)
-               .createSequenceAnnotationReport(
-                       contents,
-                       seq,
-                       true,
-                       true,
-                       (ap.getSeqPanel().seqCanvas.fr != null) ? ap
-                               .getSeqPanel().seqCanvas.fr.getMinMax()
-                               : null);
+       contents.append("<p><h2>" + MessageManager.formatMessage(
+               "label.create_sequence_details_report_annotation_for",
+               new Object[]
+               { seq.getDisplayId(true) }) + "</h2></p><p>");
+       new SequenceAnnotationReport(null).createSequenceAnnotationReport(
+               contents, seq, true, true,
+               (ap.getSeqPanel().seqCanvas.fr != null)
+                       ? ap.getSeqPanel().seqCanvas.fr.getMinMax()
+                       : null);
        contents.append("</p>");
      }
      cap.setText("<html>" + contents.toString() + "</html>");
  
-     Desktop.addInternalFrame(cap, MessageManager.formatMessage(
-             "label.sequence_details_for",
-             (sequences.length == 1 ? new Object[] { sequences[0]
-                     .getDisplayId(true) } : new Object[] { MessageManager
-                     .getString("label.selection") })), 500, 400);
+     Desktop.addInternalFrame(cap,
+             MessageManager.formatMessage("label.sequence_details_for",
+                     (sequences.length == 1 ? new Object[]
+                     { sequences[0].getDisplayId(true) }
+                             : new Object[]
+                             { MessageManager
+                                     .getString("label.selection") })),
+             500, 400);
  
    }
  
                sg.getStartRes(), sg.getEndRes() + 1));
  
        int threshold = SliderPanel.setPIDSliderSource(ap,
-               sg.getGroupColourScheme(), getGroup()
-               .getName());
+               sg.getGroupColourScheme(), getGroup().getName());
  
        sg.cs.setThreshold(threshold, ap.av.isIgnoreGapsConsensus());
  
      if (selected)
      {
        // JBPNote: Conservation name shouldn't be i18n translated
-       Conservation c = new Conservation("Group", sg.getSequences(ap.av
-               .getHiddenRepSequences()), sg.getStartRes(),
-               sg.getEndRes() + 1);
+       Conservation c = new Conservation("Group",
+               sg.getSequences(ap.av.getHiddenRepSequences()),
+               sg.getStartRes(), sg.getEndRes() + 1);
  
        c.calculate();
        c.verdict(false, ap.av.getConsPercGaps());
  
      SequenceGroup sg = getGroup();
      EditNameDialog dialog = new EditNameDialog(sg.getName(),
-             sg.getDescription(), "       "
-                     + MessageManager.getString("label.group_name") + " ",
+             sg.getDescription(),
+             "       " + MessageManager.getString("label.group_name") + " ",
              MessageManager.getString("label.group_description") + " ",
              MessageManager.getString("label.edit_group_name_description"),
              ap.alignFrame);
              "       " + MessageManager.getString("label.sequence_name")
                      + " ",
              MessageManager.getString("label.sequence_description") + " ",
-             MessageManager
-                     .getString("label.edit_sequence_name_description"),
+             MessageManager.getString(
+                     "label.edit_sequence_name_description"),
              ap.alignFrame);
  
      if (!dialog.accept)
      {
        if (dialog.getName().indexOf(" ") > -1)
        {
-         JvOptionPane
-                 .showMessageDialog(
-                         ap,
-                         MessageManager
-                                 .getString("label.spaces_converted_to_backslashes"),
-                         MessageManager
-                                 .getString("label.no_spaces_allowed_sequence_name"),
-                         JvOptionPane.WARNING_MESSAGE);
+         JvOptionPane.showMessageDialog(ap,
+                 MessageManager
+                         .getString("label.spaces_converted_to_backslashes"),
+                 MessageManager
+                         .getString("label.no_spaces_allowed_sequence_name"),
+                 JvOptionPane.WARNING_MESSAGE);
        }
  
        sequence.setName(dialog.getName().replace(' ', '_'));
  
      sequence.setDescription(dialog.getDescription());
  
-     ap.av.firePropertyChange("alignment", null, ap.av.getAlignment()
-             .getSequences());
+     ap.av.firePropertyChange("alignment", null,
+             ap.av.getAlignment().getSequences());
  
    }
  
  
        ap.alignFrame.addHistoryItem(caseCommand);
  
-       ap.av.firePropertyChange("alignment", null, ap.av.getAlignment()
-               .getSequences());
+       ap.av.firePropertyChange("alignment", null,
+               ap.av.getAlignment().getSequences());
  
      }
    }
    {
      CutAndPasteTransfer cap = new CutAndPasteTransfer();
      cap.setForInput(null);
-     Desktop.addInternalFrame(cap, MessageManager.formatMessage(
-             "label.alignment_output_command",
-             new Object[] { e.getActionCommand() }), 600, 500);
+     Desktop.addInternalFrame(cap, MessageManager
+             .formatMessage("label.alignment_output_command", new Object[]
+             { e.getActionCommand() }), 600, 500);
  
      String[] omitHidden = null;
  
      // or we simply trust the user wants
      // wysiwig behaviour
  
-     FileFormatI fileFormat = FileFormats.getInstance().forName(e.getActionCommand());
-     cap.setText(new FormatAdapter(ap).formatSequences(fileFormat, ap, true));
+     FileFormatI fileFormat = FileFormats.getInstance()
+             .forName(e.getActionCommand());
+     cap.setText(
+             new FormatAdapter(ap).formatSequences(fileFormat, ap, true));
    }
  
    public void sequenceFeature_actionPerformed()
        if (start <= end)
        {
          seqs.add(sg.getSequenceAt(i).getDatasetSequence());
 -        features.add(
 -                new SequenceFeature(null, null, null, start, end, null));
 +        features.add(new SequenceFeature(null, null, start, end, null));
        }
      }
  
       */
      if (!seqs.isEmpty())
      {
-       if (ap.getSeqPanel().seqCanvas.getFeatureRenderer().amendFeatures(
-               seqs, features, true, ap))
+       if (ap.getSeqPanel().seqCanvas.getFeatureRenderer()
+               .amendFeatures(seqs, features, true, ap))
        {
          ap.alignFrame.setShowSeqFeatures(true);
 -        ap.highlightSearchResults(null);
 +        ap.av.setSearchResults(null); // clear highlighting
 +        ap.repaint(); // draw new/amended features
        }
      }
    }
  
        EditNameDialog dialog = new EditNameDialog(
                sequence.getSequenceAsString(sg.getStartRes(),
-                       sg.getEndRes() + 1), null,
-               MessageManager.getString("label.edit_sequence"), null,
+                       sg.getEndRes() + 1),
+               null, MessageManager.getString("label.edit_sequence"), null,
                MessageManager.getString("label.edit_sequence"),
                ap.alignFrame);
  
        {
          EditCommand editCommand = new EditCommand(
                  MessageManager.getString("label.edit_sequences"),
-                 Action.REPLACE, dialog.getName().replace(' ',
-                         ap.av.getGapCharacter()),
+                 Action.REPLACE,
+                 dialog.getName().replace(' ', ap.av.getGapCharacter()),
                  sg.getSequencesAsArray(ap.av.getHiddenRepSequences()),
                  sg.getStartRes(), sg.getEndRes() + 1, ap.av.getAlignment());
  
          ap.alignFrame.addHistoryItem(editCommand);
  
-         ap.av.firePropertyChange("alignment", null, ap.av.getAlignment()
-                 .getSequences());
+         ap.av.firePropertyChange("alignment", null,
+                 ap.av.getAlignment().getSequences());
        }
      }
    }
@@@ -52,8 -52,6 +52,8 @@@ import javax.swing.JComponent
   */
  public class SeqCanvas extends JComponent implements ViewportListenerI
  {
 +  private static String ZEROS = "0000000000";
 +
    final FeatureRenderer fr;
  
    final SequenceRenderer sr;
@@@ -70,9 -68,9 +70,9 @@@
  
    boolean fastPaint = false;
  
 -  int LABEL_WEST;
 +  int labelWidthWest;
  
 -  int LABEL_EAST;
 +  int labelWidthEast;
  
    int cursorX = 0;
  
          {
            g.drawString(mstring, mpos * charWidth, ypos - (charHeight / 2));
          }
-         g.drawLine((mpos * charWidth) + (charWidth / 2), (ypos + 2)
-                 - (charHeight / 2), (mpos * charWidth) + (charWidth / 2),
-                 ypos - 2);
+         g.drawLine((mpos * charWidth) + (charWidth / 2),
+                 (ypos + 2) - (charHeight / 2),
+                 (mpos * charWidth) + (charWidth / 2), ypos - 2);
        }
      }
    }
  
        if (value != -1)
        {
 -        int x = LABEL_WEST - fm.stringWidth(String.valueOf(value))
 +        int x = labelWidthWest - fm.stringWidth(String.valueOf(value))
                  - charWidth / 2;
-         g.drawString(value + "", x, (ypos + (i * charHeight))
-                 - (charHeight / 5));
+         g.drawString(value + "", x,
+                 (ypos + (i * charHeight)) - (charHeight / 5));
        }
      }
    }
  
        if (value != -1)
        {
-         g.drawString(String.valueOf(value), 0, (ypos + (i * charHeight))
-                 - (charHeight / 5));
+         g.drawString(String.valueOf(value), 0,
+                 (ypos + (i * charHeight)) - (charHeight / 5));
        }
      }
    }
      updateViewport();
  
      ViewportRanges ranges = av.getRanges();
 -    int sr = ranges.getStartRes();
 -    int er = ranges.getEndRes();
 -    int ss = ranges.getStartSeq();
 -    int es = ranges.getEndSeq();
 +    int startRes = ranges.getStartRes();
 +    int endRes = ranges.getEndRes();
 +    int startSeq = ranges.getStartSeq();
 +    int endSeq = ranges.getEndSeq();
      int transX = 0;
      int transY = 0;
  
  
      if (horizontal > 0) // scrollbar pulled right, image to the left
      {
 -      transX = (er - sr - horizontal) * charWidth;
 -      sr = er - horizontal;
 +      transX = (endRes - startRes - horizontal) * charWidth;
 +      startRes = endRes - horizontal;
      }
      else if (horizontal < 0)
      {
 -      er = sr - horizontal;
 +      endRes = startRes - horizontal;
      }
      else if (vertical > 0) // scroll down
      {
 -      ss = es - vertical;
 +      startSeq = endSeq - vertical;
  
 -      if (ss < ranges.getStartSeq())
 +      if (startSeq < ranges.getStartSeq())
        { // ie scrolling too fast, more than a page at a time
 -        ss = ranges.getStartSeq();
 +        startSeq = ranges.getStartSeq();
        }
        else
        {
      }
      else if (vertical < 0)
      {
 -      es = ss - vertical;
 +      endSeq = startSeq - vertical;
  
 -      if (es > ranges.getEndSeq())
 +      if (endSeq > ranges.getEndSeq())
        {
 -        es = ranges.getEndSeq();
 +        endSeq = ranges.getEndSeq();
        }
      }
  
      gg.translate(transX, transY);
 -    drawPanel(gg, sr, er, ss, es, 0);
 +    drawPanel(gg, startRes, endRes, startSeq, endSeq, 0);
      gg.translate(-transX, -transY);
  
      repaint();
      fastpainting = false;
    }
  
 -  /**
 -   * Definitions of startx and endx (hopefully): SMJS This is what I'm working
 -   * towards! startx is the first residue (starting at 0) to display. endx is
 -   * the last residue to display (starting at 0). starty is the first sequence
 -   * to display (starting at 0). endy is the last sequence to display (starting
 -   * at 0). NOTE 1: The av limits are set in setFont in this class and in the
 -   * adjustment listener in SeqPanel when the scrollbars move.
 -   */
 -
 -  // Set this to false to force a full panel paint
    @Override
    public void paintComponent(Graphics g)
    {
      // img and call later.
      super.paintComponent(g);
  
-     if (lcimg != null
-             && (fastPaint
-                     || (getVisibleRect().width != g.getClipBounds().width) || (getVisibleRect().height != g
-                     .getClipBounds().height)))
+     if (lcimg != null && (fastPaint
+             || (getVisibleRect().width != g.getClipBounds().width)
+             || (getVisibleRect().height != g.getClipBounds().height)))
      {
        g.drawImage(lcimg, 0, 0, this);
        fastPaint = false;
    }
  
    /**
 -   * DOCUMENT ME!
 +   * Returns the visible width of the canvas in residues, after allowing for
 +   * East or West scales (if shown)
     * 
 -   * @param cwidth
 -   *          DOCUMENT ME!
 +   * @param canvasWidth
 +   *          the width in pixels (possibly including scales)
     * 
 -   * @return DOCUMENT ME!
 +   * @return
     */
 -  public int getWrappedCanvasWidth(int cwidth)
 +  public int getWrappedCanvasWidth(int canvasWidth)
    {
      FontMetrics fm = getFontMetrics(av.getFont());
  
 -    LABEL_EAST = 0;
 -    LABEL_WEST = 0;
 +    labelWidthEast = 0;
 +    labelWidthWest = 0;
  
      if (av.getScaleRightWrapped())
      {
 -      LABEL_EAST = fm.stringWidth(getMask());
 +      labelWidthEast = getLabelWidth(fm);
      }
  
      if (av.getScaleLeftWrapped())
      {
 -      LABEL_WEST = fm.stringWidth(getMask());
 +      labelWidthWest = labelWidthEast > 0 ? labelWidthEast
 +              : getLabelWidth(fm);
      }
  
 -    return (cwidth - LABEL_EAST - LABEL_WEST) / charWidth;
 +    return (canvasWidth - labelWidthEast - labelWidthWest) / charWidth;
    }
  
    /**
 -   * Generates a string of zeroes.
 +   * Returns a pixel width suitable for showing the largest sequence coordinate
 +   * (end position) in the alignment. Returns 2 plus the number of decimal
 +   * digits to be shown (3 for 1-10, 4 for 11-99 etc).
     * 
 -   * @return String
 +   * @param fm
 +   * @return
     */
 -  String getMask()
 +  protected int getLabelWidth(FontMetrics fm)
    {
 -    String mask = "00";
 +    /*
 +     * find the biggest sequence end position we need to show
 +     * (note this is not necessarily the sequence length)
 +     */
      int maxWidth = 0;
 -    int tmp;
 -    for (int i = 0; i < av.getAlignment().getHeight(); i++)
 +    AlignmentI alignment = av.getAlignment();
 +    for (int i = 0; i < alignment.getHeight(); i++)
      {
 -      tmp = av.getAlignment().getSequenceAt(i).getEnd();
 -      if (tmp > maxWidth)
 -      {
 -        maxWidth = tmp;
 -      }
 +      maxWidth = Math.max(maxWidth, alignment.getSequenceAt(i).getEnd());
      }
  
 +    int length = 2;
      for (int i = maxWidth; i > 0; i /= 10)
      {
 -      mask += "0";
 +      length++;
      }
 -    return mask;
 +
 +    return fm.stringWidth(ZEROS.substring(0, length));
    }
  
    /**
      updateViewport();
      AlignmentI al = av.getAlignment();
  
 -    FontMetrics fm = getFontMetrics(av.getFont());
 -
 -    LABEL_EAST = 0;
 -    LABEL_WEST = 0;
 -
 -    if (av.getScaleRightWrapped())
 +    int labelWidth = 0;
 +    if (av.getScaleRightWrapped() || av.getScaleLeftWrapped())
      {
 -      LABEL_EAST = fm.stringWidth(getMask());
 +      FontMetrics fm = getFontMetrics(av.getFont());
 +      labelWidth = getLabelWidth(fm);
      }
  
 -    if (av.getScaleLeftWrapped())
 -    {
 -      LABEL_WEST = fm.stringWidth(getMask());
 -    }
 +    labelWidthEast = av.getScaleRightWrapped() ? labelWidth : 0;
 +    labelWidthWest = av.getScaleLeftWrapped() ? labelWidth : 0;
  
      int hgap = charHeight;
      if (av.getScaleAboveWrapped())
        hgap += charHeight;
      }
  
 -    int cWidth = (canvasWidth - LABEL_EAST - LABEL_WEST) / charWidth;
 +    int cWidth = (canvasWidth - labelWidthEast - labelWidthWest) / charWidth;
      int cHeight = av.getAlignment().getHeight() * charHeight;
  
      av.setWrappedWidth(cWidth);
                .findColumnPosition(maxwidth);
      }
  
 +    int annotationHeight = getAnnotationHeight();
 +
      while ((ypos <= canvasHeight) && (startRes < maxwidth))
      {
        endx = startRes + cWidth - 1;
  
        if (av.getScaleRightWrapped())
        {
 -        g.translate(canvasWidth - LABEL_EAST, 0);
 +        g.translate(canvasWidth - labelWidthEast, 0);
          drawEastScale(g, startRes, endx, ypos);
 -        g.translate(-(canvasWidth - LABEL_EAST), 0);
 +        g.translate(-(canvasWidth - labelWidthEast), 0);
        }
  
 -      g.translate(LABEL_WEST, 0);
 +      g.translate(labelWidthWest, 0);
  
        if (av.getScaleAboveWrapped())
        {
            }
  
            gg.fillPolygon(
-                   new int[] { res * charWidth - charHeight / 4,
+                   new int[]
+                   { res * charWidth - charHeight / 4,
                        res * charWidth + charHeight / 4, res * charWidth },
-                   new int[] { ypos - (charHeight / 2),
-                       ypos - (charHeight / 2), ypos - (charHeight / 2) + 8 },
+                   new int[]
+                   { ypos - (charHeight / 2), ypos - (charHeight / 2),
+                       ypos - (charHeight / 2) + 8 },
                    3);
  
          }
            annotations = new AnnotationPanel(av);
          }
  
-         annotations.renderer.drawComponent(annotations, av, g, -1,
-                 startRes, endx + 1);
+         annotations.renderer.drawComponent(annotations, av, g, -1, startRes,
+                 endx + 1);
          g.translate(0, -cHeight - ypos - 3);
        }
        g.setClip(clip);
 -      g.translate(-LABEL_WEST, 0);
 +      g.translate(-labelWidthWest, 0);
  
 -      ypos += cHeight + getAnnotationHeight() + hgap;
 +      ypos += cHeight + annotationHeight + hgap;
  
        startRes += cWidth;
      }
    }
  
    /**
 -   * DOCUMENT ME!
 +   * Draws the visible region of the alignment on the graphics context. If there
 +   * are hidden column markers in the visible region, then each sub-region
 +   * between the markers is drawn separately, followed by the hidden column
 +   * marker.
     * 
     * @param g1
 -   *          DOCUMENT ME!
     * @param startRes
 -   *          DOCUMENT ME!
 +   *          offset of the first column in the visible region (0..)
     * @param endRes
 -   *          DOCUMENT ME!
 +   *          offset of the last column in the visible region (0..)
     * @param startSeq
 -   *          DOCUMENT ME!
 +   *          offset of the first sequence in the visible region (0..)
     * @param endSeq
 -   *          DOCUMENT ME!
 -   * @param offset
 -   *          DOCUMENT ME!
 +   *          offset of the last sequence in the visible region (0..)
 +   * @param yOffset
 +   *          vertical offset at which to draw (for wrapped alignments)
     */
 -  public void drawPanel(Graphics g1, int startRes, int endRes, int startSeq,
 -          int endSeq, int offset)
 +  public void drawPanel(Graphics g1, final int startRes, final int endRes,
 +          final int startSeq, final int endSeq, final int yOffset)
    {
      updateViewport();
      if (!av.hasHiddenColumns())
      {
 -      draw(g1, startRes, endRes, startSeq, endSeq, offset);
 +      draw(g1, startRes, endRes, startSeq, endSeq, yOffset);
      }
      else
      {
        int screenY = 0;
 +      final int screenYMax = endRes - startRes;
        int blockStart = startRes;
        int blockEnd = endRes;
  
            continue;
          }
  
 -        blockEnd = hideStart - 1;
 +        /*
 +         * draw up to just before the next hidden region, or the end of
 +         * the visible region, whichever comes first
 +         */
 +        blockEnd = Math.min(hideStart - 1, blockStart + screenYMax
 +                - screenY);
  
          g1.translate(screenY * charWidth, 0);
  
 -        draw(g1, blockStart, blockEnd, startSeq, endSeq, offset);
 +        draw(g1, blockStart, blockEnd, startSeq, endSeq, yOffset);
  
 -        if (av.getShowHiddenMarkers())
 +        /*
 +         * draw the downline of the hidden column marker (ScalePanel draws the
 +         * triangle on top) if we reached it
 +         */
 +        if (av.getShowHiddenMarkers() && blockEnd == hideStart - 1)
          {
            g1.setColor(Color.blue);
  
            g1.drawLine((blockEnd - blockStart + 1) * charWidth - 1,
 -                  0 + offset, (blockEnd - blockStart + 1) * charWidth - 1,
 -                  (endSeq - startSeq + 1) * charHeight + offset);
 +                  0 + yOffset, (blockEnd - blockStart + 1) * charWidth - 1,
 +                  (endSeq - startSeq + 1) * charHeight + yOffset);
          }
  
          g1.translate(-screenY * charWidth, 0);
          screenY += blockEnd - blockStart + 1;
          blockStart = hideEnd + 1;
  
 -        if (screenY > (endRes - startRes))
 +        if (screenY > screenYMax)
          {
            // already rendered last block
            return;
          }
        }
  
 -      if (screenY <= (endRes - startRes))
 +      if (screenY <= screenYMax)
        {
          // remaining visible region to render
 -        blockEnd = blockStart + (endRes - startRes) - screenY;
 +        blockEnd = blockStart + screenYMax - screenY;
          g1.translate(screenY * charWidth, 0);
 -        draw(g1, blockStart, blockEnd, startSeq, endSeq, offset);
 +        draw(g1, blockStart, blockEnd, startSeq, endSeq, yOffset);
  
          g1.translate(-screenY * charWidth, 0);
        }
  
    }
  
 -  // int startRes, int endRes, int startSeq, int endSeq, int x, int y,
 -  // int x1, int x2, int y1, int y2, int startx, int starty,
 +  /**
 +   * Draws a region of the visible alignment
 +   * 
 +   * @param g1
 +   * @param startRes
 +   *          offset of the first column in the visible region (0..)
 +   * @param endRes
 +   *          offset of the last column in the visible region (0..)
 +   * @param startSeq
 +   *          offset of the first sequence in the visible region (0..)
 +   * @param endSeq
 +   *          offset of the last sequence in the visible region (0..)
 +   * @param yOffset
 +   *          vertical offset at which to draw (for wrapped alignments)
 +   */
    private void draw(Graphics g, int startRes, int endRes, int startSeq,
            int endSeq, int offset)
    {
  
        if (av.isShowSequenceFeatures())
        {
-         fr.drawSequence(g, nextSeq, startRes, endRes, offset
-                 + ((i - startSeq) * charHeight), false);
+         fr.drawSequence(g, nextSeq, startRes, endRes,
+                 offset + ((i - startSeq) * charHeight), false);
        }
  
 -      // / Highlight search Results once all sequences have been drawn
 -      // ////////////////////////////////////////////////////////
 +      /*
 +       * highlight search Results once sequence has been drawn
 +       */
        if (av.hasSearchResults())
        {
 -        int[] visibleResults = av.getSearchResults().getResults(nextSeq,
 +        SearchResultsI searchResults = av.getSearchResults();
 +        int[] visibleResults = searchResults.getResults(nextSeq,
                  startRes, endRes);
          if (visibleResults != null)
          {
            for (int r = 0; r < visibleResults.length; r += 2)
            {
              sr.drawHighlightedText(nextSeq, visibleResults[r],
-                     visibleResults[r + 1], (visibleResults[r] - startRes)
-                             * charWidth, offset
-                             + ((i - startSeq) * charHeight));
+                     visibleResults[r + 1],
+                     (visibleResults[r] - startRes) * charWidth,
+                     offset + ((i - startSeq) * charHeight));
            }
          }
        }
          {
            sx = (group.getStartRes() - startRes) * charWidth;
            sy = offset + ((i - startSeq) * charHeight);
-           ex = (((group.getEndRes() + 1) - group.getStartRes()) * charWidth) - 1;
+           ex = (((group.getEndRes() + 1) - group.getStartRes()) * charWidth)
+                   - 1;
  
            if (sx + ex < 0 || sx > visWidth)
            {
            }
  
            if ((sx <= (endRes - startRes) * charWidth)
-                   && group.getSequences(null).contains(
-                           av.getAlignment().getSequenceAt(i)))
+                   && group.getSequences(null)
+                           .contains(av.getAlignment().getSequenceAt(i)))
            {
-             if ((bottom == -1)
-                     && !group.getSequences(null).contains(
-                             av.getAlignment().getSequenceAt(i + 1)))
+             if ((bottom == -1) && !group.getSequences(null)
+                     .contains(av.getAlignment().getSequenceAt(i + 1)))
              {
                bottom = sy + charHeight;
              }
  
              if (!inGroup)
              {
-               if (((top == -1) && (i == 0))
-                       || !group.getSequences(null).contains(
-                               av.getAlignment().getSequenceAt(i - 1)))
+               if (((top == -1) && (i == 0)) || !group.getSequences(null)
+                       .contains(av.getAlignment().getSequenceAt(i - 1)))
                {
                  top = sy;
                }
                if (group == av.getSelectionGroup())
                {
                  g.setStroke(new BasicStroke(1, BasicStroke.CAP_BUTT,
-                         BasicStroke.JOIN_ROUND, 3f, new float[] { 5f, 3f },
-                         0f));
+                         BasicStroke.JOIN_ROUND, 3f, new float[]
+                         { 5f, 3f }, 0f));
                  g.setColor(Color.RED);
                }
                else
    }
  
    /**
 -   * DOCUMENT ME!
 +   * Highlights search results in the visible region by rendering as white text
 +   * on a black background. Any previous highlighting is removed. Answers true
 +   * if any highlight was left on the visible alignment (so status bar should be
 +   * set to match), else false.
 +   * <p>
 +   * Currently fastPaint is not implemented for wrapped alignments. If a wrapped
 +   * alignment had to be scrolled to show the highlighted region, then it should
 +   * be fully redrawn, otherwise a fast paint can be performed. This argument
 +   * could be removed if fast paint of scrolled wrapped alignment is coded in
 +   * future (JAL-2609).
     * 
     * @param results
 -   *          DOCUMENT ME!
 +   * @param noFastPaint
 +   * @return
     */
 -  public void highlightSearchResults(SearchResultsI results)
 +  public boolean highlightSearchResults(SearchResultsI results,
 +          boolean noFastPaint)
    {
 -    img = null;
 +    if (fastpainting)
 +    {
 +      return false;
 +    }
 +    boolean wrapped = av.getWrapAlignment();
  
 -    av.setSearchResults(results);
 +    try
 +    {
 +      fastPaint = !noFastPaint;
 +      fastpainting = fastPaint;
 +
 +      updateViewport();
 +
 +      /*
 +       * to avoid redrawing the whole visible region, we instead
 +       * redraw just the minimal regions to remove previous highlights
 +       * and add new ones
 +       */
 +      SearchResultsI previous = av.getSearchResults();
 +      av.setSearchResults(results);
 +      boolean redrawn = false;
 +      boolean drawn = false;
 +      if (wrapped)
 +      {
 +        redrawn = drawMappedPositionsWrapped(previous);
 +        drawn = drawMappedPositionsWrapped(results);
 +        redrawn |= drawn;
 +      }
 +      else
 +      {
 +        redrawn = drawMappedPositions(previous);
 +        drawn = drawMappedPositions(results);
 +        redrawn |= drawn;
 +      }
  
 -    repaint();
 +      /*
 +       * if highlights were either removed or added, repaint
 +       */
 +      if (redrawn)
 +      {
 +        repaint();
 +      }
 +
 +      /*
 +       * return true only if highlights were added
 +       */
 +      return drawn;
 +
 +    } finally
 +    {
 +      fastpainting = false;
 +    }
 +  }
 +
 +  /**
 +   * Redraws the minimal rectangle in the visible region (if any) that includes
 +   * mapped positions of the given search results. Whether or not positions are
 +   * highlighted depends on the SearchResults set on the Viewport. This allows
 +   * this method to be called to either clear or set highlighting. Answers true
 +   * if any positions were drawn (in which case a repaint is still required),
 +   * else false.
 +   * 
 +   * @param results
 +   * @return
 +   */
 +  protected boolean drawMappedPositions(SearchResultsI results)
 +  {
 +    if (results == null)
 +    {
 +      return false;
 +    }
 +
 +    /*
 +     * calculate the minimal rectangle to redraw that 
 +     * includes both new and existing search results
 +     */
 +    int firstSeq = Integer.MAX_VALUE;
 +    int lastSeq = -1;
 +    int firstCol = Integer.MAX_VALUE;
 +    int lastCol = -1;
 +    boolean matchFound = false;
 +
 +    ViewportRanges ranges = av.getRanges();
 +    int firstVisibleColumn = ranges.getStartRes();
 +    int lastVisibleColumn = ranges.getEndRes();
 +    AlignmentI alignment = av.getAlignment();
 +    if (av.hasHiddenColumns())
 +    {
 +      firstVisibleColumn = alignment.getHiddenColumns()
 +              .adjustForHiddenColumns(firstVisibleColumn);
 +      lastVisibleColumn = alignment.getHiddenColumns()
 +              .adjustForHiddenColumns(lastVisibleColumn);
 +    }
 +
 +    for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
 +            .getEndSeq(); seqNo++)
 +    {
 +      SequenceI seq = alignment.getSequenceAt(seqNo);
 +
 +      int[] visibleResults = results.getResults(seq, firstVisibleColumn,
 +              lastVisibleColumn);
 +      if (visibleResults != null)
 +      {
 +        for (int i = 0; i < visibleResults.length - 1; i += 2)
 +        {
 +          int firstMatchedColumn = visibleResults[i];
 +          int lastMatchedColumn = visibleResults[i + 1];
 +          if (firstMatchedColumn <= lastVisibleColumn
 +                  && lastMatchedColumn >= firstVisibleColumn)
 +          {
 +            /*
 +             * found a search results match in the visible region - 
 +             * remember the first and last sequence matched, and the first
 +             * and last visible columns in the matched positions
 +             */
 +            matchFound = true;
 +            firstSeq = Math.min(firstSeq, seqNo);
 +            lastSeq = Math.max(lastSeq, seqNo);
 +            firstMatchedColumn = Math.max(firstMatchedColumn,
 +                    firstVisibleColumn);
 +            lastMatchedColumn = Math.min(lastMatchedColumn,
 +                    lastVisibleColumn);
 +            firstCol = Math.min(firstCol, firstMatchedColumn);
 +            lastCol = Math.max(lastCol, lastMatchedColumn);
 +          }
 +        }
 +      }
 +    }
 +
 +    if (matchFound)
 +    {
 +      if (av.hasHiddenColumns())
 +      {
 +        firstCol = alignment.getHiddenColumns()
 +                .findColumnPosition(firstCol);
 +        lastCol = alignment.getHiddenColumns().findColumnPosition(lastCol);
 +      }
 +      int transX = (firstCol - ranges.getStartRes()) * av.getCharWidth();
 +      int transY = (firstSeq - ranges.getStartSeq()) * av.getCharHeight();
 +      gg.translate(transX, transY);
 +      drawPanel(gg, firstCol, lastCol, firstSeq, lastSeq, 0);
 +      gg.translate(-transX, -transY);
 +    }
 +
 +    return matchFound;
    }
  
    @Override
        }
      }
    }
 +
 +  /**
 +   * Redraws any positions in the search results in the visible region of a
 +   * wrapped alignment. Any highlights are drawn depending on the search results
 +   * set on the Viewport, not the <code>results</code> argument. This allows
 +   * this method to be called either to clear highlights (passing the previous
 +   * search results), or to draw new highlights.
 +   * 
 +   * @param results
 +   * @return
 +   */
 +  protected boolean drawMappedPositionsWrapped(SearchResultsI results)
 +  {
 +    if (results == null)
 +    {
 +      return false;
 +    }
 +  
 +    boolean matchFound = false;
 +
 +    int wrappedWidth = av.getWrappedWidth();
 +    int wrappedHeight = getRepeatHeightWrapped();
 +
 +    ViewportRanges ranges = av.getRanges();
 +    int canvasHeight = getHeight();
 +    int repeats = canvasHeight / wrappedHeight;
 +    if (canvasHeight / wrappedHeight > 0)
 +    {
 +      repeats++;
 +    }
 +
 +    int firstVisibleColumn = ranges.getStartRes();
 +    int lastVisibleColumn = ranges.getStartRes() + repeats
 +            * ranges.getViewportWidth() - 1;
 +
 +    AlignmentI alignment = av.getAlignment();
 +    if (av.hasHiddenColumns())
 +    {
 +      firstVisibleColumn = alignment.getHiddenColumns()
 +              .adjustForHiddenColumns(firstVisibleColumn);
 +      lastVisibleColumn = alignment.getHiddenColumns()
 +              .adjustForHiddenColumns(lastVisibleColumn);
 +    }
 +
 +    int gapHeight = charHeight * (av.getScaleAboveWrapped() ? 2 : 1);
 +
 +    for (int seqNo = ranges.getStartSeq(); seqNo <= ranges
 +            .getEndSeq(); seqNo++)
 +    {
 +      SequenceI seq = alignment.getSequenceAt(seqNo);
 +
 +      int[] visibleResults = results.getResults(seq, firstVisibleColumn,
 +              lastVisibleColumn);
 +      if (visibleResults != null)
 +      {
 +        for (int i = 0; i < visibleResults.length - 1; i += 2)
 +        {
 +          int firstMatchedColumn = visibleResults[i];
 +          int lastMatchedColumn = visibleResults[i + 1];
 +          if (firstMatchedColumn <= lastVisibleColumn
 +                  && lastMatchedColumn >= firstVisibleColumn)
 +          {
 +            /*
 +             * found a search results match in the visible region
 +             */
 +            firstMatchedColumn = Math.max(firstMatchedColumn,
 +                    firstVisibleColumn);
 +            lastMatchedColumn = Math.min(lastMatchedColumn,
 +                    lastVisibleColumn);
 +
 +            /*
 +             * draw each mapped position separately (as contiguous positions may
 +             * wrap across lines)
 +             */
 +            for (int mappedPos = firstMatchedColumn; mappedPos <= lastMatchedColumn; mappedPos++)
 +            {
 +              int displayColumn = mappedPos;
 +              if (av.hasHiddenColumns())
 +              {
 +                displayColumn = alignment.getHiddenColumns()
 +                        .findColumnPosition(displayColumn);
 +              }
 +
 +              /*
 +               * transX: offset from left edge of canvas to residue position
 +               */
 +              int transX = labelWidthWest
 +                      + ((displayColumn - ranges.getStartRes()) % wrappedWidth)
 +                      * av.getCharWidth();
 +
 +              /*
 +               * transY: offset from top edge of canvas to residue position
 +               */
 +              int transY = gapHeight;
 +              transY += (displayColumn - ranges.getStartRes())
 +                      / wrappedWidth * wrappedHeight;
 +              transY += (seqNo - ranges.getStartSeq()) * av.getCharHeight();
 +
 +              /*
 +               * yOffset is from graphics origin to start of visible region
 +               */
 +              int yOffset = 0;// (displayColumn / wrappedWidth) * wrappedHeight;
 +              if (transY < getHeight())
 +              {
 +                matchFound = true;
 +                gg.translate(transX, transY);
 +                drawPanel(gg, displayColumn, displayColumn, seqNo, seqNo,
 +                        yOffset);
 +                gg.translate(-transX, -transY);
 +              }
 +            }
 +          }
 +        }
 +      }
 +    }
 +  
 +    return matchFound;
 +  }
 +
 +  /**
 +   * Answers the height in pixels of a repeating section of the wrapped
 +   * alignment, including space above, scale above if shown, sequences, and
 +   * annotation panel if shown
 +   * 
 +   * @return
 +   */
 +  protected int getRepeatHeightWrapped()
 +  {
 +    // gap (and maybe scale) above
 +    int repeatHeight = charHeight * (av.getScaleAboveWrapped() ? 2 : 1);
 +
 +    // add sequences
 +    repeatHeight += av.getRanges().getViewportHeight() * charHeight;
 +
 +    // add annotations panel height if shown
 +    repeatHeight += getAnnotationHeight();
 +
 +    return repeatHeight;
 +  }
  }
@@@ -62,6 -62,7 +62,6 @@@ import java.awt.event.MouseWheelListene
  import java.util.ArrayList;
  import java.util.Collections;
  import java.util.List;
 -import java.util.ListIterator;
  
  import javax.swing.JPanel;
  import javax.swing.SwingUtilities;
@@@ -73,9 -74,9 +73,9 @@@ import javax.swing.ToolTipManager
   * @author $author$
   * @version $Revision: 1.130 $
   */
- public class SeqPanel extends JPanel implements MouseListener,
-         MouseMotionListener, MouseWheelListener, SequenceListener,
-         SelectionListener
+ public class SeqPanel extends JPanel
+         implements MouseListener, MouseMotionListener, MouseWheelListener,
+         SequenceListener, SelectionListener
  
  {
    /** DOCUMENT ME!! */
    /** DOCUMENT ME!! */
    public AlignmentPanel ap;
  
 +  /*
 +   * last column position for mouseMoved event
 +   */
 +  private int lastMouseColumn;
 +
 +  /*
 +   * last sequence offset for mouseMoved event
 +   */
 +  private int lastMouseSeq;
 +
    protected int lastres;
  
    protected int startseq;
        ssm.addStructureViewerListener(this);
        ssm.addSelectionListener(this);
      }
 +
 +    lastMouseColumn = -1;
 +    lastMouseSeq = -1;
    }
  
    int startWrapBlock = -1;
  
        int y = evt.getY();
        y -= hgap;
 -      x = Math.max(0, x - seqCanvas.LABEL_WEST);
 +      x = Math.max(0, x - seqCanvas.labelWidthWest);
  
        int cwidth = seqCanvas.getWrappedCanvasWidth(this.getWidth());
        if (cwidth < 1)
  
        y -= hgap;
  
-       seq = Math.min((y % cHeight) / av.getCharHeight(), av.getAlignment()
-               .getHeight() - 1);
+       seq = Math.min((y % cHeight) / av.getCharHeight(),
+               av.getAlignment().getHeight() - 1);
      }
      else
      {
-       seq = Math.min((y / av.getCharHeight())
-               + av.getRanges().getStartSeq(),
-               av
-               .getAlignment().getHeight() - 1);
+       seq = Math.min(
+               (y / av.getCharHeight()) + av.getRanges().getStartSeq(),
+               av.getAlignment().getHeight() - 1);
      }
  
      return seq;
        if (editCommand != null && editCommand.getSize() > 0)
        {
          ap.alignFrame.addHistoryItem(editCommand);
-         av.firePropertyChange("alignment", null, av.getAlignment()
-                 .getSequences());
+         av.firePropertyChange("alignment", null,
+                 av.getAlignment().getSequences());
        }
      } finally
      {
  
      HiddenColumns hidden = av.getAlignment().getHiddenColumns();
  
-     if (av.hasHiddenColumns()
-  && !hidden.isVisible(seqCanvas.cursorX))
+     if (av.hasHiddenColumns() && !hidden.isVisible(seqCanvas.cursorX))
      {
        int original = seqCanvas.cursorX - dx;
        int maxWidth = av.getAlignment().getWidth();
      }
      lastSearchResults = results;
  
 +    boolean wasScrolled = false;
 +
      if (av.isFollowHighlight())
      {
        // don't allow highlight of protein/cDNA to also scroll a complementary
        // over residue to change abruptly, causing highlighted residue in panel 2
        // to change, causing a scroll in panel 1 etc)
        ap.setToScrollComplementPanel(false);
 -      if (ap.scrollToPosition(results, false))
 +      wasScrolled = ap.scrollToPosition(results, false);
 +      if (wasScrolled)
        {
          seqCanvas.revalidate();
        }
        ap.setToScrollComplementPanel(true);
      }
 -    setStatusMessage(results);
 -    seqCanvas.highlightSearchResults(results);
 +
 +    boolean noFastPaint = wasScrolled && av.getWrapAlignment();
 +    if (seqCanvas.highlightSearchResults(results, noFastPaint))
 +    {
 +      setStatusMessage(results);
 +    }
    }
  
    @Override
      int seq = findSeq(evt);
      if (column < 0 || seq < 0 || seq >= av.getAlignment().getHeight())
      {
 +      lastMouseSeq = -1;
        return;
      }
 +    if (column == lastMouseColumn && seq == lastMouseSeq)
 +    {
 +      /*
 +       * just a pixel move without change of residue
 +       */
 +      return;
 +    }
 +    lastMouseColumn = column;
 +    lastMouseSeq = seq;
  
      SequenceI sequence = av.getAlignment().getSequenceAt(seq);
  
      if (av.isShowSequenceFeatures())
      {
        List<SequenceFeature> features = ap.getFeatureRenderer()
 -              .findFeaturesAtRes(sequence.getDatasetSequence(), pos);
 -      if (isGapped)
 -      {
 -        removeAdjacentFeatures(features, column + 1, sequence);
 -      }
 +              .findFeaturesAtColumn(sequence, column + 1);
        seqARep.appendFeatures(tooltipText, pos, features,
                this.ap.getSeqPanel().seqCanvas.fr.getMinMax());
      }
      }
      else
      {
 -      if (lastTooltip == null
 -              || !lastTooltip.equals(tooltipText.toString()))
 +      String textString = tooltipText.toString();
 +      if (lastTooltip == null || !lastTooltip.equals(textString))
        {
 -        String formatedTooltipText = JvSwingUtils.wrapTooltip(true,
 -                tooltipText.toString());
 -        // String formatedTooltipText = tooltipText.toString();
 -        setToolTipText(formatedTooltipText);
 -        lastTooltip = tooltipText.toString();
 -      }
 -
 -    }
 -
 -  }
 -
 -  /**
 -   * Removes from the list of features any that start after, or end before, the
 -   * given column position. This allows us to retain only those features
 -   * adjacent to a gapped position that straddle the position. Contact features
 -   * that 'straddle' the position are also removed, since they are not 'at' the
 -   * position.
 -   * 
 -   * @param features
 -   * @param column
 -   *          alignment column (1..)
 -   * @param sequence
 -   */
 -  protected void removeAdjacentFeatures(List<SequenceFeature> features,
 -          final int column, SequenceI sequence)
 -  {
 -    // TODO should this be an AlignViewController method (and reused by applet)?
 -    ListIterator<SequenceFeature> it = features.listIterator();
 -    while (it.hasNext())
 -    {
 -      SequenceFeature sf = it.next();
 -      if (sf.isContactFeature()
 -              || sequence.findIndex(sf.getBegin()) > column
 -              || sequence.findIndex(sf.getEnd()) < column)
 -      {
 -        it.remove();
 +        String formattedTooltipText = JvSwingUtils.wrapTooltip(true,
 +                textString);
 +        setToolTipText(formattedTooltipText);
 +        lastTooltip = textString;
        }
      }
    }
      Point p = lastp;
      if (!event.isShiftDown() || p == null)
      {
-       p = (tooltipText != null && tooltipText.length() > 6) ? new Point(
-               event.getX() + wdth, event.getY() - 20) : null;
+       p = (tooltipText != null && tooltipText.length() > 6)
+               ? new Point(event.getX() + wdth, event.getY() - 20)
+               : null;
      }
      /*
       * TODO: try to modify position region is not obcured by tooltip
     *          aligned sequence object
     * @param column
     *          alignment column
 -   * @param seq
 +   * @param seqIndex
     *          index of sequence in alignment
     * @return sequence position of residue at column, or adjacent residue if at a
     *         gap
     */
 -  int setStatusMessage(SequenceI sequence, final int column, int seq)
 +  int setStatusMessage(SequenceI sequence, final int column, int seqIndex)
 +  {
 +    char sequenceChar = sequence.getCharAt(column);
 +    int pos = sequence.findPosition(column);
 +    setStatusMessage(sequence, seqIndex, sequenceChar, pos);
 +
 +    return pos;
 +  }
 +
 +  /**
 +   * Builds the status message for the current cursor location and writes it to
 +   * the status bar, for example
 +   * 
 +   * <pre>
 +   * Sequence 3 ID: FER1_SOLLC
 +   * Sequence 5 ID: FER1_PEA Residue: THR (4)
 +   * Sequence 5 ID: FER1_PEA Residue: B (3)
 +   * Sequence 6 ID: O.niloticus.3 Nucleotide: Uracil (2)
 +   * </pre>
 +   * 
 +   * @param sequence
 +   * @param seqIndex
 +   *          sequence position in the alignment (1..)
 +   * @param sequenceChar
 +   *          the character under the cursor
 +   * @param residuePos
 +   *          the sequence residue position (if not over a gap)
 +   */
 +  protected void setStatusMessage(SequenceI sequence, int seqIndex,
 +          char sequenceChar, int residuePos)
    {
      StringBuilder text = new StringBuilder(32);
  
      /*
       * Sequence number (if known), and sequence name.
       */
 -    String seqno = seq == -1 ? "" : " " + (seq + 1);
 +    String seqno = seqIndex == -1 ? "" : " " + (seqIndex + 1);
      text.append("Sequence").append(seqno).append(" ID: ")
              .append(sequence.getName());
  
      /*
       * Try to translate the display character to residue name (null for gap).
       */
 -    final String displayChar = String.valueOf(sequence.getCharAt(column));
 -    boolean isGapped = Comparison.isGap(sequence.getCharAt(column));
 -    int pos = sequence.findPosition(column);
 +    boolean isGapped = Comparison.isGap(sequenceChar);
  
      if (!isGapped)
      {
        boolean nucleotide = av.getAlignment().isNucleotide();
 +      String displayChar = String.valueOf(sequenceChar);
        if (nucleotide)
        {
          residue = ResidueProperties.nucleotideName.get(displayChar);
        }
        else
        {
-         residue = "X".equalsIgnoreCase(displayChar) ? "X" : ("*"
-                 .equals(displayChar) ? "STOP"
-                 : ResidueProperties.aa2Triplet.get(displayChar));
+         residue = "X".equalsIgnoreCase(displayChar) ? "X"
+                 : ("*".equals(displayChar) ? "STOP"
+                         : ResidueProperties.aa2Triplet.get(displayChar));
        }
        text.append(" ").append(nucleotide ? "Nucleotide" : "Residue")
                .append(": ").append(residue == null ? displayChar : residue);
  
 -      text.append(" (").append(Integer.toString(pos)).append(")");
 +      text.append(" (").append(Integer.toString(residuePos)).append(")");
      }
      ap.alignFrame.statusBar.setText(text.toString());
 -
 -    return pos;
    }
  
    /**
  
        if (seq == ds)
        {
 -        /*
 -         * Convert position in sequence (base 1) to sequence character array
 -         * index (base 0)
 -         */
 -        int start = m.getStart() - m.getSequence().getStart();
 -        setStatusMessage(seq, start, sequenceIndex);
 +        int start = m.getStart();
 +        setStatusMessage(seq, sequenceIndex, seq.getCharAt(start - 1),
 +                start);
          return;
        }
      }
        int oldWidth = av.getCharWidth();
  
        // Which is bigger, left-right or up-down?
-       if (Math.abs(evt.getY() - lastMousePress.getY()) > Math.abs(evt
-               .getX() - lastMousePress.getX()))
+       if (Math.abs(evt.getY() - lastMousePress.getY()) > Math
+               .abs(evt.getX() - lastMousePress.getX()))
        {
          /*
           * on drag up or down, decrement or increment font size
        }
        if (editCommand == null)
        {
-         editCommand = new EditCommand(MessageManager.formatMessage(
-                 "label.edit_params", new String[] { label }));
+         editCommand = new EditCommand(MessageManager
+                 .formatMessage("label.edit_params", new String[]
+                 { label }));
        }
      }
  
      ap.alignFrame.statusBar.setText(message.toString());
  
      // Are we editing within a selection group?
-     if (groupEditing
-             || (sg != null && sg.getSequences(av.getHiddenRepSequences())
-                     .contains(seq)))
+     if (groupEditing || (sg != null
+             && sg.getSequences(av.getHiddenRepSequences()).contains(seq)))
      {
        fixedColumns = true;
  
          // Find the next gap before the end
          // of the visible region boundary
          boolean blank = false;
--        for (fixedRight = fixedRight; fixedRight > lastres; fixedRight--)
++        for (; fixedRight > lastres; fixedRight--)
          {
            blank = true;
  
          }
          else
          {
-           appendEdit(Action.INSERT_GAP, groupSeqs, startres, startres
-                   - lastres);
+           appendEdit(Action.INSERT_GAP, groupSeqs, startres,
+                   startres - lastres);
          }
        }
        else
          }
          else
          {
-           appendEdit(Action.DELETE_GAP, groupSeqs, startres, lastres
-                   - startres);
+           appendEdit(Action.DELETE_GAP, groupSeqs, startres,
+                   lastres - startres);
          }
  
        }
        }
  
        int column = findColumn(evt);
 -      boolean isGapped = Comparison.isGap(sequence.getCharAt(column));
  
        /*
         * find features at the position (if not gapped), or straddling
         * the position (if at a gap)
         */
        List<SequenceFeature> features = seqCanvas.getFeatureRenderer()
 -              .findFeaturesAtRes(sequence.getDatasetSequence(),
 -                      sequence.findPosition(column));
 -      if (isGapped)
 -      {
 -        removeAdjacentFeatures(features, column, sequence);
 -      }
 +              .findFeaturesAtColumn(sequence, column + 1);
  
        if (!features.isEmpty())
        {
           * highlight the first feature at the position on the alignment
           */
          SearchResultsI highlight = new SearchResults();
 -        highlight.addResult(sequence, features.get(0).getBegin(),
 -                features.get(0).getEnd());
 -        seqCanvas.highlightSearchResults(highlight);
 +        highlight.addResult(sequence, features.get(0).getBegin(), features
 +                .get(0).getEnd());
 +        seqCanvas.highlightSearchResults(highlight, false);
  
          /*
           * open the Amend Features dialog; clear highlighting afterwards,
          List<SequenceI> seqs = Collections.singletonList(sequence);
          seqCanvas.getFeatureRenderer().amendFeatures(seqs, features, false,
                  ap);
 -        seqCanvas.highlightSearchResults(null);
 +        av.setSearchResults(null); // clear highlighting
 +        seqCanvas.repaint(); // draw new/amended features
        }
      }
    }
  
      if (av.getWrapAlignment() && seq > av.getAlignment().getHeight())
      {
-       JvOptionPane.showInternalMessageDialog(Desktop.desktop, MessageManager
-               .getString("label.cannot_edit_annotations_in_wrapped_view"),
+       JvOptionPane.showInternalMessageDialog(Desktop.desktop,
+               MessageManager.getString(
+                       "label.cannot_edit_annotations_in_wrapped_view"),
                MessageManager.getString("label.wrapped_view_no_edit"),
                JvOptionPane.WARNING_MESSAGE);
        return;
     */
    void showPopupMenu(MouseEvent evt)
    {
 -    final int res = findColumn(evt);
 +    final int column = findColumn(evt);
      final int seq = findSeq(evt);
      SequenceI sequence = av.getAlignment().getSequenceAt(seq);
      List<SequenceFeature> allFeatures = ap.getFeatureRenderer()
 -            .findFeaturesAtRes(sequence.getDatasetSequence(),
 -                    sequence.findPosition(res));
 +            .findFeaturesAtColumn(sequence, column + 1);
      List<String> links = new ArrayList<>();
      for (SequenceFeature sf : allFeatures)
      {
        stretchGroup.cs.alignmentChanged(stretchGroup,
                av.getHiddenRepSequences());
  
-       ResidueShaderI groupColourScheme = stretchGroup.getGroupColourScheme();
+       ResidueShaderI groupColourScheme = stretchGroup
+               .getGroupColourScheme();
        String name = stretchGroup.getName();
        if (stretchGroup.cs.conservationApplied())
        {
              running = av.getRanges().scrollUp(true);
            }
  
-           if (mouseDragging && (evt.getY() >= getHeight())
-                   && (av.getAlignment().getHeight() > av.getRanges()
-                           .getEndSeq()))
+           if (mouseDragging && (evt.getY() >= getHeight()) && (av
+                   .getAlignment().getHeight() > av.getRanges().getEndSeq()))
            {
              running = av.getRanges().scrollUp(false);
            }
      // handles selection messages...
      // TODO: extend config options to allow user to control if selections may be
      // shared between viewports.
-     boolean iSentTheSelection = (av == source || (source instanceof AlignViewport && ((AlignmentViewport) source)
-             .getSequenceSetId().equals(av.getSequenceSetId())));
+     boolean iSentTheSelection = (av == source
+             || (source instanceof AlignViewport
+                     && ((AlignmentViewport) source).getSequenceSetId()
+                             .equals(av.getSequenceSetId())));
  
      if (iSentTheSelection)
      {
        repaint = true;
      }
  
-     if (copycolsel
-             && av.hasHiddenColumns()
+     if (copycolsel && av.hasHiddenColumns()
              && (av.getAlignment().getHiddenColumns() == null))
      {
        System.err.println("Bad things");
@@@ -136,11 -136,10 +136,10 @@@ public class SequenceFetcher extends JP
      {
        if (guiWindow != null)
        {
-         guiWindow
-                 .setProgressBar(
-                         MessageManager
-                                 .getString("status.waiting_sequence_database_fetchers_init"),
-                         Thread.currentThread().hashCode());
+         guiWindow.setProgressBar(
+                 MessageManager.getString(
+                         "status.waiting_sequence_database_fetchers_init"),
+                 Thread.currentThread().hashCode());
        }
        // initting happening on another thread - so wait around to see if it
        // finishes.
        }
        if (guiWindow != null)
        {
-         guiWindow
-                 .setProgressBar(
-                         MessageManager
-                                 .getString("status.waiting_sequence_database_fetchers_init"),
-                         Thread.currentThread().hashCode());
+         guiWindow.setProgressBar(
+                 MessageManager.getString(
+                         "status.waiting_sequence_database_fetchers_init"),
+                 Thread.currentThread().hashCode());
        }
      }
-     if (sfetch == null
-             || dasRegistry != Cache.getDasSourceRegistry()
+     if (sfetch == null || dasRegistry != Cache.getDasSourceRegistry()
              || lastDasSourceRegistry != (Cache.getDasSourceRegistry()
-                     .getDasRegistryURL() + Cache.getDasSourceRegistry()
-                     .getLocalSourceString()).hashCode())
+                     .getDasRegistryURL()
+                     + Cache.getDasSourceRegistry().getLocalSourceString())
+                             .hashCode())
      {
        _initingFetcher = true;
        initingThread = Thread.currentThread();
         */
        if (guiWindow != null)
        {
-         guiWindow.setProgressBar(MessageManager
-                 .getString("status.init_sequence_database_fetchers"),
+         guiWindow.setProgressBar(
+                 MessageManager.getString(
+                         "status.init_sequence_database_fetchers"),
                  Thread.currentThread().hashCode());
        }
        dasRegistry = Cache.getDasSourceRegistry();
        {
          guiWindow.setProgressBar(null, Thread.currentThread().hashCode());
        }
-       lastDasSourceRegistry = (dasRegistry.getDasRegistryURL() + dasRegistry
-               .getLocalSourceString()).hashCode();
+       lastDasSourceRegistry = (dasRegistry.getDasRegistryURL()
+               + dasRegistry.getLocalSourceString()).hashCode();
        sfetch = sf;
        _initingFetcher = false;
        initingThread = null;
              @Override
              public void run()
              {
-               JvOptionPane
-                       .showInternalMessageDialog(
-                               Desktop.desktop,
-                               MessageManager
-                                       .getString("warn.couldnt_create_sequence_fetcher_client"),
-                               MessageManager
-                                       .getString("label.couldnt_create_sequence_fetcher"),
-                               JvOptionPane.ERROR_MESSAGE);
+               JvOptionPane.showInternalMessageDialog(Desktop.desktop,
+                       MessageManager.getString(
+                               "warn.couldnt_create_sequence_fetcher_client"),
+                       MessageManager.getString(
+                               "label.couldnt_create_sequence_fetcher"),
+                       JvOptionPane.ERROR_MESSAGE);
              }
            });
  
          if (sourcep.getTier() == 0)
          {
            database.selection = Arrays
-                   .asList(new DbSourceProxy[] { sourcep });
+                   .asList(new DbSourceProxy[]
+                   { sourcep });
            break;
          }
        }
        if (database.selection == null || database.selection.size() == 0)
        {
-         System.err.println("Ignoring fetch parameter db='" + selectedDb
-                 + "'");
+         System.err.println(
+                 "Ignoring fetch parameter db='" + selectedDb + "'");
          return false;
        }
        textArea.setText(queryString);
  
    private String getFrameTitle()
    {
-     return ((alignFrame == null) ? MessageManager
-             .getString("label.new_sequence_fetcher") : MessageManager
-             .getString("label.additional_sequence_fetcher"));
+     return ((alignFrame == null)
+             ? MessageManager.getString("label.new_sequence_fetcher")
+             : MessageManager
+                     .getString("label.additional_sequence_fetcher"));
    }
  
    private void jbInit() throws Exception
      replacePunctuation.setHorizontalAlignment(SwingConstants.CENTER);
      replacePunctuation
              .setFont(new java.awt.Font("Verdana", Font.ITALIC, 11));
-     replacePunctuation.setText(MessageManager
-             .getString("label.replace_commas_semicolons"));
+     replacePunctuation.setText(
+             MessageManager.getString("label.replace_commas_semicolons"));
      ok.setText(MessageManager.getString("action.ok"));
      ok.addActionListener(new ActionListener()
      {
                        + database.getSelectedSources().size() + " others)"
                        : ""));
        String eq = database.getExampleQueries();
-       dbeg.setText(MessageManager.formatMessage(
-               "label.example_query_param", new String[] { eq }));
+       dbeg.setText(MessageManager.formatMessage("label.example_query_param",
+               new String[]
+               { eq }));
        boolean enablePunct = !(eq != null && eq.indexOf(",") > -1);
        for (DbSourceProxy dbs : database.getSelectedSources())
        {
      if (replacePunctuation.isEnabled() && replacePunctuation.isSelected())
      {
        empty = new com.stevesoft.pat.Regex(
-       // replace commas and spaces with a semicolon
+               // replace commas and spaces with a semicolon
                "(\\s|[,; ])+", ";");
      }
      else
      }
      textArea.setText(empty.replaceAll(textArea.getText()));
      // see if there's anthing to search with
-     if (!new com.stevesoft.pat.Regex("[A-Za-z0-9_.]").search(textArea
-             .getText()))
+     if (!new com.stevesoft.pat.Regex("[A-Za-z0-9_.]")
+             .search(textArea.getText()))
      {
        error += "Please enter a (semi-colon separated list of) database id(s)";
      }
      Iterator<DbSourceProxy> proxies = database.getSelectedSources()
              .iterator();
      String[] qries;
-     List<String> nextFetch = Arrays.asList(qries = textArea.getText()
-             .split(";"));
+     List<String> nextFetch = Arrays
+             .asList(qries = textArea.getText().split(";"));
      Iterator<String> en = Arrays.asList(new String[0]).iterator();
      int nqueries = qries.length;
  
        try
        {
          // update status
-         guiWindow
-                 .setProgressBar(MessageManager.formatMessage(
-                         "status.fetching_sequence_queries_from",
-                         new String[] {
-                             Integer.valueOf(nqueries).toString(),
-                             proxy.getDbName() }), Thread.currentThread()
-                         .hashCode());
+         guiWindow.setProgressBar(MessageManager.formatMessage(
+                 "status.fetching_sequence_queries_from", new String[]
+                 { Integer.valueOf(nqueries).toString(),
+                     proxy.getDbName() }),
+                 Thread.currentThread().hashCode());
          if (proxy.getMaximumQueryCount() == 1)
          {
            /*
          }
        } catch (Exception e)
        {
-         showErrorMessage("Error retrieving " + textArea.getText()
-                 + " from " + database.getSelectedItem());
+         showErrorMessage("Error retrieving " + textArea.getText() + " from "
+                 + database.getSelectedItem());
          // error
          // +="Couldn't retrieve sequences from "+database.getSelectedItem();
          System.err.println("Retrieval failed for source ='"
        } catch (OutOfMemoryError e)
        {
          showErrorMessage("Out of Memory when retrieving "
-                 + textArea.getText()
-                 + " from "
-                 + database.getSelectedItem()
+                 + textArea.getText() + " from " + database.getSelectedItem()
                  + "\nPlease see the Jalview FAQ for instructions for increasing the memory available to Jalview.\n");
          e.printStackTrace();
        } catch (Error e)
            while (aresult.size() > 0)
            {
              presult.add(aresult.remove(0));
-             presultTitle.add(aresultq.remove(0) + " "
-                     + getDefaultRetrievalTitle());
+             presultTitle.add(
+                     aresultq.remove(0) + " " + getDefaultRetrievalTitle());
            }
          }
          else
            presultTitle.add(titl);
          }
        }
-       guiWindow.setProgressBar(MessageManager
-               .getString("status.finshed_querying"), Thread.currentThread()
-               .hashCode());
+       guiWindow.setProgressBar(
+               MessageManager.getString("status.finshed_querying"),
+               Thread.currentThread().hashCode());
      }
-     guiWindow.setProgressBar(
-             (presult.size() > 0) ? MessageManager
-                     .getString("status.parsing_results") : MessageManager
-                     .getString("status.processing"), Thread.currentThread()
-                     .hashCode());
+     guiWindow
+             .setProgressBar(
+                     (presult.size() > 0)
+                             ? MessageManager
+                                     .getString("status.parsing_results")
+                             : MessageManager.getString("status.processing"),
+                     Thread.currentThread().hashCode());
      // process results
      while (presult.size() > 0)
      {
      {
        StringBuffer sb = new StringBuffer();
        sb.append("Didn't retrieve the following "
-               + (nextFetch.size() == 1 ? "query" : nextFetch.size()
-                       + " queries") + ": \n");
+               + (nextFetch.size() == 1 ? "query"
+                       : nextFetch.size() + " queries")
+               + ": \n");
        int l = sb.length(), lr = 0;
        for (String s : nextFetch)
        {
     */
    void fetchMultipleAccessions(DbSourceProxy proxy,
            Iterator<String> accessions, List<String> aresultq,
-           List<AlignmentI> aresult, List<String> nextFetch)
-           throws Exception
+           List<AlignmentI> aresult, List<String> nextFetch) throws Exception
    {
      StringBuilder multiacc = new StringBuilder();
      List<String> tosend = new ArrayList<String>();
          indres = proxy.getSequenceRecords(accession);
        } catch (OutOfMemoryError oome)
        {
-         new OOMWarning("fetching " + accession + " from "
-                 + proxy.getDbName(), oome, this);
+         new OOMWarning(
+                 "fetching " + accession + " from " + proxy.getDbName(),
+                 oome, this);
        }
        if (indres != null)
        {
        }
      } catch (Exception e)
      {
-       Cache.log.info(
-               "Error retrieving " + accession + " from "
-                       + proxy.getDbName(), e);
+       Cache.log.info("Error retrieving " + accession + " from "
+               + proxy.getDbName(), e);
      }
      return success;
    }
          {
            for (SequenceI sq : alsqs)
            {
 -            if ((sfs = sq.getSequenceFeatures()) != null)
 +            if (sq.getFeatures().hasFeatures())
              {
 -              if (sfs.length > 0)
 -              {
 -                af.setShowSeqFeatures(true);
 -                break;
 -              }
 +              af.setShowSeqFeatures(true);
 +              break;
              }
 -
            }
          }
  
@@@ -158,8 -158,8 +158,8 @@@ public class TreePanel extends GTreePan
            }
            if (evt.getNewValue() == null)
            {
-             System.out
-                     .println("new alignment sequences vector value is null");
+             System.out.println(
+                     "new alignment sequences vector value is null");
            }
  
            tree.updatePlaceHolders((List<SequenceI>) evt.getNewValue());
  
    void buildAssociatedViewMenu()
    {
-     AlignmentPanel[] aps = PaintRefresher.getAssociatedPanels(av
-             .getSequenceSetId());
+     AlignmentPanel[] aps = PaintRefresher
+             .getAssociatedPanels(av.getSequenceSetId());
      if (aps.length == 1 && treeCanvas.ap == aps[0])
      {
        associateLeavesMenu.setVisible(false);
  
      associateLeavesMenu.setVisible(true);
  
-     if ((viewMenu.getItem(viewMenu.getItemCount() - 2) instanceof JMenuItem))
+     if ((viewMenu
+             .getItem(viewMenu.getItemCount() - 2) instanceof JMenuItem))
      {
        viewMenu.insertSeparator(viewMenu.getItemCount() - 1);
      }
        }
        else
        {
-         ScoreModelI sm = ScoreModels.getInstance().getScoreModel(
-                 scoreModelName, treeCanvas.ap);
-         TreeBuilder njtree = treeType.equals(TreeBuilder.NEIGHBOUR_JOINING) ? new NJTree(
-                 av, sm, similarityParams) : new AverageDistanceTree(av, sm,
-                 similarityParams);
+         ScoreModelI sm = ScoreModels.getInstance()
+                 .getScoreModel(scoreModelName, treeCanvas.ap);
+         TreeBuilder njtree = treeType.equals(TreeBuilder.NEIGHBOUR_JOINING)
+                 ? new NJTree(av, sm, similarityParams)
+                 : new AverageDistanceTree(av, sm, similarityParams);
          tree = new TreeModel(njtree);
          showDistances(true);
        }
      JalviewFileChooser chooser = new JalviewFileChooser(
              jalview.bin.Cache.getProperty("LAST_DIRECTORY"));
      chooser.setFileView(new JalviewFileView());
-     chooser.setDialogTitle(MessageManager
-             .getString("label.save_tree_as_newick"));
+     chooser.setDialogTitle(
+             MessageManager.getString("label.save_tree_as_newick"));
      chooser.setToolTipText(MessageManager.getString("action.save"));
  
      int value = chooser.showSaveDialog(null);
      if (value == JalviewFileChooser.APPROVE_OPTION)
      {
        String choice = chooser.getSelectedFile().getPath();
-       jalview.bin.Cache.setProperty("LAST_DIRECTORY", chooser
-               .getSelectedFile().getParent());
+       jalview.bin.Cache.setProperty("LAST_DIRECTORY",
+               chooser.getSelectedFile().getParent());
  
        try
        {
          jalview.io.NewickFile fout = new jalview.io.NewickFile(
                  tree.getTopNode());
-         String output = fout.print(tree.hasBootstrap(),
-                 tree.hasDistances(), tree.hasRootDistance());
+         String output = fout.print(tree.hasBootstrap(), tree.hasDistances(),
+                 tree.hasRootDistance());
          java.io.PrintWriter out = new java.io.PrintWriter(
                  new java.io.FileWriter(choice));
          out.println(output);
      AlignmentView originalData = tree.getOriginalData();
      if (originalData == null)
      {
-       jalview.bin.Cache.log
-               .info("Unexpected call to originalSeqData_actionPerformed - should have hidden this menu action.");
+       jalview.bin.Cache.log.info(
+               "Unexpected call to originalSeqData_actionPerformed - should have hidden this menu action.");
        return;
      }
      // decide if av alignment is sufficiently different to original data to
        // AlignmentOrder origorder = new AlignmentOrder(alAndColsel[0]);
  
        AlignmentI al = new Alignment((SequenceI[]) alAndColsel[0]);
-       AlignmentI dataset = (av != null && av.getAlignment() != null) ? av
-               .getAlignment().getDataset() : null;
+       AlignmentI dataset = (av != null && av.getAlignment() != null)
+               ? av.getAlignment().getDataset()
+               : null;
        if (dataset != null)
        {
          al.setDataset(dataset);
        {
          // make a new frame!
          AlignFrame af = new AlignFrame(al, (HiddenColumns) alAndColsel[1],
-                 AlignFrame.DEFAULT_WIDTH,
-                 AlignFrame.DEFAULT_HEIGHT);
+                 AlignFrame.DEFAULT_WIDTH, AlignFrame.DEFAULT_HEIGHT);
  
          // >>>This is a fix for the moment, until a better solution is
          // found!!<<<
          // msaorder);
  
          Desktop.addInternalFrame(af, MessageManager.formatMessage(
-                 "label.original_data_for_params",
-                 new Object[] { this.title }), AlignFrame.DEFAULT_WIDTH,
+                 "label.original_data_for_params", new Object[]
+                 { this.title }), AlignFrame.DEFAULT_WIDTH,
                  AlignFrame.DEFAULT_HEIGHT);
        }
      }
      if (treeCanvas.applyToAllViews)
      {
        final ArrayList<CommandI> commands = new ArrayList<CommandI>();
-       for (AlignmentPanel ap : PaintRefresher.getAssociatedPanels(av
-               .getSequenceSetId()))
+       for (AlignmentPanel ap : PaintRefresher
+               .getAssociatedPanels(av.getSequenceSetId()))
        {
          commands.add(sortAlignmentIn(ap.av.getAlignPanel()));
        }
            }
          }
        });
-       for (AlignmentPanel ap : PaintRefresher.getAssociatedPanels(av
-               .getSequenceSetId()))
+       for (AlignmentPanel ap : PaintRefresher
+               .getAssociatedPanels(av.getSequenceSetId()))
        {
          // ensure all the alignFrames refresh their GI after adding an undo item
          ap.alignFrame.updateEditMenuBar();
        JalviewFileChooser chooser = new JalviewFileChooser(
                ImageMaker.EPS_EXTENSION, ImageMaker.EPS_EXTENSION);
        chooser.setFileView(new JalviewFileView());
-       chooser.setDialogTitle(MessageManager
-               .getString("label.create_eps_from_tree"));
+       chooser.setDialogTitle(
+               MessageManager.getString("label.create_eps_from_tree"));
        chooser.setToolTipText(MessageManager.getString("action.save"));
  
        int value = chooser.showSaveDialog(this);
          return;
        }
  
-       Cache.setProperty("LAST_DIRECTORY", chooser.getSelectedFile()
-               .getParent());
+       Cache.setProperty("LAST_DIRECTORY",
+               chooser.getSelectedFile().getParent());
  
-       FileOutputStream out = new FileOutputStream(chooser.getSelectedFile());
-       EpsGraphics2D pg = new EpsGraphics2D("Tree", out, 0, 0, width, height);
+       FileOutputStream out = new FileOutputStream(
+               chooser.getSelectedFile());
+       EpsGraphics2D pg = new EpsGraphics2D("Tree", out, 0, 0, width,
+               height);
  
        pg.setAccurateTextMode(accurateText);
  
                ImageMaker.PNG_EXTENSION, ImageMaker.PNG_DESCRIPTION);
  
        chooser.setFileView(new jalview.io.JalviewFileView());
-       chooser.setDialogTitle(MessageManager
-               .getString("label.create_png_from_tree"));
+       chooser.setDialogTitle(
+               MessageManager.getString("label.create_png_from_tree"));
        chooser.setToolTipText(MessageManager.getString("action.save"));
  
        int value = chooser.showSaveDialog(this);
          return;
        }
  
-       jalview.bin.Cache.setProperty("LAST_DIRECTORY", chooser
-               .getSelectedFile().getParent());
+       jalview.bin.Cache.setProperty("LAST_DIRECTORY",
+               chooser.getSelectedFile().getParent());
  
-       FileOutputStream out = new FileOutputStream(chooser.getSelectedFile());
+       FileOutputStream out = new FileOutputStream(
+               chooser.getSelectedFile());
  
        BufferedImage bi = new BufferedImage(width, height,
                BufferedImage.TYPE_INT_RGB);
            if (sq != null)
            {
              // search dbrefs, features and annotation
-             DBRefEntry[] refs = jalview.util.DBRefUtils.selectRefs(
-                     sq.getDBRefs(),
-                     new String[] { labelClass.toUpperCase() });
+             DBRefEntry[] refs = jalview.util.DBRefUtils
+                     .selectRefs(sq.getDBRefs(), new String[]
+                     { labelClass.toUpperCase() });
              if (refs != null)
              {
                for (int i = 0; i < refs.length; i++)
              }
              if (newname == null)
              {
 -              SequenceFeature sf[] = sq.getSequenceFeatures();
 -              for (int i = 0; sf != null && i < sf.length; i++)
 +              List<SequenceFeature> features = sq.getFeatures()
 +                      .getPositionalFeatures(labelClass);
 +              for (SequenceFeature feature : features)
                {
 -                if (sf[i].getType().equals(labelClass))
 +                if (newname == null)
 +                {
 +                  newname = feature.getDescription();
 +                }
 +                else
                  {
 -                  if (newname == null)
 -                  {
 -                    newname = new String(sf[i].getDescription());
 -                  }
 -                  else
 -                  {
 -                    newname = newname + "; " + sf[i].getDescription();
 -                  }
 +                  newname = newname + "; " + feature.getDescription();
                  }
                }
              }
     * Neighbour Joining Using BLOSUM62
     * <p>
     * For a tree loaded from file, just uses the file name
+    * 
     * @return
     */
    public String getPanelTitle()
      /*
       * i18n description of Neighbour Joining or Average Distance method
       */
-     String treecalcnm = MessageManager.getString("label.tree_calc_"
-             + treeType.toLowerCase());
+     String treecalcnm = MessageManager
+             .getString("label.tree_calc_" + treeType.toLowerCase());
  
      /*
       * short score model name (long description can be too long)
@@@ -61,7 -61,8 +61,8 @@@ public class ClustalFile extends AlignF
      boolean flag = false;
      boolean rna = false;
      boolean top = false;
-     StringBuffer pssecstr = new StringBuffer(), consstr = new StringBuffer();
+     StringBuffer pssecstr = new StringBuffer(),
+             consstr = new StringBuffer();
      Vector headers = new Vector();
      Hashtable seqhash = new Hashtable();
      StringBuffer tempseq;
            }
  
            Sequence newSeq = parseId(headers.elementAt(i).toString());
-           newSeq.setSequence(seqhash.get(headers.elementAt(i).toString())
-                   .toString());
+           newSeq.setSequence(
+                   seqhash.get(headers.elementAt(i).toString()).toString());
  
            seqs.addElement(newSeq);
          }
          else
          {
-           System.err
-                   .println("Clustal File Reader: Can't find sequence for "
-                           + headers.elementAt(i));
+           System.err.println("Clustal File Reader: Can't find sequence for "
+                   + headers.elementAt(i));
          }
        }
        AlignmentAnnotation lastssa = null;
          AlignmentAnnotation ssa = StockholmFile.parseAnnotationRow(ss,
                  "secondary structure", consstr.toString());
          ssa.label = "Consensus Secondary Structure";
-         if (lastssa == null
-                 || !lastssa.getRNAStruc().equals(
-                         ssa.getRNAStruc().replace('-', '.')))
+         if (lastssa == null || !lastssa.getRNAStruc()
+                 .equals(ssa.getRNAStruc().replace('-', '.')))
          {
            annotations.addElement(ssa);
          }
      {
        String tmp = printId(s[i], jvsuffix);
  
 -      if (s[i].getSequence().length > max)
 -      {
 -        max = s[i].getSequence().length;
 -      }
 +      max = Math.max(max, s[i].getLength());
  
        if (tmp.length() > maxid)
        {
  
        while ((j < s.length) && (s[j] != null))
        {
-         out.append(new Format("%-" + maxid + "s").form(printId(s[j],
-                 jvsuffix) + " "));
+         out.append(new Format("%-" + maxid + "s")
+                 .form(printId(s[j], jvsuffix) + " "));
  
          int start = i * len;
          int end = start + len;
  
 -        if ((end < s[j].getSequence().length)
 -                && (start < s[j].getSequence().length))
 +        int length = s[j].getLength();
 +        if ((end < length) && (start < length))
          {
            out.append(s[j].getSequenceAsString(start, end));
          }
          else
          {
 -          if (start < s[j].getSequence().length)
 +          if (start < length)
            {
              out.append(s[j].getSequenceAsString().substring(start));
            }
@@@ -44,9 -44,8 +44,8 @@@ import java.awt.Color
  import java.io.IOException;
  import java.util.ArrayList;
  import java.util.Arrays;
 +import java.util.Collections;
- import java.util.Comparator;
  import java.util.HashMap;
 -import java.util.Iterator;
  import java.util.List;
  import java.util.Map;
  import java.util.Map.Entry;
@@@ -77,19 -76,6 +76,6 @@@ public class FeaturesFile extends Align
  
    protected static final String GFF_VERSION = "##gff-version";
  
-   private static final Comparator<String> SORT_NULL_LAST = new Comparator<String>()
-   {
-     @Override
-     public int compare(String o1, String o2)
-     {
-       if (o1 == null)
-       {
-         return o2 == null ? 0 : 1;
-       }
-       return (o2 == null ? -1 : o1.compareTo(o2));
-     }
-   };
    private AlignmentI lastmatchedAl = null;
  
    private SequenceIdMatcher matcher = null;
    /**
     * Constructor which does not parse the file immediately
     * 
--   * @param inFile
++   * @param file
     * @param paste
     * @throws IOException
     */
--  public FeaturesFile(String inFile, DataSourceType paste)
++  public FeaturesFile(String file, DataSourceType paste)
            throws IOException
    {
--    super(false, inFile, paste);
++    super(false, file, paste);
    }
  
    /**
     * Constructor that optionally parses the file immediately
     * 
     * @param parseImmediately
--   * @param inFile
++   * @param file
     * @param type
     * @throws IOException
     */
--  public FeaturesFile(boolean parseImmediately, String inFile,
-           DataSourceType type)
-           throws IOException
++  public FeaturesFile(boolean parseImmediately, String file,
+           DataSourceType type) throws IOException
    {
--    super(parseImmediately, inFile, type);
++    super(parseImmediately, file, type);
    }
  
    /**
       */
      for (SequenceI newseq : newseqs)
      {
 -      if (newseq.getSequenceFeatures() != null)
 +      if (newseq.getFeatures().hasFeatures())
        {
          align.addSequence(newseq);
        }
     */
    protected boolean parseJalviewFeature(String line, String[] gffColumns,
            AlignmentI alignment, Map<String, FeatureColourI> featureColours,
-           boolean removeHTML, boolean relaxedIdMatching, String featureGroup)
+           boolean removeHTML, boolean relaxedIdMatching,
+           String featureGroup)
    {
      /*
       * tokens: description seqid seqIndex start end type [score]
        Color colour = ColorUtils.createColourFromName(ft);
        featureColours.put(ft, new FeatureColour(colour));
      }
 -    SequenceFeature sf = new SequenceFeature(ft, desc, "", startPos, endPos,
 -            featureGroup);
 +    SequenceFeature sf = null;
      if (gffColumns.length > 6)
      {
        float score = Float.NaN;
        try
        {
          score = new Float(gffColumns[6]).floatValue();
 -        // update colourgradient bounds if allowed to
        } catch (NumberFormatException ex)
        {
 -        // leave as NaN
 +        sf = new SequenceFeature(ft, desc, startPos, endPos, featureGroup);
        }
 -      sf.setScore(score);
 +      sf = new SequenceFeature(ft, desc, startPos, endPos, score,
 +              featureGroup);
 +    }
 +    else
 +    {
 +      sf = new SequenceFeature(ft, desc, startPos, endPos, featureGroup);
      }
  
      parseDescriptionHTML(sf, removeHTML);
      ParseHtmlBodyAndLinks parsed = new ParseHtmlBodyAndLinks(
              sf.getDescription(), removeHTML, newline);
  
 -    sf.description = (removeHTML) ? parsed.getNonHtmlContent()
 -            : sf.description;
 +    if (removeHTML)
 +    {
 +      sf.setDescription(parsed.getNonHtmlContent());
 +    }
 +
      for (String link : parsed.getLinks())
      {
        sf.addLink(link);
      }
    }
  
    /**
 -   * generate a features file for seqs includes non-pos features by default.
 -   * 
 -   * @param sequences
 -   *          source of sequence features
 -   * @param visible
 -   *          hash of feature types and colours
 -   * @return features file contents
 -   */
 -  public String printJalviewFormat(SequenceI[] sequences,
 -          Map<String, FeatureColourI> visible)
 -  {
 -    return printJalviewFormat(sequences, visible, true, true);
 -  }
 -
 -  /**
 -   * generate a features file for seqs with colours from visible (if any)
 +   * Returns contents of a Jalview format features file, for visible features,
 +   * as filtered by type and group. Features with a null group are displayed if
 +   * their feature type is visible. Non-positional features may optionally be
 +   * included (with no check on type or group).
     * 
     * @param sequences
     *          source of features
     * @param visible
 -   *          hash of Colours for each feature type
 -   * @param visOnly
 -   *          when true only feature types in 'visible' will be output
 -   * @param nonpos
 -   *          indicates if non-positional features should be output (regardless
 -   *          of group or type)
 -   * @return features file contents
 +   *          map of colour for each visible feature type
 +   * @param visibleFeatureGroups
 +   * @param includeNonPositional
 +   *          if true, include non-positional features (regardless of group or
 +   *          type)
 +   * @return
     */
    public String printJalviewFormat(SequenceI[] sequences,
 -          Map<String, FeatureColourI> visible, boolean visOnly,
 -          boolean nonpos)
 +          Map<String, FeatureColourI> visible,
 +          List<String> visibleFeatureGroups, boolean includeNonPositional)
    {
 -    StringBuilder out = new StringBuilder(256);
 -    boolean featuresGen = false;
 -    if (visOnly && !nonpos && (visible == null || visible.size() < 1))
 +    if (!includeNonPositional && (visible == null || visible.isEmpty()))
      {
        // no point continuing.
        return "No Features Visible";
      }
  
 -    if (visible != null && visOnly)
 +    /*
 +     * write out feature colours (if we know them)
 +     */
 +    // TODO: decide if feature links should also be written here ?
 +    StringBuilder out = new StringBuilder(256);
 +    if (visible != null)
      {
 -      // write feature colours only if we're given them and we are generating
 -      // viewed features
 -      // TODO: decide if feature links should also be written here ?
 -      Iterator<String> en = visible.keySet().iterator();
 -      while (en.hasNext())
 +      for (Entry<String, FeatureColourI> featureColour : visible.entrySet())
        {
 -        String featureType = en.next().toString();
 -        FeatureColourI colour = visible.get(featureType);
 -        out.append(colour.toJalviewFormat(featureType)).append(newline);
 +        FeatureColourI colour = featureColour.getValue();
 +        out.append(colour.toJalviewFormat(featureColour.getKey())).append(
 +                newline);
        }
      }
  
 -    // Work out which groups are both present and visible
 -    List<String> groups = new ArrayList<String>();
 -    int groupIndex = 0;
 -    boolean isnonpos = false;
 +    String[] types = visible == null ? new String[0] : visible.keySet()
 +            .toArray(new String[visible.keySet().size()]);
  
 -    SequenceFeature[] features;
 -    for (int i = 0; i < sequences.length; i++)
 +    /*
 +     * sort groups alphabetically, and ensure that features with a
 +     * null or empty group are output after those in named groups
 +     */
 +    List<String> sortedGroups = new ArrayList<String>(visibleFeatureGroups);
 +    sortedGroups.remove(null);
 +    sortedGroups.remove("");
 +    Collections.sort(sortedGroups);
 +    sortedGroups.add(null);
 +    sortedGroups.add("");
 +
 +    boolean foundSome = false;
 +
 +    /*
 +     * first output any non-positional features
 +     */
 +    if (includeNonPositional)
      {
 -      features = sequences[i].getSequenceFeatures();
 -      if (features != null)
 +      for (int i = 0; i < sequences.length; i++)
        {
 -        for (int j = 0; j < features.length; j++)
 +        String sequenceName = sequences[i].getName();
 +        for (SequenceFeature feature : sequences[i].getFeatures()
 +                .getNonPositionalFeatures())
          {
 -          isnonpos = features[j].begin == 0 && features[j].end == 0;
 -          if ((!nonpos && isnonpos) || (!isnonpos && visOnly
 -                  && !visible.containsKey(features[j].type)))
 -          {
 -            continue;
 -          }
 -
 -          if (features[j].featureGroup != null
 -                  && !groups.contains(features[j].featureGroup))
 -          {
 -            groups.add(features[j].featureGroup);
 -          }
 +          foundSome = true;
 +          out.append(formatJalviewFeature(sequenceName, feature));
          }
        }
      }
  
 -    String group = null;
 -    do
 +    for (String group : sortedGroups)
      {
 -      if (groups.size() > 0 && groupIndex < groups.size())
 +      boolean isNamedGroup = (group != null && !"".equals(group));
 +      if (isNamedGroup)
        {
 -        group = groups.get(groupIndex);
          out.append(newline);
          out.append("STARTGROUP").append(TAB);
          out.append(group);
          out.append(newline);
        }
 -      else
 -      {
 -        group = null;
 -      }
  
 +      /*
 +       * output positional features within groups
 +       */
        for (int i = 0; i < sequences.length; i++)
        {
 -        features = sequences[i].getSequenceFeatures();
 -        if (features != null)
 +        String sequenceName = sequences[i].getName();
 +        List<SequenceFeature> features = new ArrayList<SequenceFeature>();
 +        if (types.length > 0)
          {
 -          for (SequenceFeature sequenceFeature : features)
 -          {
 -            isnonpos = sequenceFeature.begin == 0
 -                    && sequenceFeature.end == 0;
 -            if ((!nonpos && isnonpos) || (!isnonpos && visOnly
 -                    && !visible.containsKey(sequenceFeature.type)))
 -            {
 -              // skip if feature is nonpos and we ignore them or if we only
 -              // output visible and it isn't non-pos and it's not visible
 -              continue;
 -            }
 -
 -            if (group != null && (sequenceFeature.featureGroup == null
 -                    || !sequenceFeature.featureGroup.equals(group)))
 -            {
 -              continue;
 -            }
 +          features.addAll(sequences[i].getFeatures().getFeaturesForGroup(
 +                  true, group, types));
 +        }
  
 -            if (group == null && sequenceFeature.featureGroup != null)
 -            {
 -              continue;
 -            }
 -            // we have features to output
 -            featuresGen = true;
 -            if (sequenceFeature.description == null
 -                    || sequenceFeature.description.equals(""))
 -            {
 -              out.append(sequenceFeature.type).append(TAB);
 -            }
 -            else
 -            {
 -              if (sequenceFeature.links != null && sequenceFeature
 -                      .getDescription().indexOf("<html>") == -1)
 -              {
 -                out.append("<html>");
 -              }
 -
 -              out.append(sequenceFeature.description);
 -              if (sequenceFeature.links != null)
 -              {
 -                for (int l = 0; l < sequenceFeature.links.size(); l++)
 -                {
 -                  String label = sequenceFeature.links.elementAt(l);
 -                  String href = label.substring(label.indexOf("|") + 1);
 -                  label = label.substring(0, label.indexOf("|"));
 -
 -                  if (sequenceFeature.description.indexOf(href) == -1)
 -                  {
 -                    out.append(
 -                            " <a href=\"" + href + "\">" + label + "</a>");
 -                  }
 -                }
 -
 -                if (sequenceFeature.getDescription()
 -                        .indexOf("</html>") == -1)
 -                {
 -                  out.append("</html>");
 -                }
 -              }
 -
 -              out.append(TAB);
 -            }
 -            out.append(sequences[i].getName());
 -            out.append("\t-1\t");
 -            out.append(sequenceFeature.begin);
 -            out.append(TAB);
 -            out.append(sequenceFeature.end);
 -            out.append(TAB);
 -            out.append(sequenceFeature.type);
 -            if (!Float.isNaN(sequenceFeature.score))
 -            {
 -              out.append(TAB);
 -              out.append(sequenceFeature.score);
 -            }
 -            out.append(newline);
 -          }
 +        for (SequenceFeature sequenceFeature : features)
 +        {
 +          foundSome = true;
 +          out.append(formatJalviewFeature(sequenceName, sequenceFeature));
          }
        }
  
 -      if (group != null)
 +      if (isNamedGroup)
        {
          out.append("ENDGROUP").append(TAB);
          out.append(group);
          out.append(newline);
 -        groupIndex++;
        }
 -      else
 +    }
 +
 +    return foundSome ? out.toString() : "No Features Visible";
 +  }
 +
 +  /**
 +   * @param out
 +   * @param sequenceName
 +   * @param sequenceFeature
 +   */
 +  protected String formatJalviewFeature(
 +          String sequenceName, SequenceFeature sequenceFeature)
 +  {
 +    StringBuilder out = new StringBuilder(64);
 +    if (sequenceFeature.description == null
 +            || sequenceFeature.description.equals(""))
 +    {
 +      out.append(sequenceFeature.type).append(TAB);
 +    }
 +    else
 +    {
 +      if (sequenceFeature.links != null
 +              && sequenceFeature.getDescription().indexOf("<html>") == -1)
        {
 -        break;
 +        out.append("<html>");
        }
  
 -    } while (groupIndex < groups.size() + 1);
 +      out.append(sequenceFeature.description);
 +      if (sequenceFeature.links != null)
 +      {
 +        for (int l = 0; l < sequenceFeature.links.size(); l++)
 +        {
 +          String label = sequenceFeature.links.elementAt(l);
 +          String href = label.substring(label.indexOf("|") + 1);
 +          label = label.substring(0, label.indexOf("|"));
 +
 +          if (sequenceFeature.description.indexOf(href) == -1)
 +          {
 +            out.append(" <a href=\"" + href + "\">" + label + "</a>");
 +          }
 +        }
 +
 +        if (sequenceFeature.getDescription().indexOf("</html>") == -1)
 +        {
 +          out.append("</html>");
 +        }
 +      }
  
 -    if (!featuresGen)
 +      out.append(TAB);
 +    }
 +    out.append(sequenceName);
 +    out.append("\t-1\t");
 +    out.append(sequenceFeature.begin);
 +    out.append(TAB);
 +    out.append(sequenceFeature.end);
 +    out.append(TAB);
 +    out.append(sequenceFeature.type);
 +    if (!Float.isNaN(sequenceFeature.score))
      {
 -      return "No Features Visible";
 +      out.append(TAB);
 +      out.append(sequenceFeature.score);
      }
 +    out.append(newline);
  
      return out.toString();
    }
    }
  
    /**
 -   * Returns features output in GFF2 format, including hidden and non-positional
 -   * features
 -   * 
 -   * @param sequences
 -   *          the sequences whose features are to be output
 -   * @param visible
 -   *          a map whose keys are the type names of visible features
 -   * @return
 -   */
 -  public String printGffFormat(SequenceI[] sequences,
 -          Map<String, FeatureColourI> visible)
 -  {
 -    return printGffFormat(sequences, visible, true, true);
 -  }
 -
 -  /**
     * Returns features output in GFF2 format
     * 
     * @param sequences
     *          the sequences whose features are to be output
     * @param visible
     *          a map whose keys are the type names of visible features
 -   * @param outputVisibleOnly
 +   * @param visibleFeatureGroups
     * @param includeNonPositionalFeatures
     * @return
     */
    public String printGffFormat(SequenceI[] sequences,
 -          Map<String, FeatureColourI> visible, boolean outputVisibleOnly,
 +          Map<String, FeatureColourI> visible,
 +          List<String> visibleFeatureGroups,
            boolean includeNonPositionalFeatures)
    {
      StringBuilder out = new StringBuilder(256);
 -    int version = gffVersion == 0 ? 2 : gffVersion;
 -    out.append(String.format("%s %d\n", GFF_VERSION, version));
 -    String source;
 -    boolean isnonpos;
 +
 +    out.append(String.format("%s %d\n", GFF_VERSION, gffVersion == 0 ? 2 : gffVersion));
 +
 +    if (!includeNonPositionalFeatures
 +            && (visible == null || visible.isEmpty()))
 +    {
 +      return out.toString();
 +    }
 +
 +    String[] types = visible == null ? new String[0] : visible.keySet()
 +            .toArray(
 +            new String[visible.keySet().size()]);
 +
      for (SequenceI seq : sequences)
      {
 -      SequenceFeature[] features = seq.getSequenceFeatures();
 -      if (features != null)
 +      List<SequenceFeature> features = new ArrayList<SequenceFeature>();
 +      if (includeNonPositionalFeatures)
        {
 -        for (SequenceFeature sf : features)
 -        {
 -          isnonpos = sf.begin == 0 && sf.end == 0;
 -          if (!includeNonPositionalFeatures && isnonpos)
 -          {
 -            /*
 -             * ignore non-positional features if not wanted
 -             */
 -            continue;
 -          }
 -          // TODO why the test !isnonpos here?
 -          // what about not visible non-positional features?
 -          if (!isnonpos && outputVisibleOnly
 -                  && !visible.containsKey(sf.type))
 -          {
 -            /*
 -             * ignore not visible features if not wanted
 -             */
 -            continue;
 -          }
 +        features.addAll(seq.getFeatures().getNonPositionalFeatures());
 +      }
 +      if (visible != null && !visible.isEmpty())
 +      {
 +        features.addAll(seq.getFeatures().getPositionalFeatures(types));
 +      }
  
 -          source = sf.featureGroup;
 -          if (source == null)
 -          {
 -            source = sf.getDescription();
 -          }
 +      for (SequenceFeature sf : features)
 +      {
 +        String source = sf.featureGroup;
 +        if (!sf.isNonPositional() && source != null
 +                && !visibleFeatureGroups.contains(source))
 +        {
 +          // group is not visible
 +          continue;
 +        }
  
 -          out.append(seq.getName());
 -          out.append(TAB);
 -          out.append(source);
 -          out.append(TAB);
 -          out.append(sf.type);
 -          out.append(TAB);
 -          out.append(sf.begin);
 -          out.append(TAB);
 -          out.append(sf.end);
 -          out.append(TAB);
 -          out.append(sf.score);
 -          out.append(TAB);
 -
 -          int strand = sf.getStrand();
 -          out.append(strand == 1 ? "+" : (strand == -1 ? "-" : "."));
 -          out.append(TAB);
 -
 -          String phase = sf.getPhase();
 -          out.append(phase == null ? "." : phase);
 -
 -          // miscellaneous key-values (GFF column 9)
 -          String attributes = sf.getAttributes();
 -          if (attributes != null)
 -          {
 -            out.append(TAB).append(attributes);
 -          }
 +        if (source == null)
 +        {
 +          source = sf.getDescription();
 +        }
  
 -          out.append(newline);
 +        out.append(seq.getName());
 +        out.append(TAB);
 +        out.append(source);
 +        out.append(TAB);
 +        out.append(sf.type);
 +        out.append(TAB);
 +        out.append(sf.begin);
 +        out.append(TAB);
 +        out.append(sf.end);
 +        out.append(TAB);
 +        out.append(sf.score);
 +        out.append(TAB);
 +
 +        int strand = sf.getStrand();
 +        out.append(strand == 1 ? "+" : (strand == -1 ? "-" : "."));
 +        out.append(TAB);
 +
 +        String phase = sf.getPhase();
 +        out.append(phase == null ? "." : phase);
 +
 +        // miscellaneous key-values (GFF column 9)
 +        String attributes = sf.getAttributes();
 +        if (attributes != null)
 +        {
 +          out.append(TAB).append(attributes);
          }
 +
 +        out.append(newline);
        }
      }
  
          fromCount = Integer.parseInt(tokens[2]);
        } catch (NumberFormatException nfe)
        {
-         throw new IOException("Invalid number in Align field: "
-                 + nfe.getMessage());
+         throw new IOException(
+                 "Invalid number in Align field: " + nfe.getMessage());
        }
  
        /*
  
        // rename sequences if GFF handler requested this
        // TODO a more elegant way e.g. gffHelper.postProcess(newseqs) ?
 -      SequenceFeature[] sfs = seq.getSequenceFeatures();
 -      if (sfs != null)
 +      List<SequenceFeature> sfs = seq.getFeatures().getPositionalFeatures();
 +      if (!sfs.isEmpty())
        {
 -        String newName = (String) sfs[0].getValue(GffHelperI.RENAME_TOKEN);
 +        String newName = (String) sfs.get(0).getValue(
 +                GffHelperI.RENAME_TOKEN);
          if (newName != null)
          {
            seq.setName(newName);
     * @param newseqs
     * @throws IOException
     */
-   protected void processGffPragma(String line,
-           Map<String, String> gffProps, AlignmentI align,
-           List<SequenceI> newseqs) throws IOException
+   protected void processGffPragma(String line, Map<String, String> gffProps,
+           AlignmentI align, List<SequenceI> newseqs) throws IOException
    {
      line = line.trim();
      if ("###".equals(line))
@@@ -72,10 -72,11 +72,11 @@@ public class IdentifyFil
      // preserves original behaviour prior to version 2.3
    }
  
-   public FileFormatI identify(AlignmentFileReaderI file, boolean closeSource)
-           throws IOException
+   public FileFormatI identify(AlignmentFileReaderI file,
+           boolean closeSource) throws IOException
    {
-     FileParse fp = new FileParse(file.getInFile(), file.getDataSourceType());
+     FileParse fp = new FileParse(file.getInFile(),
+             file.getDataSourceType());
      return identify(fp, closeSource);
    }
  
            // read as a FASTA (probably)
            break;
          }
 +        if (data.indexOf("{\"") > -1)
 +        {
 +          reply = FileFormat.Json;
 +          break;
 +        }
          int lessThan = data.indexOf("<");
          if ((lessThan > -1)) // possible Markup Language data i.e HTML,
                               // RNAML, XML
            }
          }
  
 -        if (data.indexOf("{\"") > -1)
 -        {
 -          reply = FileFormat.Json;
 -          break;
 -        }
          if ((data.length() < 1) || (data.indexOf("#") == 0))
          {
            lineswereskipped = true;
            break;
          }
  
-         if ((data.indexOf("//") == 0)
-                 || ((data.indexOf("!!") > -1) && (data.indexOf("!!") < data
-                         .indexOf("_MULTIPLE_ALIGNMENT "))))
+         if ((data.indexOf("//") == 0) || ((data.indexOf("!!") > -1) && (data
+                 .indexOf("!!") < data.indexOf("_MULTIPLE_ALIGNMENT "))))
          {
            reply = FileFormat.MSF;
  
      }
      if (trimmedLength == 0)
      {
-       System.err
-               .println("File Identification failed! - Empty file was read.");
+       System.err.println(
+               "File Identification failed! - Empty file was read.");
        throw new FileFormatException("EMPTY DATA FILE");
      }
      System.out.println("File format identified as " + reply.toString());
          type = ider.identify(args[i], DataSourceType.FILE);
        } catch (FileFormatException e)
        {
-         System.err.println(String.format(
-                 "Error '%s' identifying file type for %s", args[i],
-                 e.getMessage()));
+         System.err.println(
+                 String.format("Error '%s' identifying file type for %s",
+                         args[i], e.getMessage()));
        }
        System.out.println("Type of " + args[i] + " is " + type);
      }
@@@ -51,7 -51,6 +51,7 @@@ import jalview.schemes.ColourSchemeProp
  import jalview.schemes.JalviewColourScheme;
  import jalview.schemes.ResidueColourScheme;
  import jalview.util.ColorUtils;
 +import jalview.util.Format;
  import jalview.viewmodel.seqfeatures.FeaturesDisplayed;
  
  import java.awt.Color;
@@@ -219,9 -218,8 +219,8 @@@ public class JSONFile extends AlignFil
          // are not exported
          if (globalColourScheme
                  .equalsIgnoreCase(JalviewColourScheme.RNAHelices.toString())
-                 || globalColourScheme
-                         .equalsIgnoreCase(JalviewColourScheme.TCoffee
-                                 .toString()))
+                 || globalColourScheme.equalsIgnoreCase(
+                         JalviewColourScheme.TCoffee.toString()))
          {
            jsonAlignmentPojo.setGlobalColorScheme(ResidueColourScheme.NONE);
          }
  
        if (exportSettings.isExportFeatures())
        {
 -        jsonAlignmentPojo
 -                .setSeqFeatures(sequenceFeatureToJsonPojo(sqs, fr));
 +        jsonAlignmentPojo.setSeqFeatures(sequenceFeatureToJsonPojo(sqs));
        }
  
        if (exportSettings.isExportGroups() && seqGroups != null
      return hiddenSections;
    }
  
 -  public List<SequenceFeaturesPojo> sequenceFeatureToJsonPojo(
 -          SequenceI[] sqs, FeatureRenderer fr)
 +  protected List<SequenceFeaturesPojo> sequenceFeatureToJsonPojo(
 +          SequenceI[] sqs)
    {
      displayedFeatures = (fr == null) ? null : fr.getFeaturesDisplayed();
      List<SequenceFeaturesPojo> sequenceFeaturesPojo = new ArrayList<>();
  
      FeatureColourFinder finder = new FeatureColourFinder(fr);
  
 +    String[] visibleFeatureTypes = displayedFeatures == null ? null
 +            : displayedFeatures.getVisibleFeatures().toArray(
 +                    new String[displayedFeatures.getVisibleFeatureCount()]);
 +
      for (SequenceI seq : sqs)
      {
 -      SequenceI dataSetSequence = seq.getDatasetSequence();
 -      SequenceFeature[] seqFeatures = (dataSetSequence == null) ? null
 -              : seq.getDatasetSequence().getSequenceFeatures();
 -
 -      seqFeatures = (seqFeatures == null) ? seq.getSequenceFeatures()
 -              : seqFeatures;
 -      if (seqFeatures == null)
 -      {
 -        continue;
 -      }
 -
 +      /*
 +       * get all features currently visible (and any non-positional features)
 +       */
 +      List<SequenceFeature> seqFeatures = seq.getFeatures().getAllFeatures(
 +              visibleFeatureTypes);
        for (SequenceFeature sf : seqFeatures)
        {
 -        if (displayedFeatures != null
 -                && displayedFeatures.isVisible(sf.getType()))
 -        {
 -          SequenceFeaturesPojo jsonFeature = new SequenceFeaturesPojo(
 -                  String.valueOf(seq.hashCode()));
 -
 -          String featureColour = (fr == null) ? null
 -                  : jalview.util.Format.getHexString(
 -                          finder.findFeatureColour(Color.white, seq,
 -                                  seq.findIndex(sf.getBegin())));
 -          jsonFeature.setXstart(seq.findIndex(sf.getBegin()) - 1);
 -          jsonFeature.setXend(seq.findIndex(sf.getEnd()));
 -          jsonFeature.setType(sf.getType());
 -          jsonFeature.setDescription(sf.getDescription());
 -          jsonFeature.setLinks(sf.links);
 -          jsonFeature.setOtherDetails(sf.otherDetails);
 -          jsonFeature.setScore(sf.getScore());
 -          jsonFeature.setFillColor(featureColour);
 -          jsonFeature.setFeatureGroup(sf.getFeatureGroup());
 -          sequenceFeaturesPojo.add(jsonFeature);
 -        }
 +        SequenceFeaturesPojo jsonFeature = new SequenceFeaturesPojo(
 +                String.valueOf(seq.hashCode()));
 +
 +        String featureColour = (fr == null) ? null : Format
 +                .getHexString(finder.findFeatureColour(Color.white, seq,
 +                        seq.findIndex(sf.getBegin())));
 +        int xStart = sf.getBegin() == 0 ? 0
 +                : seq.findIndex(sf.getBegin()) - 1;
 +        int xEnd = sf.getEnd() == 0 ? 0 : seq.findIndex(sf.getEnd());
 +        jsonFeature.setXstart(xStart);
 +        jsonFeature.setXend(xEnd);
 +        jsonFeature.setType(sf.getType());
 +        jsonFeature.setDescription(sf.getDescription());
 +        jsonFeature.setLinks(sf.links);
 +        jsonFeature.setOtherDetails(sf.otherDetails);
 +        jsonFeature.setScore(sf.getScore());
 +        jsonFeature.setFillColor(featureColour);
 +        jsonFeature.setFeatureGroup(sf.getFeatureGroup());
 +        sequenceFeaturesPojo.add(jsonFeature);
        }
      }
      return sequenceFeaturesPojo;
            annotationPojo.setDisplayCharacter(displayChar);
            if (annotation.colour != null)
            {
-             annotationPojo.setColour(jalview.util.Format
-                     .getHexString(annotation.colour));
+             annotationPojo.setColour(
+                     jalview.util.Format.getHexString(annotation.colour));
            }
            alignAnnotPojo.getAnnotations().add(annotationPojo);
          }
        {
          globalColourScheme = (String) jvSettingsJsonObj
                  .get("globalColorScheme");
-         Boolean showFeatures = Boolean.valueOf(jvSettingsJsonObj.get(
-                 "showSeqFeatures").toString());
+         Boolean showFeatures = Boolean.valueOf(
+                 jvSettingsJsonObj.get("showSeqFeatures").toString());
          setShowSeqFeatures(showFeatures);
          parseHiddenSeqRefsAsList(jvSettingsJsonObj);
          parseHiddenCols(jvSettingsJsonObj);
  
        hiddenSequences = new ArrayList<>();
        seqMap = new Hashtable<>();
-       for (Iterator<JSONObject> sequenceIter = seqJsonArray.iterator(); sequenceIter
-               .hasNext();)
+       for (Iterator<JSONObject> sequenceIter = seqJsonArray
+               .iterator(); sequenceIter.hasNext();)
        {
          JSONObject sequence = sequenceIter.next();
          String sequcenceString = sequence.get("seq").toString();
  
        parseFeatures(jsonSeqArray);
  
-       for (Iterator<JSONObject> seqGrpIter = seqGrpJsonArray.iterator(); seqGrpIter
-               .hasNext();)
+       for (Iterator<JSONObject> seqGrpIter = seqGrpJsonArray
+               .iterator(); seqGrpIter.hasNext();)
        {
          JSONObject seqGrpObj = seqGrpIter.next();
          String grpName = seqGrpObj.get("groupName").toString();
          String colourScheme = seqGrpObj.get("colourScheme").toString();
          String description = (seqGrpObj.get("description") == null) ? null
                  : seqGrpObj.get("description").toString();
-         boolean displayBoxes = Boolean.valueOf(seqGrpObj
-                 .get("displayBoxes").toString());
-         boolean displayText = Boolean.valueOf(seqGrpObj.get("displayText")
-                 .toString());
-         boolean colourText = Boolean.valueOf(seqGrpObj.get("colourText")
-                 .toString());
-         boolean showNonconserved = Boolean.valueOf(seqGrpObj.get(
-                 "showNonconserved").toString());
+         boolean displayBoxes = Boolean
+                 .valueOf(seqGrpObj.get("displayBoxes").toString());
+         boolean displayText = Boolean
+                 .valueOf(seqGrpObj.get("displayText").toString());
+         boolean colourText = Boolean
+                 .valueOf(seqGrpObj.get("colourText").toString());
+         boolean showNonconserved = Boolean
+                 .valueOf(seqGrpObj.get("showNonconserved").toString());
          int startRes = Integer
                  .valueOf(seqGrpObj.get("startRes").toString());
          int endRes = Integer.valueOf(seqGrpObj.get("endRes").toString());
          }
          SequenceGroup seqGrp = new SequenceGroup(grpSeqs, grpName, null,
                  displayBoxes, displayText, colourText, startRes, endRes);
-         seqGrp.setColourScheme(ColourSchemeMapper.getJalviewColourScheme(
-                 colourScheme, seqGrp));
+         seqGrp.setColourScheme(ColourSchemeMapper
+                 .getJalviewColourScheme(colourScheme, seqGrp));
          seqGrp.setShowNonconserved(showNonconserved);
          seqGrp.setDescription(description);
          this.seqGroups.add(seqGrp);
  
        }
  
-       for (Iterator<JSONObject> alAnnotIter = alAnnotJsonArray.iterator(); alAnnotIter
-               .hasNext();)
+       for (Iterator<JSONObject> alAnnotIter = alAnnotJsonArray
+               .iterator(); alAnnotIter.hasNext();)
        {
          JSONObject alAnnot = alAnnotIter.next();
          JSONArray annotJsonArray = (JSONArray) alAnnot.get("annotations");
          Annotation[] annotations = new Annotation[annotJsonArray.size()];
          int count = 0;
-         for (Iterator<JSONObject> annotIter = annotJsonArray.iterator(); annotIter
-                 .hasNext();)
+         for (Iterator<JSONObject> annotIter = annotJsonArray
+                 .iterator(); annotIter.hasNext();)
          {
            JSONObject annot = annotIter.next();
            if (annot == null)
            }
            else
            {
-             float val = annot.get("value") == null ? null : Float
-                     .valueOf(annot.get("value").toString());
-             String desc = annot.get("description") == null ? null : annot
-                     .get("description").toString();
+             float val = annot.get("value") == null ? null
+                     : Float.valueOf(annot.get("value").toString());
+             String desc = annot.get("description") == null ? null
+                     : annot.get("description").toString();
              char ss = annot.get("secondaryStructure") == null
                      || annot.get("secondaryStructure").toString()
-                             .equalsIgnoreCase("u0000") ? ' ' : annot
-                     .get("secondaryStructure").toString().charAt(0);
+                             .equalsIgnoreCase("u0000") ? ' '
+                                     : annot.get("secondaryStructure")
+                                             .toString().charAt(0);
              String displayChar = annot.get("displayCharacter") == null ? ""
                      : annot.get("displayCharacter").toString();
  
              annotations[count] = new Annotation(displayChar, desc, ss, val);
              if (annot.get("colour") != null)
              {
-               Color color = ColorUtils.parseColourString(annot.get(
-                       "colour").toString());
+               Color color = ColorUtils
+                       .parseColourString(annot.get("colour").toString());
                annotations[count].colour = color;
              }
            }
            ++count;
          }
  
-         AlignmentAnnotation alignAnnot = new AlignmentAnnotation(alAnnot
-                 .get("label").toString(), alAnnot.get("description")
-                 .toString(), annotations);
-         alignAnnot.graph = (alAnnot.get("graphType") == null) ? 0 : Integer
-                 .valueOf(alAnnot.get("graphType").toString());
+         AlignmentAnnotation alignAnnot = new AlignmentAnnotation(
+                 alAnnot.get("label").toString(),
+                 alAnnot.get("description").toString(), annotations);
+         alignAnnot.graph = (alAnnot.get("graphType") == null) ? 0
+                 : Integer.valueOf(alAnnot.get("graphType").toString());
  
          JSONObject diplaySettings = (JSONObject) alAnnot
                  .get("annotationSettings");
          if (diplaySettings != null)
          {
  
-           alignAnnot.scaleColLabel = (diplaySettings.get("scaleColLabel") == null) ? false
-                   : Boolean.valueOf(diplaySettings.get("scaleColLabel")
-                           .toString());
+           alignAnnot.scaleColLabel = (diplaySettings
+                   .get("scaleColLabel") == null) ? false
+                           : Boolean.valueOf(diplaySettings
+                                   .get("scaleColLabel").toString());
            alignAnnot.showAllColLabels = (diplaySettings
-                   .get("showAllColLabels") == null) ? true : Boolean
-                   .valueOf(diplaySettings.get("showAllColLabels")
-                           .toString());
+                   .get("showAllColLabels") == null) ? true
+                           : Boolean.valueOf(diplaySettings
+                                   .get("showAllColLabels").toString());
            alignAnnot.centreColLabels = (diplaySettings
                    .get("centreColLabels") == null) ? true
-                   : Boolean.valueOf(diplaySettings.get("centreColLabels")
-                           .toString());
-           alignAnnot.belowAlignment = (diplaySettings.get("belowAlignment") == null) ? false
-                   : Boolean.valueOf(diplaySettings.get("belowAlignment")
-                           .toString());
-           alignAnnot.visible = (diplaySettings.get("visible") == null) ? true
-                   : Boolean.valueOf(diplaySettings.get("visible")
-                           .toString());
-           alignAnnot.hasIcons = (diplaySettings.get("hasIcon") == null) ? true
-                   : Boolean.valueOf(diplaySettings.get("hasIcon")
-                           .toString());
+                           : Boolean.valueOf(diplaySettings
+                                   .get("centreColLabels").toString());
+           alignAnnot.belowAlignment = (diplaySettings
+                   .get("belowAlignment") == null) ? false
+                           : Boolean.valueOf(diplaySettings
+                                   .get("belowAlignment").toString());
+           alignAnnot.visible = (diplaySettings.get("visible") == null)
+                   ? true
+                   : Boolean.valueOf(
+                           diplaySettings.get("visible").toString());
+           alignAnnot.hasIcons = (diplaySettings.get("hasIcon") == null)
+                   ? true
+                   : Boolean.valueOf(
+                           diplaySettings.get("hasIcon").toString());
  
          }
          if (alAnnot.get("score") != null)
                    .valueOf(alAnnot.get("score").toString());
          }
  
-         String calcId = (alAnnot.get("calcId") == null) ? "" : alAnnot.get(
-                 "calcId").toString();
+         String calcId = (alAnnot.get("calcId") == null) ? ""
+                 : alAnnot.get("calcId").toString();
          alignAnnot.setCalcId(calcId);
-         String seqHash = (alAnnot.get("sequenceRef") != null) ? alAnnot
-                 .get("sequenceRef").toString() : null;
+         String seqHash = (alAnnot.get("sequenceRef") != null)
+                 ? alAnnot.get("sequenceRef").toString()
+                 : null;
  
          Sequence sequence = (seqHash != null) ? seqMap.get(seqHash) : null;
          if (sequence != null)
      if (jsonSeqFeatures != null)
      {
        displayedFeatures = new FeaturesDisplayed();
-       for (Iterator<JSONObject> seqFeatureItr = jsonSeqFeatures.iterator(); seqFeatureItr
-               .hasNext();)
+       for (Iterator<JSONObject> seqFeatureItr = jsonSeqFeatures
+               .iterator(); seqFeatureItr.hasNext();)
        {
          JSONObject jsonFeature = seqFeatureItr.next();
          Long begin = (Long) jsonFeature.get("xStart");
          Long end = (Long) jsonFeature.get("xEnd");
          String type = (String) jsonFeature.get("type");
          String featureGrp = (String) jsonFeature.get("featureGroup");
 -        String descripiton = (String) jsonFeature.get("description");
 +        String description = (String) jsonFeature.get("description");
          String seqRef = (String) jsonFeature.get("sequenceRef");
          Float score = Float.valueOf(jsonFeature.get("score").toString());
  
          Sequence seq = seqMap.get(seqRef);
 -        SequenceFeature sequenceFeature = new SequenceFeature();
 +
 +        /*
 +         * begin/end of 0 is for a non-positional feature
 +         */
 +        int featureBegin = begin.intValue() == 0 ? 0 : seq
 +                .findPosition(begin.intValue());
 +        int featureEnd = end.intValue() == 0 ? 0 : seq.findPosition(end
 +                .intValue()) - 1;
 +
 +        SequenceFeature sequenceFeature = new SequenceFeature(type,
 +                description, featureBegin, featureEnd, score, featureGrp);
 +
          JSONArray linksJsonArray = (JSONArray) jsonFeature.get("links");
          if (linksJsonArray != null && linksJsonArray.size() > 0)
          {
              sequenceFeature.addLink(link);
            }
          }
 -        sequenceFeature.setFeatureGroup(featureGrp);
 -        sequenceFeature.setScore(score);
 -        sequenceFeature.setDescription(descripiton);
 -        sequenceFeature.setType(type);
 -        sequenceFeature.setBegin(seq.findPosition(begin.intValue()));
 -        sequenceFeature.setEnd(seq.findPosition(end.intValue()) - 1);
 +
          seq.addSequenceFeature(sequenceFeature);
          displayedFeatures.setVisible(type);
        }
          }
        }
      }
-     globalColourScheme = ColourSchemeProperty.getColourName(viewport
-             .getGlobalColourScheme());
+     globalColourScheme = ColourSchemeProperty
+             .getColourName(viewport.getGlobalColourScheme());
      setDisplayedFeatures(viewport.getFeaturesDisplayed());
      showSeqFeatures = viewport.isShowSequenceFeatures();
  
@@@ -59,22 -59,19 +59,19 @@@ public class JnetAnnotationMake
      // in the future we could search for the query
      // sequence in the alignment before calling this function.
      SequenceI seqRef = al.getSequenceAt(firstSeq);
 -    int width = preds[0].getSequence().length;
 +    int width = preds[0].getLength();
      int[] gapmap = al.getSequenceAt(firstSeq).gapMap();
      if ((delMap != null && delMap.length > width)
              || (delMap == null && gapmap.length != width))
      {
-       throw (new Exception(
-               MessageManager
-                       .formatMessage(
-                               "exception.number_of_residues_in_query_sequence_differ_from_prediction",
-                               new String[] {
-                                   (delMap == null ? "" : MessageManager
-                                           .getString("label.mapped")),
-                                   al.getSequenceAt(firstSeq).getName(),
-                                   al.getSequenceAt(firstSeq)
-                                           .getSequenceAsString(),
-                                   Integer.valueOf(width).toString() })));
+       throw (new Exception(MessageManager.formatMessage(
+               "exception.number_of_residues_in_query_sequence_differ_from_prediction",
+               new String[]
+               { (delMap == null ? ""
+                       : MessageManager.getString("label.mapped")),
+                   al.getSequenceAt(firstSeq).getName(),
+                   al.getSequenceAt(firstSeq).getSequenceAsString(),
+                   Integer.valueOf(width).toString() })));
      }
  
      AlignmentAnnotation annot;
@@@ -98,7 -95,8 +95,8 @@@
        {
          if (id.startsWith("JNETSOL"))
          {
-           float amnt = (id.endsWith("25") ? 3f : id.endsWith("5") ? 6f : 9f);
+           float amnt = (id.endsWith("25") ? 3f
+                   : id.endsWith("5") ? 6f : 9f);
            for (int spos = 0; spos < width; spos++)
            {
              int sposw = (delMap == null) ? gapmap[spos]
      if (!firstsol)
      {
        // add the solvent accessibility
-       annot = new AlignmentAnnotation(
-               "Jnet Burial",
+       annot = new AlignmentAnnotation("Jnet Burial",
                "<html>Prediction of Solvent Accessibility<br/>levels are<ul><li>0 - Exposed</li><li>3 - 25% or more S.A. accessible</li><li>6 - 5% or more S.A. accessible</li><li>9 - Buried (<5% exposed)</li></ul>",
                sol, 0f, 9f, AlignmentAnnotation.BAR_GRAPH);
  
          seqRef.addAlignmentAnnotation(annot);
        }
        al.addAnnotation(annot);
-       al.setAnnotationIndex(annot, al.getAlignmentAnnotation().length
-               - existingAnnotations - 1);
+       al.setAnnotationIndex(annot,
+               al.getAlignmentAnnotation().length - existingAnnotations - 1);
      }
      // Hashtable scores = prediction.getScores();
  
@@@ -278,10 -278,10 +278,10 @@@ public class MSFfile extends AlignFil
        i++;
      }
  
-     Format maxLenpad = new Format("%" + (new String("" + max)).length()
-             + "d");
-     Format maxChkpad = new Format("%" + (new String("1" + max)).length()
-             + "d");
+     Format maxLenpad = new Format(
+             "%" + (new String("" + max)).length() + "d");
+     Format maxChkpad = new Format(
+             "%" + (new String("1" + max)).length() + "d");
      i = 0;
  
      int bigChecksum = 0;
      }
  
      long maxNB = 0;
 -    out.append("   MSF: " + s[0].getSequence().length + "   Type: "
 +    out.append("   MSF: " + s[0].getLength() + "   Type: "
              + (is_NA ? "N" : "P") + "    Check:  " + (bigChecksum % 10000)
              + "   ..");
      out.append(newline);
  
        nameBlock[i] = new String("  Name: " + printId(s[i], jvSuffix) + " ");
  
 -      idBlock[i] = new String("Len: "
 -              + maxLenpad.form(s[i].getSequence().length) + "  Check: "
 -              + maxChkpad.form(checksums[i]) + "  Weight: 1.00" + newline);
 +      idBlock[i] = new String("Len: " + maxLenpad.form(s[i].getLength())
 +              + "  Check: " + maxChkpad.form(checksums[i])
 +              + "  Weight: 1.00" + newline);
  
        if (s[i].getName().length() > maxid)
        {
            int start = (i * 50) + (k * 10);
            int end = start + 10;
  
 -          if ((end < s[j].getSequence().length)
 -                  && (start < s[j].getSequence().length))
 +          int length = s[j].getLength();
 +          if ((end < length)
 +                  && (start < length))
            {
              out.append(s[j].getSequence(start, end));
  
            }
            else
            {
 -            if (start < s[j].getSequence().length)
 +            if (start < length)
              {
                out.append(s[j].getSequenceAsString().substring(start));
                out.append(newline);
@@@ -117,8 -117,8 +117,8 @@@ public class PfamFile extends AlignFil
  
      if (noSeqs < 1)
      {
-       throw new IOException(
-               MessageManager.getString("exception.pfam_no_sequences_found"));
+       throw new IOException(MessageManager
+               .getString("exception.pfam_no_sequences_found"));
      }
  
      for (i = 0; i < headers.size(); i++)
          }
  
          Sequence newSeq = parseId(headers.get(i).toString());
-         newSeq.setSequence(seqhash.get(headers.get(i).toString())
-                 .toString());
+         newSeq.setSequence(
+                 seqhash.get(headers.get(i).toString()).toString());
          seqs.addElement(newSeq);
        }
        else
      {
        String tmp = printId(s[i], jvsuffix);
  
 -      if (s[i].getSequence().length > max)
 -      {
 -        max = s[i].getSequence().length;
 -      }
 +      max = Math.max(max, s[i].getLength());
  
        if (tmp.length() > maxid)
        {
@@@ -27,9 -27,9 +27,9 @@@ import java.io.IOException
  
  /**
   * <p>
-  * Parser and exporter for PHYLIP file format, as defined <a
-  * href="http://evolution.genetics.washington.edu/phylip/doc/main.html">in the
-  * documentation</a>. The parser imports PHYLIP files in both sequential and
+  * Parser and exporter for PHYLIP file format, as defined
+  * <a href="http://evolution.genetics.washington.edu/phylip/doc/main.html">in
+  * the documentation</a>. The parser imports PHYLIP files in both sequential and
   * interleaved format, and (currently) exports in interleaved format (using 60
   * characters per matrix for the sequence).
   * <p>
@@@ -38,7 -38,8 +38,8 @@@
   * The following assumptions have been made for input
   * <ul>
   * <li>Sequences are expressed as letters, not real numbers with decimal points
-  * separated by blanks (which is a valid option according to the specification)</li>
+  * separated by blanks (which is a valid option according to the
+  * specification)</li>
   * </ul>
   *
   * The following assumptions have been made for output
@@@ -112,8 -113,8 +113,8 @@@ public class PhylipFile extends AlignFi
                  "First line must contain the number of specifies and number of characters");
        }
  
-       int numberSpecies = Integer.parseInt(lineElements[0]), numberCharacters = Integer
-               .parseInt(lineElements[1]);
+       int numberSpecies = Integer.parseInt(lineElements[0]),
+               numberCharacters = Integer.parseInt(lineElements[1]);
  
        if (numberSpecies <= 0)
        {
          }
          else
          {
-           sequenceElements[i] = parseId(validateName(potentialName
-                   .substring(0, tabIndex)));
+           sequenceElements[i] = parseId(
+                   validateName(potentialName.substring(0, tabIndex)));
            sequences[i] = new StringBuffer(
                    removeWhitespace(line.substring(tabIndex)));
          }
      {
        if (name.indexOf(c) > -1)
        {
-         throw new IOException("Species name contains illegal character "
-                 + c);
+         throw new IOException(
+                 "Species name contains illegal character " + c);
        }
      }
      return name;
      sb.append(" ");
      // if there are no sequences, then define the number of characters as 0
      sb.append(
 -            (sqs.length > 0) ? Integer.toString(sqs[0].getSequence().length)
 +(sqs.length > 0) ? Integer.toString(sqs[0].getLength())
                      : "0")
              .append(newline);
  
        // sequential has the entire sequence following the name
        if (sequential)
        {
 -        sb.append(s.getSequence());
 +        sb.append(s.getSequenceAsString());
        }
        else
        {
          // Jalview ensures all sequences are of same length so no need
          // to keep track of min/max length
 -        sequenceLength = s.getSequence().length;
 +        sequenceLength = s.getLength();
          // interleaved breaks the sequence into chunks for
          // interleavedColumns characters
          sb.append(s.getSequence(0,
          int start = i * numInterleavedColumns;
          for (SequenceI s : sqs)
          {
-           sb.append(
-                   s.getSequence(start, Math.min(start
-                           + numInterleavedColumns, sequenceLength)))
+           sb.append(s.getSequence(start,
+                   Math.min(start + numInterleavedColumns, sequenceLength)))
                    .append(newline);
          }
        }
@@@ -92,7 -92,7 +92,7 @@@ public class PileUpfile extends MSFfil
        i++;
      }
  
 -    out.append("   MSF: " + s[0].getSequence().length
 +    out.append("   MSF: " + s[0].getLength()
              + "   Type: P    Check:  " + bigChecksum % 10000 + "   ..");
      out.append(newline);
      out.append(newline);
      {
        String seq = s[i].getSequenceAsString();
        out.append(" Name: " + printId(s[i], jvsuffix) + " oo  Len:  "
-               + seq.length()
-               + "  Check:  " + checksums[i] + "  Weight:  1.00");
+               + seq.length() + "  Check:  " + checksums[i]
+               + "  Weight:  1.00");
        out.append(newline);
  
        if (seq.length() > max)
            int start = (i * 50) + (k * 10);
            int end = start + 10;
  
 -          if ((end < s[j].getSequence().length)
 -                  && (start < s[j].getSequence().length))
 +          int length = s[j].getLength();
 +          if ((end < length) && (start < length))
            {
              out.append(s[j].getSequence(start, end));
  
            }
            else
            {
 -            if (start < s[j].getSequence().length)
 +            if (start < length)
              {
                out.append(s[j].getSequenceAsString().substring(start));
                out.append(newline);
@@@ -57,8 -57,7 +57,8 @@@ public class SequenceAnnotationRepor
    final String linkImageURL;
  
    /*
 -   * Comparator to order DBRefEntry by Source + accession id (case-insensitive)
 +   * Comparator to order DBRefEntry by Source + accession id (case-insensitive),
 +   * with 'Primary' sources placed before others
     */
    private static Comparator<DBRefEntry> comparator = new Comparator<DBRefEntry>()
    {
        {
          return 1;
        }
-       int comp = s1 == null ? -1 : (s2 == null ? 1 : s1
-               .compareToIgnoreCase(s2));
+       int comp = s1 == null ? -1
+               : (s2 == null ? 1 : s1.compareToIgnoreCase(s2));
        if (comp == 0)
        {
          String a1 = ref1.getAccessionId();
          String a2 = ref2.getAccessionId();
-         comp = a1 == null ? -1 : (a2 == null ? 1 : a1
-                 .compareToIgnoreCase(a2));
+         comp = a1 == null ? -1
+                 : (a2 == null ? 1 : a1.compareToIgnoreCase(a2));
        }
        return comp;
      }
            sb.append("<br>");
          }
          sb.append(feature.getType()).append(" ").append(feature.getBegin())
-                 .append(":")
-                 .append(feature.getEnd());
+                 .append(":").append(feature.getEnd());
        }
      }
      else
          // check score should be shown
          if (!Float.isNaN(feature.getScore()))
          {
-           float[][] rng = (minmax == null) ? null : minmax.get(feature
-                   .getType());
+           float[][] rng = (minmax == null) ? null
+                   : minmax.get(feature.getType());
            if (rng != null && rng[0] != null && rng[0][0] != rng[0][1])
            {
              sb.append(" Score=").append(String.valueOf(feature.getScore()));
            {
              for (List<String> urllink : createLinksFrom(null, urlstring))
              {
-               sb.append("<br/> <a href=\""
-                       + urllink.get(3)
-                       + "\" target=\""
-                       + urllink.get(0)
-                       + "\">"
+               sb.append("<br/> <a href=\"" + urllink.get(3) + "\" target=\""
+                       + urllink.get(0) + "\">"
                        + (urllink.get(0).toLowerCase()
-                               .equals(urllink.get(1).toLowerCase()) ? urllink
-                               .get(0) : (urllink.get(0) + ":" + urllink
-                               .get(1)))
+                               .equals(urllink.get(1).toLowerCase())
+                                       ? urllink.get(0)
+                                       : (urllink.get(0) + ":"
+                                               + urllink.get(1)))
                        + "</a></br>");
              }
            } catch (Exception x)
            {
-             System.err.println("problem when creating links from "
-                     + urlstring);
+             System.err.println(
+                     "problem when creating links from " + urlstring);
              x.printStackTrace();
            }
          }
      {
        ds = ds.getDatasetSequence();
      }
 +    
 +    if (showDbRefs)
 +    {
 +      maxWidth = Math.max(maxWidth, appendDbRefs(sb, ds, summary));
 +    }
 +
 +    /*
 +     * add non-positional features if wanted
 +     */
 +    if (showNpFeats)
 +    {
 +      for (SequenceFeature sf : sequence.getFeatures()
 +              .getNonPositionalFeatures())
 +      {
 +        int sz = -sb.length();
 +        appendFeature(sb, 0, minmax, sf);
 +        sz += sb.length();
 +        maxWidth = Math.max(maxWidth, sz);
 +      }
 +    }
 +    sb.append("</i>");
 +    return maxWidth;
 +  }
 +
 +  /**
 +   * A helper method that appends any DBRefs, returning the maximum line length
 +   * added
 +   * 
 +   * @param sb
 +   * @param ds
 +   * @param summary
 +   * @return
 +   */
 +  protected int appendDbRefs(final StringBuilder sb, SequenceI ds,
 +          boolean summary)
 +  {
      DBRefEntry[] dbrefs = ds.getDBRefs();
 -    if (showDbRefs && dbrefs != null)
 +    if (dbrefs == null)
 +    {
 +      return 0;
 +    }
 +
 +    // note this sorts the refs held on the sequence!
 +    Arrays.sort(dbrefs, comparator);
 +    boolean ellipsis = false;
 +    String source = null;
 +    String lastSource = null;
 +    int countForSource = 0;
 +    int sourceCount = 0;
 +    boolean moreSources = false;
 +    int maxLineLength = 0;
 +    int lineLength = 0;
 +
 +    for (DBRefEntry ref : dbrefs)
      {
 -      // note this sorts the refs held on the sequence!
 -      Arrays.sort(dbrefs, comparator);
 -      boolean ellipsis = false;
 -      String source = null;
 -      String lastSource = null;
 -      int countForSource = 0;
 -      int sourceCount = 0;
 -      boolean moreSources = false;
 -      int lineLength = 0;
 -
 -      for (DBRefEntry ref : dbrefs)
 +      source = ref.getSource();
 +      if (source == null)
        {
 -        source = ref.getSource();
 -        if (source == null)
 -        {
 -          // shouldn't happen
 -          continue;
 -        }
 -        boolean sourceChanged = !source.equals(lastSource);
 -        if (sourceChanged)
 -        {
 -          lineLength = 0;
 -          countForSource = 0;
 -          sourceCount++;
 -        }
 -        if (sourceCount > MAX_SOURCES && summary)
 -        {
 -          ellipsis = true;
 -          moreSources = true;
 -          break;
 -        }
 -        lastSource = source;
 -        countForSource++;
 -        if (countForSource == 1 || !summary)
 -        {
 -          sb.append("<br>");
 -        }
 -        if (countForSource <= MAX_REFS_PER_SOURCE || !summary)
 -        {
 -          String accessionId = ref.getAccessionId();
 -          lineLength += accessionId.length() + 1;
 -          if (countForSource > 1 && summary)
 -          {
 -            sb.append(", ").append(accessionId);
 -            lineLength++;
 -          }
 -          else
 -          {
 -            sb.append(source).append(" ").append(accessionId);
 -            lineLength += source.length();
 -          }
 -          maxWidth = Math.max(maxWidth, lineLength);
 -        }
 -        if (countForSource == MAX_REFS_PER_SOURCE && summary)
 -        {
 -          sb.append(COMMA).append(ELLIPSIS);
 -          ellipsis = true;
 -        }
 +        // shouldn't happen
 +        continue;
        }
 -      if (moreSources)
 +      boolean sourceChanged = !source.equals(lastSource);
 +      if (sourceChanged)
        {
 -        sb.append("<br>").append(ELLIPSIS).append(COMMA).append(source)
 -                .append(COMMA).append(ELLIPSIS);
 +        lineLength = 0;
 +        countForSource = 0;
 +        sourceCount++;
        }
 -      if (ellipsis)
 +      if (sourceCount > MAX_SOURCES && summary)
        {
 -        sb.append("<br>(");
 -        sb.append(MessageManager.getString("label.output_seq_details"));
 -        sb.append(")");
 +        ellipsis = true;
 +        moreSources = true;
 +        break;
        }
 -    }
 -
 -    /*
 -     * add non-positional features if wanted
 -     */
 -    SequenceFeature[] features = sequence.getSequenceFeatures();
 -    if (showNpFeats && features != null)
 -    {
 -      for (int i = 0; i < features.length; i++)
 +      lastSource = source;
 +      countForSource++;
 +      if (countForSource == 1 || !summary)
 +      {
 +        sb.append("<br>");
 +      }
 +      if (countForSource <= MAX_REFS_PER_SOURCE || !summary)
        {
 -        if (features[i].begin == 0 && features[i].end == 0)
 +        String accessionId = ref.getAccessionId();
 +        lineLength += accessionId.length() + 1;
 +        if (countForSource > 1 && summary)
          {
 -          int sz = -sb.length();
 -          appendFeature(sb, 0, minmax, features[i]);
 -          sz += sb.length();
 -          maxWidth = Math.max(maxWidth, sz);
 +          sb.append(", ").append(accessionId);
 +          lineLength++;
          }
 +        else
 +        {
 +          sb.append(source).append(" ").append(accessionId);
 +          lineLength += source.length();
 +        }
 +        maxLineLength = Math.max(maxLineLength, lineLength);
 +      }
 +      if (countForSource == MAX_REFS_PER_SOURCE && summary)
 +      {
 +        sb.append(COMMA).append(ELLIPSIS);
 +        ellipsis = true;
        }
      }
 -    sb.append("</i>");
 -    return maxWidth;
 +    if (moreSources)
 +    {
 +      sb.append("<br>").append(source)
 +              .append(COMMA).append(ELLIPSIS);
 +    }
 +    if (ellipsis)
 +    {
 +      sb.append("<br>(");
 +      sb.append(MessageManager.getString("label.output_seq_details"));
 +      sb.append(")");
 +    }
 +
 +    return maxLineLength;
    }
  
    public void createTooltipAnnotationReport(final StringBuilder tip,
            SequenceI sequence, boolean showDbRefs, boolean showNpFeats,
            Map<String, float[][]> minmax)
    {
-     int maxWidth = createSequenceAnnotationReport(tip, sequence,
-             showDbRefs, showNpFeats, minmax, true);
+     int maxWidth = createSequenceAnnotationReport(tip, sequence, showDbRefs,
+             showNpFeats, minmax, true);
  
      if (maxWidth > 60)
      {
@@@ -74,8 -74,6 +74,8 @@@ import fr.orsay.lri.varna.models.rna.RN
   */
  public class StockholmFile extends AlignFile
  {
 +  private static final String ANNOTATION = "annotation";
 +
    private static final Regex OPEN_PAREN = new Regex("(<|\\[)", "(");
  
    private static final Regex CLOSE_PAREN = new Regex("(>|\\])", ")");
  
        for (int k = 0; k < rna.length(); k++)
        {
-         ann[k] = new Annotation(annot[k], "", Rna.getRNASecStrucState(
-                 annot[k]).charAt(0), 0f);
+         ann[k] = new Annotation(annot[k], "",
+                 Rna.getRNASecStrucState(annot[k]).charAt(0), 0f);
  
        }
        AlignmentAnnotation align = new AlignmentAnnotation("Sec. str.",
      r = new Regex("# STOCKHOLM ([\\d\\.]+)");
      if (!r.search(nextLine()))
      {
-       throw new IOException(
-               MessageManager
-                       .getString("exception.stockholm_invalid_format"));
+       throw new IOException(MessageManager
+               .getString("exception.stockholm_invalid_format"));
      }
      else
      {
                while (j.hasMoreElements())
                {
                  String desc = j.nextElement().toString();
 -                if ("annotations".equals(desc) && annotsAdded)
 +                if (ANNOTATION.equals(desc) && annotsAdded)
                  {
                    // don't add features if we already added an annotation row
                    continue;
                      int new_pos = posmap[k]; // look up nearest seqeunce
                      // position to this column
                      SequenceFeature feat = new SequenceFeature(type, desc,
 -                            new_pos, new_pos, 0f, null);
 +                            new_pos, new_pos, null);
  
                      seqO.addSequenceFeature(feat);
                    }
          {
            // logger.error("Could not parse sequence line: " + line);
            throw new IOException(MessageManager.formatMessage(
-                   "exception.couldnt_parse_sequence_line",
-                   new String[] { line }));
+                   "exception.couldnt_parse_sequence_line", new String[]
+                   { line }));
          }
          String ns = seqs.get(x.stringMatched(1));
          if (ns == null)
                content = new Hashtable();
                features.put(this.id2type(type), content);
              }
 -            String ns = (String) content.get("annotation");
 +            String ns = (String) content.get(ANNOTATION);
  
              if (ns == null)
              {
              }
              // finally, append the annotation line
              ns += seq;
 -            content.put("annotation", ns);
 +            content.put(ANNOTATION, ns);
              // // end of wrapped annotation block.
              // // Now a new row is created with the current set of data
  
            // }
            else
            {
-             System.err
-                     .println("Warning - couldn't parse sequence annotation row line:\n"
+             System.err.println(
+                     "Warning - couldn't parse sequence annotation row line:\n"
                              + line);
              // throw new IOException("Error parsing " + line);
            }
          else
          {
            throw new IOException(MessageManager.formatMessage(
-                   "exception.unknown_annotation_detected", new String[] {
-                       annType, annContent }));
+                   "exception.unknown_annotation_detected", new String[]
+                   { annType, annContent }));
          }
        }
      }
      {
        for (DBRefEntry d : dbrs)
        {
-         jalview.util.MapList mp = new jalview.util.MapList(new int[] {
-             seqO.getStart(), seqO.getEnd() }, new int[] { st, en }, 1, 1);
+         jalview.util.MapList mp = new jalview.util.MapList(
+                 new int[]
+                 { seqO.getStart(), seqO.getEnd() }, new int[] { st, en }, 1,
+                 1);
          jalview.datamodel.Mapping mping = new Mapping(mp);
          d.setMap(mping);
        }
      String type = label;
      if (label.contains("_cons"))
      {
-       type = (label.indexOf("_cons") == label.length() - 5) ? label
-               .substring(0, label.length() - 5) : label;
+       type = (label.indexOf("_cons") == label.length() - 5)
+               ? label.substring(0, label.length() - 5)
+               : label;
      }
      boolean ss = false, posterior = false;
      type = id2type(type);
              ann.secondaryStructure = ResidueProperties.getDssp3state(pos)
                      .charAt(0);
  
-           if (ann.secondaryStructure == pos.charAt(0))
-           {
-             ann.displayCharacter = ""; // null; // " ";
-           }
-           else
-           {
-             ann.displayCharacter = " " + ann.displayCharacter;
-           }
+             if (ann.secondaryStructure == pos.charAt(0))
+             {
+               ann.displayCharacter = ""; // null; // " ";
+             }
+             else
+             {
+               ann.displayCharacter = " " + ann.displayCharacter;
+             }
            }
          }
  
      while ((in < s.length) && (s[in] != null))
      {
        String tmp = printId(s[in], jvSuffix);
 -      if (s[in].getSequence().length > max)
 -      {
 -        max = s[in].getSequence().length;
 -      }
 +      max = Math.max(max, s[in].getLength());
  
        if (tmp.length() > maxid)
        {
        {
          Object idd = en.nextElement();
          String type = (String) dataRef.remove(idd);
-         out.append(new Format("%-" + (maxid - 2) + "s").form("#=GS "
-                 + idd.toString() + " "));
+         out.append(new Format("%-" + (maxid - 2) + "s")
+                 .form("#=GS " + idd.toString() + " "));
          if (type.contains("PFAM") || type.contains("RFAM"))
          {
  
            }
  
            // out.append("#=GR ");
-           out.append(new Format("%-" + maxid + "s").form("#=GR "
-                   + printId(s[i], jvSuffix) + " " + key + " "));
+           out.append(new Format("%-" + maxid + "s").form(
+                   "#=GR " + printId(s[i], jvSuffix) + " " + key + " "));
            ann = alAnot[j].annotations;
            String seq = "";
            for (int k = 0; k < ann.length; k++)
          }
          label = label.replace(" ", "_");
  
-         out.append(new Format("%-" + maxid + "s").form("#=GC " + label
-                 + " "));
+         out.append(
+                 new Format("%-" + maxid + "s").form("#=GC " + label + " "));
          boolean isrna = aa.isValidStruc();
          for (int j = 0; j < aa.annotations.length; j++)
          {
    {
      char seq = ' ';
      Annotation annot = ann[k];
-     String ch = (annot == null) ? ((sequenceI == null) ? "-" : Character
-             .toString(sequenceI.getCharAt(k))) : annot.displayCharacter;
+     String ch = (annot == null)
+             ? ((sequenceI == null) ? "-"
+                     : Character.toString(sequenceI.getCharAt(k)))
+             : annot.displayCharacter;
      if (key != null && key.equals("SS"))
      {
        if (annot == null)
      {
        return (String) typeIds.get(id);
      }
-     System.err.println("Warning : Unknown Stockholm annotation type code "
-             + id);
+     System.err.println(
+             "Warning : Unknown Stockholm annotation type code " + id);
      return id;
    }
  
      {
        return key;
      }
-     System.err.println("Warning : Unknown Stockholm annotation type: "
-             + type);
+     System.err.println(
+             "Warning : Unknown Stockholm annotation type: " + type);
      return key;
    }
  
@@@ -191,11 -191,13 +191,13 @@@ public abstract class StructureFile ext
        {
          // TODO: use the PDB ID of the structure if one is available, to save
          // bandwidth and avoid uploading the whole structure to the service
-         Object annotate3d = cl.getConstructor(new Class[] {}).newInstance(
-                 new Object[] {});
-         AlignmentI al = ((AlignmentI) cl.getMethod("getRNAMLFor",
-                 new Class[] { FileParse.class }).invoke(annotate3d,
-                 new Object[] { new FileParse(getDataName(), dataSourceType) }));
+         Object annotate3d = cl.getConstructor(new Class[] {})
+                 .newInstance(new Object[] {});
+         AlignmentI al = ((AlignmentI) cl
+                 .getMethod("getRNAMLFor", new Class[]
+                 { FileParse.class })
+                 .invoke(annotate3d, new Object[]
+                 { new FileParse(getDataName(), dataSourceType) }));
          for (SequenceI sq : al.getSequences())
          {
            if (sq.getDatasetSequence() != null)
    }
  
    @SuppressWarnings("unchecked")
-   protected void replaceAndUpdateChains(List<SequenceI> prot,
-           AlignmentI al, String pep, boolean b)
+   protected void replaceAndUpdateChains(List<SequenceI> prot, AlignmentI al,
+           String pep, boolean b)
    {
      List<List<? extends Object>> replaced = AlignSeq
              .replaceMatchingSeqsWith(seqs, annotations, prot, al, pep,
          processWithJmolParser(proteinSequences);
        } catch (Exception x)
        {
-         System.err
-                 .println("Exceptions from Jmol when processing data in pdb file");
+         System.err.println(
+                 "Exceptions from Jmol when processing data in pdb file");
          x.printStackTrace();
        }
      }
        Class cl = Class.forName("jalview.ext.jmol.JmolParser");
        if (cl != null)
        {
-         final Constructor constructor = cl.getConstructor(new Class[] {FileParse.class });
-         final Object[] args = new Object[] { new FileParse(getDataName(), dataSourceType) };
+         final Constructor constructor = cl
+                 .getConstructor(new Class[]
+                 { FileParse.class });
+         final Object[] args = new Object[] {
+             new FileParse(getDataName(), dataSourceType) };
  
          StructureImportSettings.setShowSeqFeatures(false);
          StructureImportSettings.setVisibleChainAnnotation(false);
          StructureImportSettings
                  .setExternalSecondaryStructure(externalSecondaryStructure);
          Object jmf = constructor.newInstance(args);
-         AlignmentI al = new Alignment((SequenceI[]) cl.getMethod(
-                 "getSeqsAsArray", new Class[] {}).invoke(jmf));
+         AlignmentI al = new Alignment((SequenceI[]) cl
+                 .getMethod("getSeqsAsArray", new Class[] {}).invoke(jmf));
          cl.getMethod("addAnnotations", new Class[] { AlignmentI.class })
                  .invoke(jmf, al);
          for (SequenceI sq : al.getSequences())
  
    public static boolean isRNA(SequenceI seq)
    {
 -    for (char c : seq.getSequence())
 +    int length = seq.getLength();
 +    for (int i = 0; i < length; i++)
      {
 +      char c = seq.getCharAt(i);
        if ((c != 'A') && (c != 'C') && (c != 'G') && (c != 'U'))
        {
          return false;
      {
        dataName = dataName.substring(p + 1);
      }
-     if(dataName.indexOf(".") > -1){
+     if (dataName.indexOf(".") > -1)
+     {
        dataName = dataName.substring(0, dataName.lastIndexOf("."));
      }
      return dataName;
@@@ -165,7 -165,8 +165,8 @@@ public class ExonerateHelper extends Gf
      SequenceI mapFromSequence = seq;
      SequenceI mapToSequence = mappedSequence;
      if ((type == MappingType.NucleotideToPeptide && featureIsOnTarget)
-             || (type == MappingType.PeptideToNucleotide && !featureIsOnTarget))
+             || (type == MappingType.PeptideToNucleotide
+                     && !featureIsOnTarget))
      {
        mapFromSequence = mappedSequence;
        mapToSequence = seq;
      {
        fromStart = alignToStart;
        toStart = alignFromStart;
-       toEnd = forwardStrand ? toStart + alignCount - 1 : toStart
-               - (alignCount - 1);
+       toEnd = forwardStrand ? toStart + alignCount - 1
+               : toStart - (alignCount - 1);
        int toLength = Math.abs(toEnd - toStart) + 1;
        int fromLength = toLength * type.getFromRatio() / type.getToRatio();
        fromEnd = fromStart + fromLength - 1;
      return false;
    }
  
 +  /**
 +   * An override to set feature group to "exonerate" instead of the default GFF
 +   * source value (column 2)
 +   */
    @Override
    protected SequenceFeature buildSequenceFeature(String[] gff,
            Map<String, List<String>> set)
    {
 -    SequenceFeature sf = super.buildSequenceFeature(gff, set);
 -    sf.setFeatureGroup("exonerate");
 +    SequenceFeature sf = super.buildSequenceFeature(gff, TYPE_COL,
 +            "exonerate", set);
  
      return sf;
    }
@@@ -152,8 -152,8 +152,8 @@@ public class Gff3Helper extends GffHelp
       */
      if ("-".equals(strand))
      {
-       System.err
-               .println("Skipping mapping from reverse complement as not yet supported");
+       System.err.println(
+               "Skipping mapping from reverse complement as not yet supported");
        return null;
      }
  
     * @return
     */
    @SuppressWarnings("unused")
-   protected String findTargetId(String target, Map<String, List<String>> set)
+   protected String findTargetId(String target,
+           Map<String, List<String>> set)
    {
      return target;
    }
     * @throws IOException
     */
    protected SequenceFeature processProteinMatch(
-           Map<String, List<String>> set, SequenceI seq,
-           String[] gffColumns, AlignmentI align, List<SequenceI> newseqs,
+           Map<String, List<String>> set, SequenceI seq, String[] gffColumns,
+           AlignmentI align, List<SequenceI> newseqs,
            boolean relaxedIdMatching)
    {
      // This is currently tailored to InterProScan GFF output:
           * give the mapped sequence a copy of the sequence feature, with 
           * start/end range adjusted 
           */
 -        SequenceFeature sf2 = new SequenceFeature(sf);
 -        sf2.setBegin(1);
          int sequenceFeatureLength = 1 + sf.getEnd() - sf.getBegin();
 -        sf2.setEnd(sequenceFeatureLength);
 +        SequenceFeature sf2 = new SequenceFeature(sf, 1,
 +                sequenceFeatureLength, sf.getFeatureGroup(), sf.getScore());
          mappedSequence.addSequenceFeature(sf2);
  
          /*
           * renamed with its qualified accession id; renaming has to wait until
           * all sequence reference resolution is complete
           */
-         String accessionId = StringUtils.listToDelimitedString(
-                 set.get(NAME), ",");
+         String accessionId = StringUtils
+                 .listToDelimitedString(set.get(NAME), ",");
          if (accessionId.length() > 0)
          {
            String database = sf.getType(); // TODO InterProScan only??
     */
    @Override
    protected SequenceFeature buildSequenceFeature(String[] gff,
 +          int typeColumn, String group,
            Map<String, List<String>> attributes)
    {
 -    SequenceFeature sf = super.buildSequenceFeature(gff, attributes);
 +    SequenceFeature sf = super.buildSequenceFeature(gff, typeColumn, group,
 +            attributes);
      String desc = getDescription(sf, attributes);
      if (desc != null)
      {
@@@ -150,8 -150,8 +150,8 @@@ public abstract class GffHelperBase imp
         * restrict from range to make them match up
         * it's kind of arbitrary which end we truncate - here it is the end
         */
-       System.err.print("Truncating mapping from " + Arrays.toString(from)
-               + " to ");
+       System.err.print(
+               "Truncating mapping from " + Arrays.toString(from) + " to ");
        if (from[1] > from[0])
        {
          from[1] -= fromOverlap / toRatio;
        /*
         * restrict to range to make them match up
         */
-       System.err.print("Truncating mapping to " + Arrays.toString(to)
-               + " to ");
+       System.err.print(
+               "Truncating mapping to " + Arrays.toString(to) + " to ");
        if (to[1] > to[0])
        {
          to[1] -= fromOverlap / fromRatio;
    /**
     * Parses the input line to a map of name / value(s) pairs. For example the
     * line <br>
-    * Notes=Fe-S;Method=manual curation, prediction; source = Pfam; Notes = Metal <br>
+    * Notes=Fe-S;Method=manual curation, prediction; source = Pfam; Notes = Metal
+    * <br>
     * if parsed with delimiter=";" and separators {' ', '='} <br>
     * would return a map with { Notes={Fe=S, Metal}, Method={manual curation,
     * prediction}, source={Pfam}} <br>
    protected SequenceFeature buildSequenceFeature(String[] gff,
            Map<String, List<String>> attributes)
    {
 +    return buildSequenceFeature(gff, TYPE_COL, gff[SOURCE_COL], attributes);
 +  }
 +
 +  /**
 +   * @param gff
 +   * @param typeColumn
 +   * @param group
 +   * @param attributes
 +   * @return
 +   */
 +  protected SequenceFeature buildSequenceFeature(String[] gff,
 +          int typeColumn, String group, Map<String, List<String>> attributes)
 +  {
      try
      {
        int start = Integer.parseInt(gff[START_COL]);
          // e.g. '.' - leave as zero
        }
  
 -      SequenceFeature sf = new SequenceFeature(gff[TYPE_COL],
 -              gff[SOURCE_COL], start, end, score, gff[SOURCE_COL]);
 +      SequenceFeature sf = new SequenceFeature(gff[typeColumn],
 +              gff[SOURCE_COL], start, end, score, group);
  
        sf.setStrand(gff[STRAND_COL]);
  
           */
          for (Entry<String, List<String>> attr : attributes.entrySet())
          {
-           String values = StringUtils.listToDelimitedString(
-                   attr.getValue(), ",");
+           String values = StringUtils.listToDelimitedString(attr.getValue(),
+                   ",");
            sf.setValue(attr.getKey(), values);
            if (NOTE.equals(attr.getKey()))
            {
@@@ -73,19 -73,13 +73,19 @@@ public class InterProScanHelper extend
    }
  
    /**
 -  * 
 -  */
 +   * An override that
 +   * <ul>
 +   * <li>uses Source (column 2) as feature type instead of the default column 3</li>
 +   * <li>sets "InterProScan" as the feature group</li>
 +   * <li>extracts "signature_desc" attribute as the feature description</li>
 +   * </ul>
 +   */
    @Override
    protected SequenceFeature buildSequenceFeature(String[] gff,
            Map<String, List<String>> attributes)
    {
 -    SequenceFeature sf = super.buildSequenceFeature(gff, attributes);
 +    SequenceFeature sf = super.buildSequenceFeature(gff, SOURCE_COL,
 +            INTER_PRO_SCAN, attributes);
  
      /*
       * signature_desc is a more informative source of description
        sf.setDescription(description);
      }
  
 -    /*
 -     * Set sequence feature group as 'InterProScan', and type as the source
 -     * database for this match (e.g. 'Pfam')
 -     */
 -    sf.setType(gff[SOURCE_COL]);
 -    sf.setFeatureGroup(INTER_PRO_SCAN);
 -
      return sf;
    }
  
      SequenceOntologyI so = SequenceOntologyFactory.getInstance();
      String type = columns[TYPE_COL];
      if (so.isA(type, SequenceOntologyI.PROTEIN_MATCH)
-             || (".".equals(columns[SOURCE_COL]) && so.isA(type,
-                     SequenceOntologyI.POLYPEPTIDE)))
+             || (".".equals(columns[SOURCE_COL])
+                     && so.isA(type, SequenceOntologyI.POLYPEPTIDE)))
      {
        return true;
      }
     * GFF field 'ID' rather than the usual 'Target' :-O
     */
    @Override
-   protected String findTargetId(String target, Map<String, List<String>> set)
+   protected String findTargetId(String target,
+           Map<String, List<String>> set)
    {
      List<String> ids = set.get(ID);
      if (ids == null || ids.size() != 1)
  package jalview.io.vamsas;
  
  import jalview.datamodel.DBRefEntry;
 +import jalview.datamodel.SequenceFeature;
  import jalview.datamodel.SequenceI;
  import jalview.io.VamsasAppDatastore;
  
 +import java.util.List;
 +
  import uk.ac.vamsas.objects.core.DataSet;
  import uk.ac.vamsas.objects.core.DbRef;
  import uk.ac.vamsas.objects.core.Sequence;
@@@ -48,12 -45,12 +48,12 @@@ public class Datasetsequence extends Da
    // private AlignmentI jvdset;
  
    public Datasetsequence(VamsasAppDatastore vamsasAppDatastore,
--          SequenceI sq, String dict, DataSet dataset)
++          SequenceI sq, String theDict, DataSet theDataset)
    {
      super(vamsasAppDatastore, sq, uk.ac.vamsas.objects.core.Sequence.class);
--    this.dataset = dataset;
++    this.dataset = theDataset;
      // this.jvdset = jvdset;
--    this.dict = dict;
++    this.dict = theDict;
      doSync();
    }
  
@@@ -64,7 -61,6 +64,7 @@@
      doJvUpdate();
    }
  
 +  @Override
    public void addFromDocument()
    {
      Sequence vseq = (Sequence) vobj;
      modified = true;
    }
  
 +  @Override
    public void updateFromDoc()
    {
      Sequence sq = (Sequence) vobj;
      SequenceI sequence = (SequenceI) jvobj;
      if (!sequence.getSequenceAsString().equals(sq.getSequence()))
      {
-       log.warn("Potential Client Error ! - mismatch of dataset sequence: and jalview internal dataset sequence.");
+       log.warn(
+               "Potential Client Error ! - mismatch of dataset sequence: and jalview internal dataset sequence.");
      }
      else
      {
     */
    private boolean updateSqFeatures()
    {
 -    boolean modified = false;
 +    boolean changed = false;
      SequenceI sq = (SequenceI) jvobj;
  
      // add or update any new features/references on dataset sequence
 -    if (sq.getSequenceFeatures() != null)
 +    List<SequenceFeature> sfs = sq.getSequenceFeatures();
 +    for (SequenceFeature sf : sfs)
      {
 -      int sfSize = sq.getSequenceFeatures().length;
 -
 -      for (int sf = 0; sf < sfSize; sf++)
 -      {
 -        modified |= new jalview.io.vamsas.Sequencefeature(datastore,
 -                (jalview.datamodel.SequenceFeature) sq
 -                        .getSequenceFeatures()[sf],
 -                dataset, (Sequence) vobj).docWasUpdated();
 -      }
 +      changed |= new jalview.io.vamsas.Sequencefeature(datastore, sf,
 +              dataset, (Sequence) vobj).docWasUpdated();
      }
 -    return modified;
 +
 +    return changed;
    }
  
 +  @Override
    public void addToDocument()
    {
      SequenceI sq = (SequenceI) jvobj;
        for (int db = 0; db < entries.length; db++)
        {
          modifiedthedoc |= new jalview.io.vamsas.Dbref(datastore,
-         // dbentry =
+                 // dbentry =
                  entries[db], sq, (Sequence) vobj, dataset).docWasUpdated();
  
        }
        for (int db = 0; db < entries.length; db++)
        {
          modifiedtheseq |= new jalview.io.vamsas.Dbref(datastore,
-         // dbentry =
+                 // dbentry =
                  entries[db], vsq, sq).jvWasUpdated();
        }
      }
      return modifiedtheseq;
    }
  
 +  @Override
    public void conflict()
    {
-     log.warn("Conflict in dataset sequence update to document. Overwriting document");
+     log.warn(
+             "Conflict in dataset sequence update to document. Overwriting document");
      // TODO: could try to import from document data to jalview first. and then
      updateToDoc();
    }
  
    boolean modified = false;
  
 +  @Override
    public void updateToDoc()
    {
      SequenceI sq = (SequenceI) jvobj;
@@@ -97,10 -97,8 +97,8 @@@ public class Sequencefeature extends Ra
      DataSetAnnotations dsa = (DataSetAnnotations) vobj;
      if (dsa.getSeqRefCount() != 1)
      {
-       Cache.log
-               .warn("Not binding "
-                       + dsa.getVorbaId()
-                       + " to Sequence Feature - has multiple dataset sequence references.");
+       Cache.log.warn("Not binding " + dsa.getVorbaId()
+               + " to Sequence Feature - has multiple dataset sequence references.");
        return;
      }
      jalview.datamodel.SequenceFeature sf = (jalview.datamodel.SequenceFeature) jvobj;
      if (dsa.getSeqRefCount() != 1)
      {
        replaceJvObjMapping(feature, null);
-       Cache.log
-               .warn("Binding of annotation to jalview feature has changed. Removing binding and recreating.");
+       Cache.log.warn(
+               "Binding of annotation to jalview feature has changed. Removing binding and recreating.");
        doSync(); // re-verify bindings.
      }
      else
      {
        // conflicting update from document - we cannot map this feature anymore.
        replaceJvObjMapping(feature, null);
-       Cache.log
-               .warn("annotation ("
-                       + dsa.getVorbaId()
-                       + " bound to jalview feature cannot be mapped. Removing binding, deleting feature, and deleting feature.");
+       Cache.log.warn("annotation (" + dsa.getVorbaId()
+               + " bound to jalview feature cannot be mapped. Removing binding, deleting feature, and deleting feature.");
        // - consider deleting the feature ?
        dsSeq.deleteFeature(feature);
        // doSync();
      vSeg.setInclusive(true);
      if (dsa.getSegCount() > 1)
      {
-       Cache.log
-               .debug("About to destroy complex annotation in vamsas document mapped to sequence feature ("
+       Cache.log.debug(
+               "About to destroy complex annotation in vamsas document mapped to sequence feature ("
                        + dsa.getVorbaId() + ")");
      }
      dsa.setSeg(new Seg[] { vSeg });
            else if (vlu instanceof Integer)
            {
              valid = true;
-             nprop.setType(uk.ac.vamsas.objects.utils.Properties.INTEGERTYPE);
+             nprop.setType(
+                     uk.ac.vamsas.objects.utils.Properties.INTEGERTYPE);
            }
            else if (vlu instanceof Float)
            {
    private SequenceFeature getJalviewSeqFeature(RangeAnnotation dseta)
    {
      int[] se = getBounds(dseta);
 -    SequenceFeature sf = new jalview.datamodel.SequenceFeature(
 -            dseta.getType(), dseta.getDescription(), dseta.getStatus(),
 -            se[0], se[1], dseta.getGroup());
 +
 +    /*
 +     * try to identify feature score
 +     */
 +    boolean scoreFound = false;
 +    float theScore = 0f;
 +    String featureType = dseta.getType();
 +    if (dseta.getScoreCount() > 0)
 +    {
 +      Enumeration scr = dseta.enumerateScore();
 +      while (scr.hasMoreElements())
 +      {
 +        Score score = (Score) scr.nextElement();
 +        if (score.getName().equals(featureType))
 +        {
 +          theScore = score.getContent();
 +          scoreFound = true;
 +        }
 +      }
 +    }
 +
 +    SequenceFeature sf = null;
 +    if (scoreFound)
 +    {
 +      sf = new SequenceFeature(featureType, dseta.getDescription(), se[0],
 +              se[1], theScore, dseta.getGroup());
 +    }
 +    else
 +    {
 +      sf = new SequenceFeature(featureType, dseta.getDescription(), se[0],
 +              se[1], dseta.getGroup());
 +    }
 +    sf.setStatus(dseta.getStatus());
      if (dseta.getLinkCount() > 0)
      {
        Link[] links = dseta.getLink();
        while (scr.hasMoreElements())
        {
          Score score = (Score) scr.nextElement();
 -        if (score.getName().equals(sf.getType()))
 -        {
 -          sf.setScore(score.getContent());
 -        }
 -        else
 +        if (!score.getName().equals(sf.getType()))
          {
            sf.setValue(score.getName(), "" + score.getContent());
          }
@@@ -1,3 -1,23 +1,23 @@@
+ /*
+  * Jalview - A Sequence Alignment Editor and Viewer ($$Version-Rel$$)
+  * Copyright (C) $$Year-Rel$$ The Jalview Authors
+  * 
+  * This file is part of Jalview.
+  * 
+  * Jalview is free software: you can redistribute it and/or
+  * modify it under the terms of the GNU General Public License 
+  * as published by the Free Software Foundation, either version 3
+  * of the License, or (at your option) any later version.
+  *  
+  * Jalview is distributed in the hope that it will be useful, but 
+  * WITHOUT ANY WARRANTY; without even the implied warranty 
+  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
+  * PURPOSE.  See the GNU General Public License for more details.
+  * 
+  * You should have received a copy of the GNU General Public License
+  * along with Jalview.  If not, see <http://www.gnu.org/licenses/>.
+  * The Jalview Authors are detailed in the 'AUTHORS' file.
+  */
  package jalview.renderer.seqfeatures;
  
  import jalview.api.FeatureRenderer;
@@@ -55,7 -75,7 +75,7 @@@ public class FeatureColourFinde
     * @param defaultColour
     * @param seq
     * @param column
 -   *          alignment column position (base zero)
 +   *          alignment column position (0..)
     * @return
     */
    public Color findFeatureColour(Color defaultColour, SequenceI seq,
        }
      }
  
 -    Color c = featureRenderer.findFeatureColour(seq, column, g);
 +    Color c = featureRenderer.findFeatureColour(seq, column + 1, g);
      if (c == null)
      {
        return defaultColour;
@@@ -21,8 -21,6 +21,8 @@@
  package jalview.renderer.seqfeatures;
  
  import jalview.api.AlignViewportI;
 +import jalview.api.FeatureColourI;
 +import jalview.datamodel.Range;
  import jalview.datamodel.SequenceFeature;
  import jalview.datamodel.SequenceI;
  import jalview.util.Comparison;
@@@ -33,7 -31,6 +33,7 @@@ import java.awt.Color
  import java.awt.FontMetrics;
  import java.awt.Graphics;
  import java.awt.Graphics2D;
 +import java.util.List;
  
  public class FeatureRenderer extends FeatureRendererModel
  {
  
        g.setColor(featureColour);
  
-       g.fillRect((i - start) * charWidth, y1, charWidth,
-               charHeight);
+       g.fillRect((i - start) * charWidth, y1, charWidth, charHeight);
  
        if (colourOnly || !validCharWidth)
        {
  
        g.setColor(Color.white);
        int charOffset = (charWidth - fm.charWidth(s)) / 2;
-       g.drawString(String.valueOf(s), charOffset
-               + (charWidth * (i - start)), pady);
+       g.drawString(String.valueOf(s),
+               charOffset + (charWidth * (i - start)), pady);
      }
      return true;
    }
  
        g.setColor(Color.black);
        int charOffset = (charWidth - fm.charWidth(s)) / 2;
-       g.drawString(String.valueOf(s), charOffset
-               + (charWidth * (i - start)), pady);
+       g.drawString(String.valueOf(s),
+               charOffset + (charWidth * (i - start)), pady);
      }
      return true;
    }
        return null;
      }
  
 -    SequenceFeature[] sequenceFeatures = seq.getSequenceFeatures();
 -
 -    if (sequenceFeatures == null || sequenceFeatures.length == 0)
 -    {
 -      return null;
 -    }
 -
      if (Comparison.isGap(seq.getCharAt(column)))
      {
        /*
         * returning null allows the colour scheme to provide gap colour
 -       * - normally white, but can be customised otherwise
 +       * - normally white, but can be customised
         */
        return null;
      }
        /*
         * simple case - just find the topmost rendered visible feature colour
         */
 -      renderedColour = findFeatureColour(seq, seq.findPosition(column));
 +      renderedColour = findFeatureColour(seq, column);
      }
      else
      {
            final SequenceI seq, int start, int end, int y1,
            boolean colourOnly)
    {
 -    SequenceFeature[] sequenceFeatures = seq.getSequenceFeatures();
 -    if (sequenceFeatures == null || sequenceFeatures.length == 0)
 +    /*
 +     * if columns are all gapped, or sequence has no features, nothing to do
 +     */
 +    Range visiblePositions = seq.findPositions(start+1, end+1);
 +    if (visiblePositions == null || !seq.getFeatures().hasFeatures())
      {
        return null;
      }
                transparency));
      }
  
 -    int startPos = seq.findPosition(start);
 -    int endPos = seq.findPosition(end);
 -
 -    int sfSize = sequenceFeatures.length;
      Color drawnColour = null;
  
      /*
          continue;
        }
  
 -      // loop through all features in sequence to find
 -      // current feature to render
 -      for (int sfindex = 0; sfindex < sfSize; sfindex++)
 +      FeatureColourI fc = getFeatureStyle(type);
 +      List<SequenceFeature> overlaps = seq.getFeatures().findFeatures(
 +              visiblePositions.getBegin(), visiblePositions.getEnd(), type);
 +
 +      filterFeaturesForDisplay(overlaps, fc);
 +
 +      for (SequenceFeature sf : overlaps)
        {
 -        final SequenceFeature sequenceFeature = sequenceFeatures[sfindex];
 -        if (!sequenceFeature.type.equals(type))
 +        Color featureColour = fc.getColor(sf);
 +        if (featureColour == null)
          {
 +          // score feature outwith threshold for colouring
            continue;
          }
  
          /*
 -         * a feature type may be flagged as shown but the group 
 -         * an instance of it belongs to may be hidden
 +         * if feature starts/ends outside the visible range,
 +         * restrict to visible positions (or if a contact feature,
 +         * to a single position)
           */
 -        if (featureGroupNotShown(sequenceFeature))
 +        int visibleStart = sf.getBegin();
 +        if (visibleStart < visiblePositions.getBegin())
          {
 -          continue;
 +          visibleStart = sf.isContactFeature() ? sf.getEnd()
 +                  : visiblePositions.getBegin();
          }
 -
 -        /*
 -         * check feature overlaps the target range
 -         * TODO: efficient retrieval of features overlapping a range
 -         */
 -        if (sequenceFeature.getBegin() > endPos
 -                || sequenceFeature.getEnd() < startPos)
 +        int visibleEnd = sf.getEnd();
 +        if (visibleEnd > visiblePositions.getEnd())
          {
 -          continue;
 +          visibleEnd = sf.isContactFeature() ? sf.getBegin()
 +                  : visiblePositions.getEnd();
          }
  
 -        Color featureColour = getColour(sequenceFeature);
 -        if (featureColour == null)
 -        {
 -          // score feature outwith threshold for colouring
 -          continue;
 -        }
 +        int featureStartCol = seq.findIndex(visibleStart);
 +        int featureEndCol = sf.begin == sf.end ? featureStartCol : seq
 +                .findIndex(visibleEnd);
 +
 +        // Color featureColour = getColour(sequenceFeature);
  
 -        boolean isContactFeature = sequenceFeature.isContactFeature();
 +        boolean isContactFeature = sf.isContactFeature();
  
          if (isContactFeature)
          {
 -          boolean drawn = renderFeature(g, seq,
 -                  seq.findIndex(sequenceFeature.begin) - 1,
 -                  seq.findIndex(sequenceFeature.begin) - 1, featureColour,
 -                  start, end, y1, colourOnly);
 -          drawn |= renderFeature(g, seq,
 -                  seq.findIndex(sequenceFeature.end) - 1,
 -                  seq.findIndex(sequenceFeature.end) - 1, featureColour,
 -                  start, end, y1, colourOnly);
 +          boolean drawn = renderFeature(g, seq, featureStartCol - 1,
 +                  featureStartCol - 1, featureColour, start, end, y1,
 +                  colourOnly);
 +          drawn |= renderFeature(g, seq, featureEndCol - 1,
 +                  featureEndCol - 1, featureColour, start, end, y1,
 +                  colourOnly);
            if (drawn)
            {
              drawnColour = featureColour;
          }
          else
          {
 +          /*
 +           * showing feature score by height of colour
 +           * is not implemented as a selectable option 
 +           *
            if (av.isShowSequenceFeaturesHeight()
                    && !Float.isNaN(sequenceFeature.score))
            {
            }
            else
            {
 +          */
              boolean drawn = renderFeature(g, seq,
 -                    seq.findIndex(sequenceFeature.begin) - 1,
 -                    seq.findIndex(sequenceFeature.end) - 1, featureColour,
 +                    featureStartCol - 1,
 +                    featureEndCol - 1, featureColour,
                      start, end, y1, colourOnly);
              if (drawn)
              {
                drawnColour = featureColour;
              }
 -          }
 +          /*}*/
          }
        }
      }
    }
  
    /**
++<<<<<<< HEAD
++=======
+    * Answers true if the feature belongs to a feature group which is not
+    * currently displayed, else false
+    * 
+    * @param sequenceFeature
+    * @return
+    */
++  @Override
+   protected boolean featureGroupNotShown(
+           final SequenceFeature sequenceFeature)
+   {
+     return featureGroups != null && sequenceFeature.featureGroup != null
+             && sequenceFeature.featureGroup.length() != 0
+             && featureGroups.containsKey(sequenceFeature.featureGroup)
+             && !featureGroups.get(sequenceFeature.featureGroup)
+                     .booleanValue();
+   }
+   /**
++>>>>>>> refs/heads/develop
     * Called when alignment in associated view has new/modified features to
     * discover and display.
     * 
    }
  
    /**
 -   * Returns the sequence feature colour rendered at the given sequence
 -   * position, or null if none found. The feature of highest render order (i.e.
 -   * on top) is found, subject to both feature type and feature group being
 -   * visible, and its colour returned.
 +   * Returns the sequence feature colour rendered at the given column position,
 +   * or null if none found. The feature of highest render order (i.e. on top) is
 +   * found, subject to both feature type and feature group being visible, and
 +   * its colour returned. This method is suitable when no feature transparency
 +   * applied (only the topmost visible feature colour is rendered).
 +   * <p>
 +   * Note this method does not check for a gap in the column so would return the
 +   * colour for features enclosing a gapped column. Check for gap before calling
 +   * if different behaviour is wanted.
     * 
     * @param seq
 -   * @param pos
 +   * @param column
 +   *          (1..)
     * @return
     */
 -  Color findFeatureColour(SequenceI seq, int pos)
 +  Color findFeatureColour(SequenceI seq, int column)
    {
 -    SequenceFeature[] sequenceFeatures = seq.getSequenceFeatures();
 -    if (sequenceFeatures == null || sequenceFeatures.length == 0)
 -    {
 -      return null;
 -    }
 -
      /*
       * check for new feature added while processing
       */
       * inspect features in reverse renderOrder (the last in the array is 
       * displayed on top) until we find one that is rendered at the position
       */
-     for (int renderIndex = renderOrder.length - 1; renderIndex >= 0; renderIndex--)
+     for (int renderIndex = renderOrder.length
+             - 1; renderIndex >= 0; renderIndex--)
      {
        String type = renderOrder[renderIndex];
        if (!showFeatureOfType(type))
          continue;
        }
  
 -      for (int sfindex = 0; sfindex < sequenceFeatures.length; sfindex++)
 +      List<SequenceFeature> overlaps = seq.findFeatures(column, column,
 +              type);
 +      for (SequenceFeature sequenceFeature : overlaps)
        {
 -        SequenceFeature sequenceFeature = sequenceFeatures[sfindex];
 -        if (!sequenceFeature.type.equals(type))
 -        {
 -          continue;
 -        }
 -
 -        if (featureGroupNotShown(sequenceFeature))
 +        if (!featureGroupNotShown(sequenceFeature))
          {
 -          continue;
 -        }
 -
 -        /*
 -         * check the column position is within the feature range
 -         * (or is one of the two contact positions for a contact feature)
 -         */
 -        boolean featureIsAtPosition = sequenceFeature.begin <= pos
 -                && sequenceFeature.end >= pos;
 -        if (sequenceFeature.isContactFeature())
 -        {
 -          featureIsAtPosition = sequenceFeature.begin == pos
 -                  || sequenceFeature.end == pos;
 -        }
 -        if (featureIsAtPosition)
 -        {
 -          return getColour(sequenceFeature);
 +          Color col = getColour(sequenceFeature);
 +          if (col != null)
 +          {
 +            return col;
 +          }
          }
        }
      }
-   
      /*
       * no displayed feature found at position
       */
@@@ -43,8 -43,8 +43,8 @@@ public class ClustalxColourScheme exten
    {
      RED(0.9f, 0.2f, 0.1f), BLUE(0.5f, 0.7f, 0.9f), GREEN(0.1f, 0.8f, 0.1f),
      ORANGE(0.9f, 0.6f, 0.3f), CYAN(0.1f, 0.7f, 0.7f),
-     PINK(0.9f, 0.5f, 0.5f), MAGENTA(0.8f, 0.3f, 0.8f), YELLOW(0.8f, 0.8f,
-             0.0f);
+     PINK(0.9f, 0.5f, 0.5f), MAGENTA(0.8f, 0.3f, 0.8f),
+     YELLOW(0.8f, 0.8f, 0.0f);
  
      final Color colour;
  
@@@ -53,6 -53,7 +53,7 @@@
        colour = new Color(r, g, b);
      }
    }
    private class ConsensusColour
    {
      Consensus[] cons;
  
      for (SequenceI sq : seqs)
      {
 -      char[] seq = sq.getSequence();
 -
 -      int end_j = seq.length - 1;
 +      int end_j = sq.getLength() - 1;
 +      int length = sq.getLength();
  
        for (int i = 0; i <= end_j; i++)
        {
 -        if ((seq.length - 1) < i)
 +        if (length - 1 < i)
          {
            res = 23;
          }
          else
          {
 -          res = ResidueProperties.aaIndex[seq[i]];
 +          res = ResidueProperties.aaIndex[sq.getCharAt(i)];
          }
          cons2[i][res]++;
        }
  
      for (int k = 0; k < residueColour[i].cons.length; k++)
      {
-       if (residueColour[i].cons[k].isConserved(cons2, j, size,
-               includeGaps))
+       if (residueColour[i].cons[k].isConserved(cons2, j, size, includeGaps))
        {
          colour = residueColour[i].c;
        }
@@@ -125,8 -125,8 +125,8 @@@ public class FeatureColour implements F
        Color colour = ColorUtils.parseColourString(descriptor);
        if (colour == null)
        {
-         throw new IllegalArgumentException("Invalid colour descriptor: "
-                 + descriptor);
+         throw new IllegalArgumentException(
+                 "Invalid colour descriptor: " + descriptor);
        }
        return new FeatureColour(colour);
      }
          {
            if (!ttype.toLowerCase().startsWith("no"))
            {
-             System.err.println("Ignoring unrecognised threshold type : "
-                     + ttype);
+             System.err.println(
+                     "Ignoring unrecognised threshold type : " + ttype);
            }
          }
        }
        }
        if (gcol.hasMoreTokens())
        {
-         System.err
-                 .println("Ignoring additional tokens in parameters in graduated colour specification\n");
+         System.err.println(
+                 "Ignoring additional tokens in parameters in graduated colour specification\n");
          while (gcol.hasMoreTokens())
          {
            System.err.println("|" + gcol.nextToken());
    {
      if (isColourByLabel())
      {
-       return ColorUtils
-               .createColourFromName(feature.getDescription());
+       return ColorUtils.createColourFromName(feature.getDescription());
      }
  
      if (!isGraduatedColour())
        return getColour();
      }
  
 -    // todo should we check for above/below threshold here?
 -    if (range == 0.0)
 -    {
 -      return getMaxColour();
 -    }
 +    /*
 +     * graduated colour case, optionally with threshold
 +     * Float.NaN is assigned minimum visible score colour
 +     */
      float scr = feature.getScore();
      if (Float.isNaN(scr))
      {
        return getMinColour();
      }
 +    if (isAboveThreshold() && scr <= threshold)
 +    {
 +      return null;
 +    }
 +    if (isBelowThreshold() && scr >= threshold)
 +    {
 +      return null;
 +    }
 +    if (range == 0.0)
 +    {
 +      return getMaxColour();
 +    }
      float scl = (scr - base) / range;
      if (isHighToLow)
      {
      return (isHighToLow) ? (base + range) : base;
    }
  
 -  /**
 -   * Answers true if the feature has a simple colour, or is coloured by label,
 -   * or has a graduated colour and the score of this feature instance is within
 -   * the range to render (if any), i.e. does not lie below or above any
 -   * threshold set.
 -   * 
 -   * @param feature
 -   * @return
 -   */
 -  @Override
 -  public boolean isColored(SequenceFeature feature)
 -  {
 -    if (isColourByLabel() || !isGraduatedColour())
 -    {
 -      return true;
 -    }
 -
 -    float val = feature.getScore();
 -    if (Float.isNaN(val))
 -    {
 -      return true;
 -    }
 -    if (Float.isNaN(this.threshold))
 -    {
 -      return true;
 -    }
 -
 -    if (isAboveThreshold() && val <= threshold)
 -    {
 -      return false;
 -    }
 -    if (isBelowThreshold() && val >= threshold)
 -    {
 -      return false;
 -    }
 -    return true;
 -  }
 -
    @Override
    public boolean isSimpleColour()
    {
@@@ -40,8 -40,9 +40,9 @@@ public class Compariso
  
    public static final char GAP_DASH = '-';
  
-   public static final String GapChars = new String(new char[] { GAP_SPACE,
-       GAP_DOT, GAP_DASH });
+   public static final String GapChars = new String(
+           new char[]
+           { GAP_SPACE, GAP_DOT, GAP_DASH });
  
    /**
     * DOCUMENT ME!
@@@ -71,7 -72,8 +72,8 @@@
     *          int
     * @return float
     */
-   public static float compare(SequenceI ii, SequenceI jj, int start, int end)
+   public static float compare(SequenceI ii, SequenceI jj, int start,
+           int end)
    {
      String si = ii.getSequenceAsString();
      String sj = jj.getSequenceAsString();
@@@ -97,8 -99,8 +99,8 @@@
      {
        for (int j = 0; j < jlen; j++)
        {
-         if (si.substring(start + j, start + j + 1).equals(
-                 sj.substring(start + j, start + j + 1)))
+         if (si.substring(start + j, start + j + 1)
+                 .equals(sj.substring(start + j, start + j + 1)))
          {
            match++;
          }
      {
        for (int j = 0; j < jlen; j++)
        {
-         if (si.substring(start + j, start + j + 1).equals(
-                 sj.substring(start + j, start + j + 1)))
+         if (si.substring(start + j, start + j + 1)
+                 .equals(sj.substring(start + j, start + j + 1)))
          {
            match++;
          }
     * @deprecated use PIDModel.computePID()
     */
    @Deprecated
-   public final static float PID(String seq1, String seq2, int start, int end)
+   public final static float PID(String seq1, String seq2, int start,
+           int end)
    {
      return PID(seq1, seq2, start, end, true, false);
    }
      {
        return false;
      }
 -    char[][] letters = new char[seqs.length][];
 -    for (int i = 0; i < seqs.length; i++)
 -    {
 -      if (seqs[i] != null)
 -      {
 -        char[] sequence = seqs[i].getSequence();
 -        if (sequence != null)
 -        {
 -          letters[i] = sequence;
 -        }
 -      }
 -    }
 -
 -    return areNucleotide(letters);
 -  }
  
 -  /**
 -   * Answers true if more than 85% of the sequence residues (ignoring gaps) are
 -   * A, G, C, T or U, else false. This is just a heuristic guess and may give a
 -   * wrong answer (as AGCT are also amino acid codes).
 -   * 
 -   * @param letters
 -   * @return
 -   */
 -  static final boolean areNucleotide(char[][] letters)
 -  {
      int ntCount = 0;
      int aaCount = 0;
 -    for (char[] seq : letters)
 +    for (SequenceI seq : seqs)
      {
        if (seq == null)
        {
        }
        // TODO could possibly make an informed guess just from the first sequence
        // to save a lengthy calculation
 -      for (char c : seq)
 +      int len = seq.getLength();
 +      for (int i = 0; i < len; i++)
        {
 +        char c = seq.getCharAt(i);
          if (isNucleotide(c))
          {
            ntCount++;
          flattened.add(s);
        }
      }
-     final SequenceI[] oneDArray = flattened.toArray(new SequenceI[flattened
-             .size()]);
+     final SequenceI[] oneDArray = flattened
+             .toArray(new SequenceI[flattened.size()]);
      return isNucleotide(oneDArray);
    }
  
@@@ -456,42 -456,18 +456,42 @@@ public class ViewportRanges extends Vie
    }
  
    /**
 -   * Scroll a wrapped alignment so that the specified residue is visible. Fires
 -   * a property change event.
 +   * Scroll a wrapped alignment so that the specified residue is in the first
 +   * repeat of the wrapped view. Fires a property change event. Answers true if
 +   * the startRes changed, else false.
     * 
     * @param res
     *          residue position to scroll to
 +   * @return
     */
 -  public void scrollToWrappedVisible(int res)
 +  public boolean scrollToWrappedVisible(int res)
    {
 -    // get the start residue of the wrapped row which res is in
 -    // and set that as our start residue
 +    int oldStartRes = startRes;
      int width = getViewportWidth();
 -    setStartRes((res / width) * width);
 +
 +    if (res >= oldStartRes && res < oldStartRes + width)
 +    {
 +      return false;
 +    }
 +
 +    boolean up = res < oldStartRes;
 +    int widthsToScroll = Math.abs((res - oldStartRes) / width);
 +    if (up)
 +    {
 +      widthsToScroll++;
 +    }
 +
 +    int residuesToScroll = width * widthsToScroll;
 +    int newStartRes = up ? oldStartRes - residuesToScroll : oldStartRes
 +            + residuesToScroll;
 +    if (newStartRes < 0)
 +    {
 +      newStartRes = 0;
 +    }
 +
 +    setStartRes(newStartRes);
 +
 +    return true;
    }
  
    /**
        }
      }
    }
-   
    /**
     * Adjust sequence position for page up. Fires a property change event.
     */
                getViewportHeight());
      }
    }
-   
    /**
     * Adjust sequence position for page down. Fires a property change event.
     */
@@@ -26,7 -26,6 +26,7 @@@ import jalview.api.FeaturesDisplayedI
  import jalview.datamodel.AlignmentI;
  import jalview.datamodel.SequenceFeature;
  import jalview.datamodel.SequenceI;
 +import jalview.datamodel.features.SequenceFeatures;
  import jalview.renderer.seqfeatures.FeatureRenderer;
  import jalview.schemes.FeatureColour;
  import jalview.util.ColorUtils;
@@@ -45,8 -44,8 +45,8 @@@ import java.util.Map
  import java.util.Set;
  import java.util.concurrent.ConcurrentHashMap;
  
- public abstract class FeatureRendererModel implements
-         jalview.api.FeatureRenderer
+ public abstract class FeatureRendererModel
+         implements jalview.api.FeatureRenderer
  {
  
    /**
            synchronized (fd)
            {
              fd.clear();
 -            java.util.Iterator<String> fdisp = _fr.getFeaturesDisplayed()
 -                    .getVisibleFeatures();
 -            while (fdisp.hasNext())
 +            for (String type : _fr.getFeaturesDisplayed()
 +                    .getVisibleFeatures())
              {
 -              fd.setVisible(fdisp.next());
 +              fd.setVisible(type);
              }
            }
          }
        if (r[0] != 0 || mm[0] < 0.0)
        {
          r[0] = 1;
-         r[1] = (byte) ((int) 128.0 + 127.0 * (sequenceFeature.score / mm[1]));
+         r[1] = (byte) ((int) 128.0
+                 + 127.0 * (sequenceFeature.score / mm[1]));
        }
        else
        {
    }
  
    @Override
 -  public List<SequenceFeature> findFeaturesAtRes(SequenceI sequence,
 -          int res)
 +  public List<SequenceFeature> findFeaturesAtColumn(SequenceI sequence, int column)
    {
 -    ArrayList<SequenceFeature> tmp = new ArrayList<SequenceFeature>();
 -    SequenceFeature[] features = sequence.getSequenceFeatures();
 -
 -    if (features != null)
 +    /*
 +     * include features at the position provided their feature type is 
 +     * displayed, and feature group is null or marked for display
 +     */
 +    List<SequenceFeature> result = new ArrayList<SequenceFeature>();
 +    if (!av.areFeaturesDisplayed() || getFeaturesDisplayed() == null)
      {
 -      for (int i = 0; i < features.length; i++)
 -      {
 -        if (!av.areFeaturesDisplayed() || !av.getFeaturesDisplayed()
 -                .isVisible(features[i].getType()))
 -        {
 -          continue;
 -        }
 +      return result;
 +    }
  
 -        if (features[i].featureGroup != null && featureGroups != null
 -                && featureGroups.containsKey(features[i].featureGroup)
 -                && !featureGroups.get(features[i].featureGroup)
 -                        .booleanValue())
 -        {
 -          continue;
 -        }
 +    Set<String> visibleFeatures = getFeaturesDisplayed()
 +            .getVisibleFeatures();
 +    String[] visibleTypes = visibleFeatures
 +            .toArray(new String[visibleFeatures.size()]);
 +    List<SequenceFeature> features = sequence.findFeatures(column, column,
 +            visibleTypes);
  
 -        // check if start/end are at res, and if not a contact feature, that res
 -        // lies between start and end
 -        if ((features[i].getBegin() == res || features[i].getEnd() == res)
 -                || (!features[i].isContactFeature()
 -                        && (features[i].getBegin() < res)
 -                        && (features[i].getEnd() >= res)))
 -        {
 -          tmp.add(features[i]);
 -        }
 +    for (SequenceFeature sf : features)
 +    {
 +      if (!featureGroupNotShown(sf))
 +      {
 +        result.add(sf);
        }
      }
 -    return tmp;
 +    return result;
    }
  
    /**
     * Searches alignment for all features and updates colours
     * 
     * @param newMadeVisible
 -   *          if true newly added feature types will be rendered immediatly
 +   *          if true newly added feature types will be rendered immediately
     *          TODO: check to see if this method should actually be proxied so
     *          repaint events can be propagated by the renderer code
     */
      }
      FeaturesDisplayedI featuresDisplayed = av.getFeaturesDisplayed();
  
 -    ArrayList<String> allfeatures = new ArrayList<String>();
 -    ArrayList<String> oldfeatures = new ArrayList<String>();
 +    Set<String> oldfeatures = new HashSet<String>();
      if (renderOrder != null)
      {
        for (int i = 0; i < renderOrder.length; i++)
          }
        }
      }
 -    if (minmax == null)
 -    {
 -      minmax = new Hashtable<String, float[][]>();
 -    }
  
 -    Set<String> oldGroups = new HashSet<String>(featureGroups.keySet());
      AlignmentI alignment = av.getAlignment();
 +    List<String> allfeatures = new ArrayList<String>();
 +
      for (int i = 0; i < alignment.getHeight(); i++)
      {
        SequenceI asq = alignment.getSequenceAt(i);
 -      SequenceFeature[] features = asq.getSequenceFeatures();
 -
 -      if (features == null)
 -      {
 -        continue;
 -      }
 -
 -      int index = 0;
 -      while (index < features.length)
 +      for (String group : asq.getFeatures().getFeatureGroups(true))
        {
 -        String fgrp = features[index].getFeatureGroup();
 -        oldGroups.remove(fgrp);
 -        if (!featuresDisplayed.isRegistered(features[index].getType()))
 +        boolean groupDisplayed = true;
 +        if (group != null)
          {
 -          if (fgrp != null)
 +          if (featureGroups.containsKey(group))
            {
 -            Boolean groupDisplayed = featureGroups.get(fgrp);
 -            if (groupDisplayed == null)
 -            {
 -              groupDisplayed = Boolean.valueOf(newMadeVisible);
 -              featureGroups.put(fgrp, groupDisplayed);
 -            }
 -            if (!groupDisplayed.booleanValue())
 -            {
 -              index++;
 -              continue;
 -            }
 +            groupDisplayed = featureGroups.get(group);
            }
 -          if (!(features[index].begin == 0 && features[index].end == 0))
 +          else
            {
 -            // If beginning and end are 0, the feature is for the whole sequence
 -            // and we don't want to render the feature in the normal way
 -
 -            if (newMadeVisible
 -                    && !oldfeatures.contains(features[index].getType()))
 -            {
 -              // this is a new feature type on the alignment. Mark it for
 -              // display.
 -              featuresDisplayed.setVisible(features[index].getType());
 -              setOrder(features[index].getType(), 0);
 -            }
 +            groupDisplayed = newMadeVisible;
 +            featureGroups.put(group, groupDisplayed);
            }
          }
 -        if (!allfeatures.contains(features[index].getType()))
 +        if (groupDisplayed)
          {
 -          allfeatures.add(features[index].getType());
 -        }
 -        if (!Float.isNaN(features[index].score))
 -        {
 -          int nonpos = features[index].getBegin() >= 1 ? 0 : 1;
 -          float[][] mm = minmax.get(features[index].getType());
 -          if (mm == null)
 -          {
 -            mm = new float[][] { null, null };
 -            minmax.put(features[index].getType(), mm);
 -          }
 -          if (mm[nonpos] == null)
 -          {
 -            mm[nonpos] = new float[] { features[index].score,
 -                features[index].score };
 -
 -          }
 -          else
 +          Set<String> types = asq.getFeatures().getFeatureTypesForGroups(
 +                  true, group);
 +          for (String type : types)
            {
 -            if (mm[nonpos][0] > features[index].score)
 +            if (!allfeatures.contains(type)) // or use HashSet and no test?
              {
 -              mm[nonpos][0] = features[index].score;
 -            }
 -            if (mm[nonpos][1] < features[index].score)
 -            {
 -              mm[nonpos][1] = features[index].score;
 +              allfeatures.add(type);
              }
 +            updateMinMax(asq, type, true); // todo: for all features?
            }
          }
        }
      }
  
 -    /*
 -     * oldGroups now consists of groups that no longer 
 -     * have any feature in them - remove these
 -     */
 -    for (String grp : oldGroups)
 +    // uncomment to add new features in alphebetical order (but JAL-2575)
 +    // Collections.sort(allfeatures, String.CASE_INSENSITIVE_ORDER);
 +    if (newMadeVisible)
      {
 -      featureGroups.remove(grp);
 +      for (String type : allfeatures)
 +      {
 +        if (!oldfeatures.contains(type))
 +        {
 +          featuresDisplayed.setVisible(type);
 +          setOrder(type, 0);
 +        }
 +      }
      }
  
      updateRenderOrder(allfeatures);
      findingFeatures = false;
    }
  
 +  /**
 +   * Updates the global (alignment) min and max values for a feature type from
 +   * the score for a sequence, if the score is not NaN. Values are stored
 +   * separately for positional and non-positional features.
 +   * 
 +   * @param seq
 +   * @param featureType
 +   * @param positional
 +   */
 +  protected void updateMinMax(SequenceI seq, String featureType,
 +          boolean positional)
 +  {
 +    float min = seq.getFeatures().getMinimumScore(featureType, positional);
 +    if (Float.isNaN(min))
 +    {
 +      return;
 +    }
 +
 +    float max = seq.getFeatures().getMaximumScore(featureType, positional);
 +
 +    /*
 +     * stored values are 
 +     * { {positionalMin, positionalMax}, {nonPositionalMin, nonPositionalMax} }
 +     */
 +    if (minmax == null)
 +    {
 +      minmax = new Hashtable<String, float[][]>();
 +    }
 +    synchronized (minmax)
 +    {
 +      float[][] mm = minmax.get(featureType);
 +      int index = positional ? 0 : 1;
 +      if (mm == null)
 +      {
 +        mm = new float[][] { null, null };
 +        minmax.put(featureType, mm);
 +      }
 +      if (mm[index] == null)
 +      {
 +        mm[index] = new float[] { min, max };
 +      }
 +      else
 +      {
 +        mm[index][0] = Math.min(mm[index][0], min);
 +        mm[index][1] = Math.max(mm[index][1], max);
 +      }
 +    }
 +  }
    protected Boolean firing = Boolean.FALSE;
  
    /**
          {
            if (initOrders)
            {
-             setOrder(oldRender[j], (1 - (1 + (float) j) / oldRender.length));
+             setOrder(oldRender[j],
+                     (1 - (1 + (float) j) / oldRender.length));
            }
            if (allfeatures.contains(oldRender[j]))
            {
     * Returns the configured colour for a particular feature instance. This
     * includes calculation of 'colour by label', or of a graduated score colour,
     * if applicable. It does not take into account feature visibility or colour
 -   * transparency.
 +   * transparency. Returns null for a score feature whose score value lies
 +   * outside any colour threshold.
     * 
     * @param feature
     * @return
    public Color getColour(SequenceFeature feature)
    {
      FeatureColourI fc = getFeatureStyle(feature.getType());
 -    return fc.isColored(feature) ? fc.getColor(feature) : null;
 -  }
 -
 -  /**
 -   * Answers true unless the feature has a score value which lies outside a
 -   * minimum or maximum threshold configured for colouring. This method does not
 -   * check feature type or group visibility.
 -   * 
 -   * @param sequenceFeature
 -   * @return
 -   */
 -  protected boolean showFeature(SequenceFeature sequenceFeature)
 -  {
 -    FeatureColourI fc = getFeatureStyle(sequenceFeature.type);
 -    return fc.isColored(sequenceFeature);
 +    return fc.getColor(feature);
    }
  
    /**
    }
  
    /**
 -   * Sets the priority order for features
 +   * Sets the priority order for features, with the highest priority (displayed
 +   * on top) at the start of the data array
     * 
     * @param data
     *          { String(Type), Colour(Type), Boolean(Displayed) }
        }
        else
        {
-         av.setFeaturesDisplayed(av_featuresdisplayed = new FeaturesDisplayed());
+         av.setFeaturesDisplayed(
+                 av_featuresdisplayed = new FeaturesDisplayed());
        }
      }
      else
    {
      // conflict between applet and desktop - featureGroups returns the map in
      // the desktop featureRenderer
-     return (featureGroups == null) ? Arrays.asList(new String[0]) : Arrays
-             .asList(featureGroups.keySet().toArray(new String[0]));
+     return (featureGroups == null) ? Arrays.asList(new String[0])
+             : Arrays.asList(featureGroups.keySet().toArray(new String[0]));
    }
  
-   public boolean checkGroupVisibility(String group, boolean newGroupsVisible)
+   public boolean checkGroupVisibility(String group,
+           boolean newGroupsVisible)
    {
      if (featureGroups == null)
      {
      {
        return fcols;
      }
 -    Iterator<String> features = getViewport().getFeaturesDisplayed()
 +    Set<String> features = getViewport().getFeaturesDisplayed()
              .getVisibleFeatures();
 -    while (features.hasNext())
 +    for (String feature : features)
      {
 -      String feature = features.next();
        fcols.put(feature, getFeatureStyle(feature));
      }
      return fcols;
    public List<String> getDisplayedFeatureGroups()
    {
      List<String> _gps = new ArrayList<String>();
 -    boolean valid = false;
      for (String gp : getFeatureGroups())
      {
        if (checkGroupVisibility(gp, false))
        {
 -        valid = true;
          _gps.add(gp);
        }
 -      if (!valid)
 +    }
 +    return _gps;
 +  }
 +
 +  /**
 +   * Answers true if the feature belongs to a feature group which is not
 +   * currently displayed, else false
 +   * 
 +   * @param sequenceFeature
 +   * @return
 +   */
 +  protected boolean featureGroupNotShown(final SequenceFeature sequenceFeature)
 +  {
 +    return featureGroups != null
 +            && sequenceFeature.featureGroup != null
 +            && sequenceFeature.featureGroup.length() != 0
 +            && featureGroups.containsKey(sequenceFeature.featureGroup)
 +            && !featureGroups.get(sequenceFeature.featureGroup)
 +                    .booleanValue();
 +  }
 +
 +  /**
 +   * {@inheritDoc}
 +   */
 +  @Override
 +  public List<SequenceFeature> findFeaturesAtResidue(SequenceI sequence,
 +          int resNo)
 +  {
 +    List<SequenceFeature> result = new ArrayList<SequenceFeature>();
 +    if (!av.areFeaturesDisplayed() || getFeaturesDisplayed() == null)
 +    {
 +      return result;
 +    }
 +
 +    /*
 +     * include features at the position provided their feature type is 
 +     * displayed, and feature group is null or the empty string
 +     * or marked for display
 +     */
 +    Set<String> visibleFeatures = getFeaturesDisplayed()
 +            .getVisibleFeatures();
 +    String[] visibleTypes = visibleFeatures
 +            .toArray(new String[visibleFeatures.size()]);
 +    List<SequenceFeature> features = sequence.getFeatures().findFeatures(
 +            resNo, resNo, visibleTypes);
 +  
 +    for (SequenceFeature sf : features)
 +    {
 +      if (!featureGroupNotShown(sf))
        {
 -        return null;
 +        result.add(sf);
        }
 -      else
 +    }
 +    return result;
 +  }
 +
 +  /**
 +   * Removes from the list of features any that have a feature group that is not
 +   * displayed, or duplicate the location of a feature of the same type (unless
 +   * a graduated colour scheme or colour by label is applied). Should be used
 +   * only for features of the same feature colour (which normally implies the
 +   * same feature type).
 +   * 
 +   * @param features
 +   * @param fc
 +   */
 +  public void filterFeaturesForDisplay(List<SequenceFeature> features,
 +          FeatureColourI fc)
 +  {
 +    if (features.isEmpty())
 +    {
 +      return;
 +    }
 +    SequenceFeatures.sortFeatures(features, true);
 +    boolean simpleColour = fc == null || fc.isSimpleColour();
 +    SequenceFeature lastFeature = null;
 +
 +    Iterator<SequenceFeature> it = features.iterator();
 +    while (it.hasNext())
 +    {
 +      SequenceFeature sf = it.next();
 +      if (featureGroupNotShown(sf))
        {
 -        // gps = new String[_gps.size()];
 -        // _gps.toArray(gps);
 +        it.remove();
 +        continue;
        }
 +
 +      /*
 +       * a feature is redundant for rendering purposes if it has the
 +       * same extent as another (so would just redraw the same colour);
 +       * (checking type and isContactFeature as a fail-safe here, although
 +       * currently they are guaranteed to match in this context)
 +       */
 +      if (simpleColour)
 +      {
 +        if (lastFeature != null && sf.getBegin() == lastFeature.getBegin()
 +                && sf.getEnd() == lastFeature.getEnd()
 +                && sf.isContactFeature() == lastFeature.isContactFeature()
 +                && sf.getType().equals(lastFeature.getType()))
 +        {
 +          it.remove();
 +        }
 +      }
 +      lastFeature = sf;
      }
 -    return _gps;
    }
  
  }
@@@ -26,6 -26,7 +26,6 @@@ import jalview.datamodel.AlignmentI
  import jalview.datamodel.DBRefEntry;
  import jalview.datamodel.DBRefSource;
  import jalview.datamodel.Mapping;
 -import jalview.datamodel.SequenceFeature;
  import jalview.datamodel.SequenceI;
  import jalview.gui.CutAndPasteTransfer;
  import jalview.gui.DasSourceBrowser;
@@@ -164,9 -165,9 +164,9 @@@ public class DBRefFetcher implements Ru
      // af.featureSettings_actionPerformed(null);
      String[] defdb = null;
      List<DbSourceProxy> selsources = new ArrayList<DbSourceProxy>();
-     Vector<jalviewSourceI> dasselsrc = (featureSettings != null) ? featureSettings
-             .getSelectedSources() : new DasSourceBrowser()
-             .getSelectedSources();
+     Vector<jalviewSourceI> dasselsrc = (featureSettings != null)
+             ? featureSettings.getSelectedSources()
+             : new DasSourceBrowser().getSelectedSources();
  
      for (jalviewSourceI src : dasselsrc)
      {
        DbSourceProxy[] newsrc = new DbSourceProxy[dbSources.length
                + otherdb.length];
        System.arraycopy(dbSources, 0, newsrc, 0, dbSources.length);
-       System.arraycopy(otherdb, 0, newsrc, dbSources.length, otherdb.length);
+       System.arraycopy(otherdb, 0, newsrc, dbSources.length,
+               otherdb.length);
        dbSources = newsrc;
      }
    }
    {
      if (dbSources == null)
      {
-       throw new Error(
-               MessageManager
-                       .getString("error.implementation_error_must_init_dbsources"));
+       throw new Error(MessageManager
+               .getString("error.implementation_error_must_init_dbsources"));
      }
      running = true;
      long startTime = System.currentTimeMillis();
              String query = queries.elementAt(0);
              if (dbsource.isValidReference(query))
              {
-               queryString.append((numq == 0) ? "" : dbsource
-                       .getAccessionSeparator());
+               queryString.append(
+                       (numq == 0) ? "" : dbsource.getAccessionSeparator());
                queryString.append(query);
                numq++;
              }
          else
          {
            // make some more strings for use as queries
-           for (int i = 0; (seqIndex < dataset.length) && (i < 50); seqIndex++, i++)
+           for (int i = 0; (seqIndex < dataset.length)
+                   && (i < 50); seqIndex++, i++)
            {
              SequenceI sequence = dataset[seqIndex];
-             DBRefEntry[] uprefs = DBRefUtils.selectRefs(
-                     sequence.getDBRefs(),
-                     new String[] { dbsource.getDbSource() }); // jalview.datamodel.DBRefSource.UNIPROT
+             DBRefEntry[] uprefs = DBRefUtils
+                     .selectRefs(sequence.getDBRefs(), new String[]
+                     { dbsource.getDbSource() }); // jalview.datamodel.DBRefSource.UNIPROT
              // });
              // check for existing dbrefs to use
              if (uprefs != null && uprefs.length > 0)
                for (int j = 0; j < uprefs.length; j++)
                {
                  addSeqId(sequence, uprefs[j].getAccessionId());
-                 queries.addElement(uprefs[j].getAccessionId().toUpperCase());
+                 queries.addElement(
+                         uprefs[j].getAccessionId().toUpperCase());
                }
              }
              else
                    // resolve the string against PICR to recover valid IDs
                    try
                    {
-                     presp = picrClient
-                             .getUPIForAccession(token, null,
-                                     picrClient.getMappedDatabaseNames(),
-                                     null, true);
+                     presp = picrClient.getUPIForAccession(token, null,
+                             picrClient.getMappedDatabaseNames(), null,
+                             true);
                    } catch (Exception e)
                    {
-                     System.err.println("Exception with Picr for '" + token
-                             + "'\n");
+                     System.err.println(
+                             "Exception with Picr for '" + token + "'\n");
                      e.printStackTrace();
                    }
                  }
                      // present, and do a transferReferences
                      // otherwise transfer non sequence x-references directly.
                    }
-                   System.out
-                           .println("Validated ID against PICR... (for what its worth):"
+                   System.out.println(
+                           "Validated ID against PICR... (for what its worth):"
                                    + token);
                    addSeqId(sequence, token);
                    queries.addElement(token.toUpperCase());
                  else
                  {
                    // if ()
-                   // System.out.println("Not querying source with token="+token+"\n");
+                   // System.out.println("Not querying source with
+                   // token="+token+"\n");
                    addSeqId(sequence, token);
                    queries.addElement(token.toUpperCase());
                  }
        output.setText(sb.toString());
  
        Desktop.addInternalFrame(output,
-               MessageManager.getString("label.sequences_updated"), 600, 300);
+               MessageManager.getString("label.sequences_updated"), 600,
+               300);
        // The above is the dataset, we must now find out the index
        // of the viewed sequence
  
      }
  
      boolean modified = false;
-     SequenceI[] retrieved = recoverDbSequences(retrievedAl
-             .getSequencesArray());
+     SequenceI[] retrieved = recoverDbSequences(
+             retrievedAl.getSequencesArray());
      SequenceI sequence = null;
  
      for (SequenceI retrievedSeq : retrieved)
        // taking into account all accessionIds and names in the file
        Vector<SequenceI> sequenceMatches = new Vector<SequenceI>();
        // look for corresponding accession ids
-       DBRefEntry[] entryRefs = DBRefUtils.selectRefs(
-               retrievedSeq.getDBRefs(), new String[] { dbSource });
+       DBRefEntry[] entryRefs = DBRefUtils
+               .selectRefs(retrievedSeq.getDBRefs(), new String[]
+               { dbSource });
        if (entryRefs == null)
        {
          System.err
          final int sequenceStart = sequence.getStart();
  
          boolean remoteEnclosesLocal = false;
-         String nonGapped = AlignSeq.extractGaps("-. ",
-                 sequence.getSequenceAsString()).toUpperCase();
+         String nonGapped = AlignSeq
+                 .extractGaps("-. ", sequence.getSequenceAsString())
+                 .toUpperCase();
          int absStart = entrySeq.indexOf(nonGapped);
          if (absStart == -1)
          {
             * So create a mapping to the external entry from the matching region of 
             * the local sequence, and leave local start/end untouched. 
             */
-           mp = new Mapping(null, new int[] { sequenceStart + absStart,
-               sequenceStart + absStart + entrySeq.length() - 1 }, new int[]
-           { retrievedSeq.getStart(),
-               retrievedSeq.getStart() + entrySeq.length() - 1 }, 1, 1);
+           mp = new Mapping(null,
+                   new int[]
+                   { sequenceStart + absStart,
+                       sequenceStart + absStart + entrySeq.length() - 1 },
+                   new int[]
+                   { retrievedSeq.getStart(),
+                       retrievedSeq.getStart() + entrySeq.length() - 1 },
+                   1, 1);
            updateRefFrame = false;
          }
          else
  
            if (updateRefFrame)
            {
 -            SequenceFeature[] sfs = sequence.getSequenceFeatures();
 -            if (sfs != null)
 +            /*
 +             * relocate existing sequence features by offset
 +             */
 +            int startShift = absStart - sequenceStart + 1;
 +            if (startShift != 0)
              {
 -              /*
 -               * relocate existing sequence features by offset
 -               */
 -              int start = sequenceStart;
 -              int end = sequence.getEnd();
 -              int startShift = 1 - absStart - start;
 -
 -              if (startShift != 0)
 -              {
 -                for (SequenceFeature sf : sfs)
 -                {
 -                  if (sf.getBegin() >= start && sf.getEnd() <= end)
 -                  {
 -                    sf.setBegin(sf.getBegin() + startShift);
 -                    sf.setEnd(sf.getEnd() + startShift);
 -                    modified = true;
 -                  }
 -                }
 -              }
 +              modified |= sequence.getFeatures().shiftFeatures(startShift);
              }
            }
          }
              sequence.setSequence(retrievedSeqString);
              modified = true;
              addWarningMessage(warningMessages,
-                     "Sequence for " + sequence.getName()
-                             + " expanded from " + retrievedSeq.getName());
+                     "Sequence for " + sequence.getName() + " expanded from "
+                             + retrievedSeq.getName());
            }
            if (sequence.getStart() != retrievedSeq.getStart())
            {
              modified = true;
              if (absStart != sequenceStart)
              {
-               addWarningMessage(warningMessages, "Start/end position for "
-                       + sequence.getName() + " updated from "
-                       + retrievedSeq.getName());
+               addWarningMessage(warningMessages,
+                       "Start/end position for " + sequence.getName()
+                               + " updated from " + retrievedSeq.getName());
              }
            }
          }
                sequence.setStart(absStart);
                sequence.setEnd(absEnd);
                modified = true;
-               addWarningMessage(warningMessages, "Start/end for "
-                       + sequence.getName() + " updated from "
-                       + retrievedSeq.getName());
+               addWarningMessage(warningMessages,
+                       "Start/end for " + sequence.getName()
+                               + " updated from " + retrievedSeq.getName());
              }
            }
            // search for alignment sequences to update coordinate frame for
            {
              if (alseqs[alsq].getDatasetSequence() == sequence)
              {
-               String ngAlsq = AlignSeq.extractGaps("-. ",
-                       alseqs[alsq].getSequenceAsString()).toUpperCase();
+               String ngAlsq = AlignSeq
+                       .extractGaps("-. ",
+                               alseqs[alsq].getSequenceAsString())
+                       .toUpperCase();
                int oldstrt = alseqs[alsq].getStart();
                alseqs[alsq].setStart(sequence.getSequenceAsString()
-                       .toUpperCase().indexOf(ngAlsq)
-                       + sequence.getStart());
+                       .toUpperCase().indexOf(ngAlsq) + sequence.getStart());
                if (oldstrt != alseqs[alsq].getStart())
                {
-                 alseqs[alsq].setEnd(ngAlsq.length()
-                         + alseqs[alsq].getStart() - 1);
+                 alseqs[alsq].setEnd(
+                         ngAlsq.length() + alseqs[alsq].getStart() - 1);
                  modified = true;
                }
              }
    private SequenceI[] recoverDbSequences(SequenceI[] sequencesArray)
    {
      Vector<SequenceI> nseq = new Vector<SequenceI>();
-     for (int i = 0; sequencesArray != null && i < sequencesArray.length; i++)
+     for (int i = 0; sequencesArray != null
+             && i < sequencesArray.length; i++)
      {
        nseq.addElement(sequencesArray[i]);
        DBRefEntry[] dbr = sequencesArray[i].getDBRefs();
@@@ -28,9 -28,8 +28,9 @@@ import jalview.datamodel.PDBEntry
  import jalview.datamodel.Sequence;
  import jalview.datamodel.SequenceFeature;
  import jalview.datamodel.SequenceI;
 -import jalview.datamodel.UniprotEntry;
 -import jalview.datamodel.UniprotFile;
 +import jalview.datamodel.xdb.uniprot.UniprotEntry;
 +import jalview.datamodel.xdb.uniprot.UniprotFeature;
 +import jalview.datamodel.xdb.uniprot.UniprotFile;
  import jalview.ws.ebi.EBIFetchClient;
  import jalview.ws.seqfetcher.DbSourceProxyImpl;
  
@@@ -167,7 -166,8 +167,8 @@@ public class Uniprot extends DbSourcePr
        // uniprotkb dbname changed introduced december 2008
        File file = ebi.fetchDataAsFile("uniprotkb:" + queries, "uniprotxml",
                "xml");
-       Vector<UniprotEntry> entries = getUniprotEntries(new FileReader(file));
+       Vector<UniprotEntry> entries = getUniprotEntries(
+               new FileReader(file));
  
        if (entries != null)
        {
    public SequenceI uniprotEntryToSequenceI(UniprotEntry entry)
    {
      String id = getUniprotEntryId(entry);
-     SequenceI sequence = new Sequence(id, entry.getUniprotSequence()
-             .getContent());
+     SequenceI sequence = new Sequence(id,
+             entry.getUniprotSequence().getContent());
      sequence.setDescription(getUniprotEntryDescription(entry));
  
      final String dbVersion = getDbVersion();
          String cdsId = (String) pdb.getProperty("protein sequence ID");
          if (cdsId != null && cdsId.trim().length() > 0)
          {
-           dbr = new DBRefEntry(DBRefSource.ENSEMBL, DBRefSource.UNIPROT
-                   + ":" + dbVersion, cdsId.trim());
+           dbr = new DBRefEntry(DBRefSource.ENSEMBL,
+                   DBRefSource.UNIPROT + ":" + dbVersion, cdsId.trim());
            dbRefs.add(dbr);
  
          }
        }
 -
      }
  
      sequence.setPDBId(onlyPdbEntries);
      if (entry.getFeature() != null)
      {
 -      for (SequenceFeature sf : entry.getFeature())
 +      for (UniprotFeature uf : entry.getFeature())
        {
 -        sf.setFeatureGroup("Uniprot");
 -        sequence.addSequenceFeature(sf);
 +        SequenceFeature copy = new SequenceFeature(uf.getType(),
 +                uf.getDescription(), uf.getBegin(), uf.getEnd(), "Uniprot");
 +        copy.setStatus(uf.getStatus());
 +        sequence.addSequenceFeature(copy);
        }
      }
      for (DBRefEntry dbr : dbRefs)
@@@ -20,6 -20,7 +20,6 @@@
   */
  package jalview.ws.jws2;
  
 -import jalview.api.AlignCalcWorkerI;
  import jalview.api.FeatureColourI;
  import jalview.bin.Cache;
  import jalview.datamodel.AlignmentAnnotation;
@@@ -62,9 -63,9 +62,9 @@@ public class AADisorderClient extends J
    AlignFrame af;
  
    public AADisorderClient(Jws2Instance sh, AlignFrame alignFrame,
--          WsParamSetI preset, List<Argument> paramset)
++          WsParamSetI thePreset, List<Argument> paramset)
    {
--    super(sh, alignFrame, preset, paramset);
++    super(sh, alignFrame, thePreset, paramset);
      af = alignFrame;
      typeName = sh.action;
      methodName = sh.serviceType;
      Map<String, String[]> fmap;
      featureMap.put(compbio.ws.client.Services.IUPredWS.toString(),
              fmap = new HashMap<String, String[]>());
-     fmap.put("Glob", new String[] { "Globular Domain",
-         "Predicted globular domain" });
+     fmap.put("Glob",
+             new String[]
+             { "Globular Domain", "Predicted globular domain" });
      featureMap.put(compbio.ws.client.Services.JronnWS.toString(),
              fmap = new HashMap<String, String[]>());
      featureMap.put(compbio.ws.client.Services.DisemblWS.toString(),
      fmap.put("COILS", new String[] { "COILS", "Random coil" });
      featureMap.put(compbio.ws.client.Services.GlobPlotWS.toString(),
              fmap = new HashMap<String, String[]>());
-     fmap.put("GlobDoms", new String[] { "Globular Domain",
-         "Predicted globular domain" });
-     fmap.put("Disorder", new String[] { "Protein Disorder",
-         "Probable unstructured peptide region" });
+     fmap.put("GlobDoms",
+             new String[]
+             { "Globular Domain", "Predicted globular domain" });
+     fmap.put("Disorder",
+             new String[]
+             { "Protein Disorder", "Probable unstructured peptide region" });
      Map<String, Map<String, Object>> amap;
      annotMap = new HashMap<String, Map<String, Map<String, Object>>>();
      annotMap.put(compbio.ws.client.Services.GlobPlotWS.toString(),
          {
            Cache.log
                    .info("Couldn't recover disorder prediction for sequence "
-                           + seq.getName()
-                           + "(Prediction name was "
-                           + seqId
+                           + seq.getName() + "(Prediction name was " + seqId
                            + ")"
                            + "\nSee http://issues.jalview.org/browse/JAL-1319 for one possible reason why disorder predictions might fail.");
          }
                  }
                  if (vals.hasNext())
                  {
 -                  sf = new SequenceFeature(type[0], type[1], base + rn.from,
 -                          base + rn.to, val = vals.next().floatValue(),
 -                          methodName);
 +                  val = vals.next().floatValue();
 +                  sf = new SequenceFeature(type[0], type[1],
 +                          base + rn.from, base + rn.to, val, methodName);
                  }
                  else
                  {
 -                  sf = new SequenceFeature(type[0], type[1], null,
 +                  sf = new SequenceFeature(type[0], type[1],
                            base + rn.from, base + rn.to, methodName);
                  }
                  dseq.addSequenceFeature(sf);
                        typename = service.serviceType + " ("
                                + scr.getMethod() + ")",
                        calcName = service.getServiceTypeURI() + "/"
-                               + scr.getMethod(), aseq, base + 1, scr);
+                               + scr.getMethod(),
+                       aseq, base + 1, scr);
                annot.graph = AlignmentAnnotation.LINE_GRAPH;
  
                Map<String, Object> styleMap = (annotTypeMap == null) ? null
                        : annotTypeMap.get(scr.getMethod());
  
-               annot.visible = (styleMap == null || styleMap.get(INVISIBLE) == null);
+               annot.visible = (styleMap == null
+                       || styleMap.get(INVISIBLE) == null);
                double[] thrsh = (styleMap == null) ? null
                        : (double[]) styleMap.get(THRESHOLD);
                float[] range = (styleMap == null) ? null
                  annot.description += "<br/>" + threshNote;
                }
                annot.description += "</html>";
-               Color col = ColorUtils.createColourFromName(typeName
-                       + scr.getMethod());
+               Color col = ColorUtils
+                       .createColourFromName(typeName + scr.getMethod());
                for (int p = 0, ps = annot.annotations.length; p < ps; p++)
                {
                  if (annot.annotations[p] != null)
@@@ -65,7 -65,7 +65,7 @@@ public class SeqVector extends InputTyp
        {
          idvector.append(sep);
        }
 -      idvector.append(seq.getSequence());
 +      idvector.append(seq.getSequenceAsString());
      }
      return new StringBody(idvector.toString());
    }
          return true;
        } catch (Exception x)
        {
-         warnings.append("Invalid molecule type '" + val
-                 + "'. Must be one of (");
+         warnings.append(
+                 "Invalid molecule type '" + val + "'. Must be one of (");
          for (molType v : molType.values())
          {
            warnings.append(" " + v);
      List<OptionI> lst = getBaseOptions();
      lst.add(new Option("sep",
              "Separator character between elements of vector", true, ",",
-             sep, Arrays.asList(new String[] { " ", ",", ";", "\t", "|" }),
-             null));
+             sep, Arrays.asList(new String[]
+             { " ", ",", ";", "\t", "|" }), null));
      lst.add(createMolTypeOption("type", "Sequence type", false, type,
              molType.MIX));