-{"seqs":[{"name":"FER_CAPAN/3-34","start":3,"svid":"1.0","end":34,"id":"1665704504","seq":"SVSATMISTSFMPRKPAVTSL-KPIPNVGE--ALF","order":1},{"name":"FER1_SOLLC/3-34","start":3,"svid":"1.0","end":34,"id":"1003594867","seq":"SISGTMISTSFLPRKPAVTSL-KAISNVGE--ALF","order":2},{"name":"Q93XJ9_SOLTU/3-34","start":3,"svid":"1.0","end":34,"id":"1332961135","seq":"SISGTMISTSFLPRKPVVTSL-KAISNVGE--ALF","order":3},{"name":"FER1_PEA/6-37","start":6,"svid":"1.0","end":37,"id":"1335040546","seq":"ALYGTAVSTSFLRTQPMPMSV-TTTKAFSN--GFL","order":4},{"name":"Q7XA98_TRIPR/6-39","start":6,"svid":"1.0","end":39,"id":"1777084554","seq":"ALYGTAVSTSFMRRQPVPMSV-ATTTTTKAFPSGF","order":5},{"name":"FER_TOCH/3-34","start":3,"svid":"1.0","end":34,"id":"823528539","seq":"FILGTMISKSFLFRKPAVTSL-KAISNVGE--ALF","order":6}],"appSettings":{"globalColorScheme":"zappo","webStartUrl":"www.jalview.org/services/launchApp","application":"Jalview","hiddenSeqs":"823528539","showSeqFeatures":"true","version":"2.9","hiddenCols":"32-33;34-34"},"seqGroups":[{"displayText":true,"startRes":21,"groupName":"JGroup:1883305585","endRes":29,"colourText":false,"sequenceRefs":["1003594867","1332961135","1335040546","1777084554"],"svid":"1.0","showNonconserved":false,"colourScheme":"Zappo","displayBoxes":true}],"alignAnnotation":[{"svid":"1.0","annotations":[{"displayCharacter":"","value":0,"secondaryStructure":"\u0000"},{"displayCharacter":"","value":0,"secondaryStructure":"\u0000"},{"displayCharacter":"α","value":0,"secondaryStructure":"H"},{"displayCharacter":"α","value":0,"secondaryStructure":"H"},{"displayCharacter":"α","value":0,"secondaryStructure":"H"},{"displayCharacter":"","value":0,"secondaryStructure":"\u0000"},{"displayCharacter":"","value":0,"secondaryStructure":"\u0000"},{"displayCharacter":"","value":0,"secondaryStructure":"\u0000"},{"displayCharacter":"β","value":0,"secondaryStructure":"E"},{"displayCharacter":"β","value":0,"secondaryStructure":"E"},{"displayCharacter":"β","value":0,"secondaryStructure":"E"},{"displayCharacter":"β","value":0,"secondaryStructure":"E"},{"displayCharacter":"β","value":0,"secondaryStructure":"E"},{"displayCharacter":"β","value":0,"secondaryStructure":"E"},{"displayCharacter":"β","value":0,"secondaryStructure":"E"},{"displayCharacter":"β","value":0,"secondaryStructure":"E"},{"displayCharacter":"","value":0,"secondaryStructure":"\u0000"},{"displayCharacter":"","value":0,"secondaryStructure":"\u0000"},{"displayCharacter":"","value":0,"secondaryStructure":"\u0000"},{"displayCharacter":"","value":0,"secondaryStructure":"\u0000"},{"displayCharacter":"","value":0,"secondaryStructure":"\u0000"},{"displayCharacter":"","value":0,"secondaryStructure":"\u0000"},{"displayCharacter":"","value":0,"secondaryStructure":"\u0000"},{"displayCharacter":"","value":0,"secondaryStructure":"\u0000"},{"displayCharacter":"","value":0,"secondaryStructure":"\u0000"},{"displayCharacter":"","value":0,"secondaryStructure":"\u0000"},{"displayCharacter":"α","value":0,"secondaryStructure":"H"},{"displayCharacter":"α","value":0,"secondaryStructure":"H"},{"displayCharacter":"α","value":0,"secondaryStructure":"H"},{"displayCharacter":"α","value":0,"secondaryStructure":"H"},{"displayCharacter":"α","value":0,"secondaryStructure":"H"},{"displayCharacter":"","value":0,"secondaryStructure":"\u0000"},{"displayCharacter":"","value":0,"secondaryStructure":"\u0000"},{"displayCharacter":"","value":0,"secondaryStructure":"\u0000"},{"displayCharacter":"","value":0,"secondaryStructure":"\u0000"}],"description":"New description","label":"Secondary Structure"}],"svid":"1.0","seqFeatures":[{"fillColor":"#7d1633","score":0,"sequenceRef":"1332961135","featureGroup":"Jalview","svid":"1.0","description":"desciption","xStart":3,"xEnd":13,"type":"feature_x"},{"fillColor":"#7d1633","score":0,"sequenceRef":"1335040546","featureGroup":"Jalview","svid":"1.0","description":"desciption","xStart":3,"xEnd":13,"type":"feature_x"},{"fillColor":"#7d1633","score":0,"sequenceRef":"1777084554","featureGroup":"Jalview","svid":"1.0","description":"desciption","xStart":3,"xEnd":13,"type":"feature_x"}]}
\ No newline at end of file
+{"seqs":[{"name":"FER_CAPAN/3-34","start":3,"svid":"1.0","end":34,"id":"1665704504","seq":"SVSATMISTSFMPRKPAVTSL-KPIPNVGE--ALF","order":1},{"name":"FER1_SOLLC/3-34","start":3,"svid":"1.0","end":34,"id":"1003594867","seq":"SISGTMISTSFLPRKPAVTSL-KAISNVGE--ALF","order":2},{"name":"Q93XJ9_SOLTU/3-34","start":3,"svid":"1.0","end":34,"id":"1332961135","seq":"SISGTMISTSFLPRKPVVTSL-KAISNVGE--ALF","order":3},{"name":"FER1_PEA/6-37","start":6,"svid":"1.0","end":37,"id":"1335040546","seq":"ALYGTAVSTSFLRTQPMPMSV-TTTKAFSN--GFL","order":4},{"name":"Q7XA98_TRIPR/6-39","start":6,"svid":"1.0","end":39,"id":"1777084554","seq":"ALYGTAVSTSFMRRQPVPMSV-ATTTTTKAFPSGF","order":5},{"name":"FER_TOCH/3-34","start":3,"svid":"1.0","end":34,"id":"823528539","seq":"FILGTMISKSFLFRKPAVTSL-KAISNVGE--ALF","order":6}],"appSettings":{"globalColorScheme":"zappo","webStartUrl":"www.jalview.org/services/launchApp","application":"Jalview","hiddenSeqs":"823528539","showSeqFeatures":"true","version":"2.9","hiddenCols":"32-33;34-34"},"seqGroups":[{"displayText":true,"startRes":21,"groupName":"JGroup:1883305585","endRes":29,"colourText":false,"sequenceRefs":["1003594867","1332961135","1335040546","1777084554"],"svid":"1.0","showNonconserved":false,"colourScheme":"Zappo","displayBoxes":true}],"alignAnnotation":[{"svid":"1.0","annotations":[{"displayCharacter":"","value":0,"secondaryStructure":"\u0000"},{"displayCharacter":"","value":0,"secondaryStructure":"\u0000"},{"displayCharacter":"α","value":0,"secondaryStructure":"H"},{"displayCharacter":"α","value":0,"secondaryStructure":"H"},{"displayCharacter":"α","value":0,"secondaryStructure":"H"},{"displayCharacter":"","value":0,"secondaryStructure":"\u0000"},{"displayCharacter":"","value":0,"secondaryStructure":"\u0000"},{"displayCharacter":"","value":0,"secondaryStructure":"\u0000"},{"displayCharacter":"β","value":0,"secondaryStructure":"E"},{"displayCharacter":"β","value":0,"secondaryStructure":"E"},{"displayCharacter":"β","value":0,"secondaryStructure":"E"},{"displayCharacter":"β","value":0,"secondaryStructure":"E"},{"displayCharacter":"β","value":0,"secondaryStructure":"E"},{"displayCharacter":"β","value":0,"secondaryStructure":"E"},{"displayCharacter":"β","value":0,"secondaryStructure":"E"},{"displayCharacter":"β","value":0,"secondaryStructure":"E"},{"displayCharacter":"","value":0,"secondaryStructure":"\u0000"},{"displayCharacter":"","value":0,"secondaryStructure":"\u0000"},{"displayCharacter":"","value":0,"secondaryStructure":"\u0000"},{"displayCharacter":"","value":0,"secondaryStructure":"\u0000"},{"displayCharacter":"","value":0,"secondaryStructure":"\u0000"},{"displayCharacter":"","value":0,"secondaryStructure":"\u0000"},{"displayCharacter":"","value":0,"secondaryStructure":"\u0000"},{"displayCharacter":"","value":0,"secondaryStructure":"\u0000"},{"displayCharacter":"","value":0,"secondaryStructure":"\u0000"},{"displayCharacter":"","value":0,"secondaryStructure":"\u0000"},{"displayCharacter":"α","value":0,"secondaryStructure":"H"},{"displayCharacter":"α","value":0,"secondaryStructure":"H"},{"displayCharacter":"α","value":0,"secondaryStructure":"H"},{"displayCharacter":"α","value":0,"secondaryStructure":"H"},{"displayCharacter":"α","value":0,"secondaryStructure":"H"},{"displayCharacter":"","value":0,"secondaryStructure":"\u0000"},{"displayCharacter":"","value":0,"secondaryStructure":"\u0000"},{"displayCharacter":"","value":0,"secondaryStructure":"\u0000"},{"displayCharacter":"","value":0,"secondaryStructure":"\u0000"}],"description":"New description","label":"Secondary Structure"}],"svid":"1.0","seqFeatures":[{"fillColor":"#7d1633","score":0,"otherDetails":{"status":"+"},"sequenceRef":"1332961135","featureGroup":"Pfam","svid":"1.0","description":"My description","xStart":0,"xEnd":0,"type":"Domain"},{"fillColor":"#7d1633","score":0,"sequenceRef":"1332961135","featureGroup":"Jalview","svid":"1.0","description":"theDesc","xStart":3,"xEnd":13,"type":"feature_x"},{"fillColor":"#7d1633","score":0,"sequenceRef":"1335040546","featureGroup":"Jalview","svid":"1.0","description":"theDesc","xStart":3,"xEnd":13,"type":"feature_x"},{"fillColor":"#7d1633","score":0,"sequenceRef":"1777084554","featureGroup":"Jalview","svid":"1.0","description":"theDesc","xStart":3,"xEnd":13,"type":"feature_x"}]}
\ No newline at end of file
error.not_implemented = Not implemented
error.no_such_method_as_clone1_for = No such method as clone1 for {0}
error.null_from_clone1 = Null from clone1!
-error.implementation_error_sortbyfeature = Implementation Error - sortByFeature method must be one of FEATURE_SCORE, FEATURE_LABEL or FEATURE_DENSITY.
error.not_yet_implemented = Not yet implemented
error.unknown_type_dna_or_pep = Unknown Type {0} - dna or pep are the only allowed values.
error.implementation_error_dont_know_threshold_annotationcolourgradient = Implementation error: don't know about threshold setting for current AnnotationColourGradient.
error.not_implemented = No implementado
error.no_such_method_as_clone1_for = No existe ese método como un clone1 de {0}
error.null_from_clone1 = Nulo de clone1!
-error.implementation_error_sortbyfeature = Error de implementación - sortByFeature debe ser uno de FEATURE_SCORE, FEATURE_LABEL o FEATURE_DENSITY.
error.not_yet_implemented = No se ha implementado todavÃa
error.unknown_type_dna_or_pep = Tipo desconocido {0} - dna o pep son los únicos valores permitidos
error.implementation_error_dont_know_threshold_annotationcolourgradient = Error de implementación: no se conoce el valor umbral para el AnnotationColourGradient actual.
* The Jalview Authors are detailed in the 'AUTHORS' file.
-->
<mapping>
- <class name="jalview.datamodel.UniprotFile">
+ <class name="jalview.datamodel.xdb.uniprot.UniprotFile">
<map-to xml="uniprot"/>
- <field name="UniprotEntries" type="jalview.datamodel.UniprotEntry" collection="vector">
+ <field name="UniprotEntries" type="jalview.datamodel.xdb.uniprot.UniprotEntry" collection="vector">
<bind-xml name="entry"/>
</field>
</class>
- <class name="jalview.datamodel.UniprotEntry">
+ <class name="jalview.datamodel.xdb.uniprot.UniprotEntry">
<field name="name" type="string" collection="vector"/>
<field name="accession" type="string" collection="vector"/>
- <field name="protein" type="jalview.datamodel.UniprotProteinName"/>
- <field name="UniprotSequence" type="jalview.datamodel.UniprotSequence">
+ <field name="protein" type="jalview.datamodel.xdb.uniprot.UniprotProteinName"/>
+ <field name="UniprotSequence" type="jalview.datamodel.xdb.uniprot.UniprotSequence">
<bind-xml name="sequence"/>
</field>
- <field name="feature" type="jalview.datamodel.SequenceFeature" collection="vector"/>
+ <field name="feature" type="jalview.datamodel.xdb.uniprot.UniprotFeature" collection="vector"/>
<field name="dbReference" type="jalview.datamodel.PDBEntry" collection="vector"/>
</class>
- <class name="jalview.datamodel.UniprotProteinName">
+ <class name="jalview.datamodel.xdb.uniprot.UniprotProteinName">
<field name="name" collection="vector" type="string">
<bind-xml name="fullName" location="recommendedName" node="element"/>
</field>
</class>
<!-- uniprot protein name is now a collection of collections - the INCLUDES and CONTAINS entries of the uniprot
record. This means this doesn't exist anymore...
- <class name="jalview.datamodel.UniprotProteinName">
+ <class name="jalview.datamodel.xdb.uniprot.UniprotProteinName">
<field name="name" type="string" collection="vector">
<bind-xml name="name"/>
</field>
</class>
-->
- <class name="jalview.datamodel.SequenceFeature">
+ <class name="jalview.datamodel.xdb.uniprot.UniprotFeature">
<field name="type">
<bind-xml node="attribute"/>
</field>
</field>
</class>
- <class name="jalview.datamodel.UniprotSequence">
+ <class name="jalview.datamodel.xdb.uniprot.UniprotSequence">
<field name="content" type="string">
<bind-xml name="sequence" node="text"/>
</field>
{
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)
? ""
}
}
}
- return features;
}
/**
&& 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
"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))
{
import jalview.datamodel.SequenceGroup;
import jalview.datamodel.SequenceI;
import jalview.datamodel.SequenceNode;
-import jalview.util.MessageManager;
import jalview.util.QuickSort;
import java.util.ArrayList;
-import java.util.Arrays;
+import java.util.Collections;
+import java.util.Iterator;
import java.util.List;
/**
*/
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;
}
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";
/**
- * 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;
}
}
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;
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;
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);
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
* 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));
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;
}
*
* @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;
}
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)
* 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;
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);
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))
{
*/
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)
* 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)
{
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)
*/
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);
*/
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))
- {
- }
}
}
- }
}
}
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
{
* @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())
{
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()]);
+ }
}
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);
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)
{
/**
* 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());
boolean hasThreshold();
/**
- * Returns the computed colour for the given sequence feature
+ * Returns the computed colour for the given sequence feature. Answers null if
+ * the score of this feature instance is outside the range to render (if any),
+ * i.e. lies below or above a configured threshold.
*
* @param feature
* @return
Color getColor(SequenceFeature feature);
/**
- * 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
- */
- boolean isColored(SequenceFeature feature);
-
- /**
* Update the min-max range for a graduated colour scheme
*
* @param min
*
* @param sequence
* @param column
+ * aligned column position (1..)
* @param g
* @return
*/
void setGroupVisibility(String group, boolean visible);
/**
- * Returns features at the specified position on the given sequence.
+ * 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 sequence
+ * @param column
+ * aligned column position (1..)
+ * @return
+ */
+ List<SequenceFeature> findFeaturesAtColumn(SequenceI sequence, int column);
+
+ /**
+ * Returns features at the specified residue position on the given sequence.
* Non-positional features are not included.
*
* @param sequence
- * @param res
+ * @param resNo
+ * residue position (start..)
* @return
*/
- List<SequenceFeature> findFeaturesAtRes(SequenceI sequence, int res);
+ List<SequenceFeature> findFeaturesAtResidue(SequenceI sequence, int resNo);
/**
* get current displayed types, in ordering of rendering (on top last)
List<String> getDisplayedFeatureTypes();
/**
- * get current displayed groups
+ * Returns a (possibly empty) list of currently visible feature groups
*
- * @return a (possibly empty) list of feature groups
+ * @return
*/
List<String> getDisplayedFeatureGroups();
* @return
*/
float getTransparency();
+
}
package jalview.api;
import java.util.Collection;
-import java.util.Iterator;
+import java.util.Set;
public interface FeaturesDisplayedI
{
- Iterator<String> getVisibleFeatures();
+ /**
+ * answers an unmodifiable view of the set of visible feature types
+ */
+ Set<String> getVisibleFeatures();
boolean isVisible(String featureType);
void setVisible(String featureType);
+ /**
+ * Sets all the specified feature types to visible. Visibility of other
+ * feature types is not changed.
+ *
+ * @param featureTypes
+ */
void setAllVisible(Collection<String> featureTypes);
boolean isRegistered(String type);
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
}
}
}
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)
{
EditCommand editCommand = (EditCommand) command;
al = editCommand.getAlignment();
- Vector comps = (Vector) PaintRefresher.components
+ Vector comps = PaintRefresher.components
.get(viewport.getSequenceSetId());
for (int i = 0; i < comps.size(); i++)
{
PaintRefresher.Register(newaf.alignPanel.seqPanel.seqCanvas,
newaf.alignPanel.av.getSequenceSetId());
- Vector comps = (Vector) PaintRefresher.components
+ Vector comps = PaintRefresher.components
.get(viewport.getSequenceSetId());
int viewSize = -1;
for (int i = 0; i < comps.size(); i++)
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;
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));
{
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)
{
/*
* 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)
{
- sf.type = enteredType;
- sf.featureGroup = group.getText().trim();
- if (sf.featureGroup != null && sf.featureGroup.length() < 1)
- {
- sf.featureGroup = null;
- }
- sf.description = description.getText().replace('\n', ' ');
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();
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;
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,
// 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(
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"));
}
*/
package jalview.appletgui;
-import jalview.datamodel.Sequence;
import jalview.datamodel.SequenceFeature;
import jalview.datamodel.SequenceGroup;
import jalview.datamodel.SequenceI;
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)
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 ((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);
}
}
}
boolean up = true;
- public ScrollThread(boolean up)
+ public ScrollThread(boolean isUp)
{
- this.up = up;
+ this.up = isUp;
start();
}
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,
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;
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,
}
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())
{
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();
{
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)
*/
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)
+ List<SequenceFeature> findFeaturesAtColumn(SequenceI sequence, int column)
{
- // 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> 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;
return;
}
- int res = findRes(evt);
+ int res = findColumn(evt);
if (res < 0)
{
// 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;
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
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)
{
}
- 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));
{
// 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);
}
}
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.
}
}
- 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()
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.findPosition(sqcol.getStartRes());
+ List<SequenceFeature> sfs = sq.findFeatures(startColumn,
+ endColumn, featureType);
+
+ if (!sfs.isEmpty())
{
- 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)
+ nseq++;
+ }
+
+ for (SequenceFeature sf : sfs)
+ {
+ int sfStartCol = sq.findIndex(sf.getBegin());
+ int sfEndCol = sq.findIndex(sf.getEnd());
+
+ if (sf.isContactFeature())
{
- // future functionality - featureType == null means mark columns
- // containing all displayed features
- if (sf != null && (featureType.equals(sf.getType())))
+ /*
+ * '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)
{
- // optimisation - could consider 'spos,apos' like cursor argument
- // - findIndex wastes time by starting from first character and
- // counting
-
- int sfStartCol = sq.findIndex(sf.getBegin());
- int sfEndCol = sq.findIndex(sf.getEnd());
-
- 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;
- }
-
- /*
- * 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
- }
+ 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
+ }
}
}
}
* 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;
{
// 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
{
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);
}
* 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.
*
int nores = (isNa) ? ResidueProperties.maxNucleotideIndex
: ResidueProperties.maxProteinIndex;
- dbinary = new double[getSequence().length * nores];
+ dbinary = new double[getLength() * nores];
return nores;
}
{
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;
{
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;
--- /dev/null
+package jalview.datamodel;
+
+public interface ContiguousI
+{
+ int getBegin(); // todo want long for genomic positions?
+
+ int getEnd();
+}
/*
* 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);
}
* 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 };
}
--- /dev/null
+package jalview.datamodel;
+
+/**
+ * An immutable data bean that models a start-end range
+ */
+public class Range implements ContiguousI
+{
+ public final int start;
+
+ public final int end;
+
+ @Override
+ public int getBegin()
+ {
+ return start;
+ }
+
+ @Override
+ public int getEnd()
+ {
+ return end;
+ }
+
+ public Range(int i, int j)
+ {
+ start = i;
+ end = j;
+ }
+
+ @Override
+ public String toString()
+ {
+ return String.valueOf(start) + "-" + String.valueOf(end);
+ }
+
+ @Override
+ public int hashCode()
+ {
+ return start * 31 + end;
+ }
+
+ @Override
+ public boolean equals(Object obj)
+ {
+ if (obj instanceof Range)
+ {
+ Range r = (Range) obj;
+ return (start == r.start && end == r.end);
+ }
+ return false;
+ }
+}
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
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)
SearchResultsI sr = (SearchResultsI) obj;
return matches.equals(sr.getResults());
}
+
+ @Override
+ public void addSearchResults(SearchResultsI toAdd)
+ {
+ matches.addAll(toAdd.getResults());
+ }
}
SearchResultMatchI addResult(SequenceI seq, int start, int end);
/**
+ * adds all match results in the argument to this set
+ *
+ * @param toAdd
+ */
+ void addSearchResults(SearchResultsI toAdd);
+
+ /**
* Answers true if the search results include the given sequence (or its
* dataset sequence), else false
*
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;
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)
}
/**
+ * 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
private boolean _isNa;
- private long _seqhash = 0;
+ private int _seqhash = 0;
/**
* Answers false if the sequence is more than 85% nucleotide (ACGTU), else
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(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);
}
}
}
}
+ /**
+ * {@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;
+ }
}
--- /dev/null
+package jalview.datamodel;
+
+/**
+ * An immutable object representing one or more residue and corresponding
+ * alignment column positions for a sequence
+ */
+public class SequenceCursor
+{
+ /**
+ * the aligned sequence this cursor applies to
+ */
+ public final SequenceI sequence;
+
+ /**
+ * residue position in sequence (start...), 0 if undefined
+ */
+ public final int residuePosition;
+
+ /**
+ * column position (1...) corresponding to residuePosition, or 0 if undefined
+ */
+ public final int columnPosition;
+
+ /**
+ * column position (1...) of first residue in the sequence, or 0 if undefined
+ */
+ public final int firstColumnPosition;
+
+ /**
+ * column position (1...) of last residue in the sequence, or 0 if undefined
+ */
+ public final int lastColumnPosition;
+
+ /**
+ * a token which may be used to check whether this cursor is still valid for
+ * its sequence (allowing it to be ignored if the sequence has changed)
+ */
+ public final int token;
+
+ /**
+ * Constructor
+ *
+ * @param seq
+ * sequence this cursor applies to
+ * @param resPos
+ * residue position in sequence (start..)
+ * @param column
+ * column position in alignment (1..)
+ * @param tok
+ * a token that may be validated by the sequence to check the cursor
+ * is not stale
+ */
+ public SequenceCursor(SequenceI seq, int resPos, int column, int tok)
+ {
+ this(seq, resPos, column, 0, 0, tok);
+ }
+
+ /**
+ * Constructor
+ *
+ * @param seq
+ * sequence this cursor applies to
+ * @param resPos
+ * residue position in sequence (start..)
+ * @param column
+ * column position in alignment (1..)
+ * @param firstResCol
+ * column position of the first residue in the sequence (1..), or 0
+ * if not known
+ * @param lastResCol
+ * column position of the last residue in the sequence (1..), or 0 if
+ * not known
+ * @param tok
+ * a token that may be validated by the sequence to check the cursor
+ * is not stale
+ */
+ public SequenceCursor(SequenceI seq, int resPos, int column, int firstResCol,
+ int lastResCol, int tok)
+ {
+ sequence = seq;
+ residuePosition = resPos;
+ columnPosition = column;
+ firstColumnPosition = firstResCol;
+ lastColumnPosition = lastResCol;
+ token = tok;
+ }
+
+ @Override
+ public int hashCode()
+ {
+ int hash = 31 * residuePosition;
+ hash = 31 * hash + columnPosition;
+ hash = 31 * hash + token;
+ if (sequence != null)
+ {
+ hash += sequence.hashCode();
+ }
+ return hash;
+ }
+
+ /**
+ * Two cursors are equal if they refer to the same sequence object and have
+ * the same residue position, column position and token value
+ */
+ @Override
+ public boolean equals(Object obj)
+ {
+ if (!(obj instanceof SequenceCursor))
+ {
+ return false;
+ }
+ SequenceCursor sc = (SequenceCursor) obj;
+ return sequence == sc.sequence && residuePosition == sc.residuePosition
+ && columnPosition == sc.columnPosition && token == sc.token;
+ }
+
+ @Override
+ public String toString()
+ {
+ String name = sequence == null ? "" : sequence.getName();
+ return String.format("%s:Pos%d:Col%d:startCol%d:endCol%d:tok%d", name,
+ residuePosition, columnPosition, firstColumnPosition,
+ lastColumnPosition, token);
+ }
+}
*/
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 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;
}
}
*/
package jalview.datamodel;
+import jalview.datamodel.features.SequenceFeaturesI;
+
import java.util.BitSet;
import java.util.List;
import java.util.Vector;
public String getSequenceAsString(int start, int end);
/**
- * Get the sequence as a character array
+ * Answers a copy of the sequence as a character array
*
- * @return seqeunce and any gaps
+ * @return
*/
public char[] getSequence();
public String getDescription();
/**
- * Return the alignment column for a sequence position
+ * Return the alignment column (from 1..) for a sequence position
*
* @param pos
* lying from start to end
public int findPosition(int i);
/**
+ * Returns the from-to sequence positions (start..) for the given column
+ * positions (1..), or null if no residues are included in the range
+ *
+ * @param fromColum
+ * @param toColumn
+ * @return
+ */
+ public Range findPositions(int fromColum, int toColumn);
+
+ /**
* Returns an int array where indices correspond to each residue in the
* sequence and the element value gives its position in the alignment
*
public void insertCharAt(int position, int count, char ch);
/**
- * Gets array holding sequence features associated with this sequence. The
- * array may be held by the sequence's dataset sequence if that is defined.
+ * Answers a list of all sequence features associated with this sequence. The
+ * list may be held by the sequence's dataset sequence if that is defined.
+ *
+ * @return
+ */
+ public List<SequenceFeature> getSequenceFeatures();
+
+ /**
+ * Answers the object holding features for the sequence
*
- * @return hard reference to array
+ * @return
*/
- public SequenceFeature[] getSequenceFeatures();
+ SequenceFeaturesI getFeatures();
/**
- * Replaces the array of sequence features associated with this sequence with
- * a new array reference. If this sequence has a dataset sequence, then this
- * method will update the dataset sequence's feature array
+ * Replaces the sequence features associated with this sequence with the given
+ * features. If this sequence has a dataset sequence, then this method will
+ * update the dataset sequence's features instead.
*
* @param features
- * New array of sequence features
*/
- public void setSequenceFeatures(SequenceFeature[] features);
+ public void setSequenceFeatures(List<SequenceFeature> features);
/**
* DOCUMENT ME!
/**
* Adds the given sequence feature and returns true, or returns false if it is
- * already present on the sequence
+ * already present on the sequence, or if the feature type is null.
*
* @param sf
* @return
public List<DBRefEntry> getPrimaryDBRefs();
/**
+ * Returns a (possibly empty) list of sequence features that overlap the given
+ * alignment column range, optionally restricted to one or more specified
+ * feature types. If the range is all gaps, then features which enclose it are
+ * included (but not contact features).
+ *
+ * @param fromCol
+ * start column of range inclusive (1..)
+ * @param toCol
+ * end column of range inclusive (1..)
+ * @param types
+ * optional feature types to restrict results to
+ * @return
+ */
+ List<SequenceFeature> findFeatures(int fromCol, int toCol, String... types);
+
+ /**
+ * Method to call to indicate that the sequence (characters or alignment/gaps)
+ * has been modified. Provided to allow any cursors on residue/column
+ * positions to be invalidated.
+ */
+ void sequenceChanged();
+
+ /**
*
* @return BitSet corresponding to index [0,length) where Comparison.isGap()
* returns true.
*/
BitSet getInsertionsAsBits();
+
+ /**
+ * Replaces every occurrence of c1 in the sequence with c2 and returns the
+ * number of characters changed
+ *
+ * @param c1
+ * @param c2
+ */
+ public int replace(char c1, char c2);
}
--- /dev/null
+package jalview.datamodel.features;
+
+import jalview.datamodel.ContiguousI;
+
+/**
+ * An extension of ContiguousI that allows start/end values to be interpreted
+ * instead as two contact positions
+ */
+public interface FeatureLocationI extends ContiguousI
+{
+ boolean isContactFeature();
+}
--- /dev/null
+package jalview.datamodel.features;
+
+import jalview.datamodel.ContiguousI;
+import jalview.datamodel.SequenceFeature;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * A data store for a set of sequence features that supports efficient lookup of
+ * features overlapping a given range. Intended for (but not limited to) storage
+ * of features for one sequence and feature type.
+ *
+ * @author gmcarstairs
+ *
+ */
+public class FeatureStore
+{
+ /**
+ * a class providing criteria for performing a binary search of a list
+ */
+ abstract static class SearchCriterion
+ {
+ /**
+ * Answers true if the entry passes the search criterion test
+ *
+ * @param entry
+ * @return
+ */
+ abstract boolean compare(SequenceFeature entry);
+
+ /**
+ * serves a search condition for finding the first feature whose start
+ * position follows a given target location
+ *
+ * @param target
+ * @return
+ */
+ static SearchCriterion byStart(final long target)
+ {
+ return new SearchCriterion() {
+
+ @Override
+ boolean compare(SequenceFeature entry)
+ {
+ return entry.getBegin() >= target;
+ }
+ };
+ }
+
+ /**
+ * serves a search condition for finding the first feature whose end
+ * position is at or follows a given target location
+ *
+ * @param target
+ * @return
+ */
+ static SearchCriterion byEnd(final long target)
+ {
+ return new SearchCriterion()
+ {
+
+ @Override
+ boolean compare(SequenceFeature entry)
+ {
+ return entry.getEnd() >= target;
+ }
+ };
+ }
+
+ /**
+ * serves a search condition for finding the first feature which follows the
+ * given range as determined by a supplied comparator
+ *
+ * @param target
+ * @return
+ */
+ static SearchCriterion byFeature(final ContiguousI to,
+ final Comparator<ContiguousI> rc)
+ {
+ return new SearchCriterion()
+ {
+
+ @Override
+ boolean compare(SequenceFeature entry)
+ {
+ return rc.compare(entry, to) >= 0;
+ }
+ };
+ }
+ }
+
+ /*
+ * Non-positional features have no (zero) start/end position.
+ * Kept as a separate list in case this criterion changes in future.
+ */
+ List<SequenceFeature> nonPositionalFeatures;
+
+ /*
+ * An ordered list of features, with the promise that no feature in the list
+ * properly contains any other. This constraint allows bounded linear search
+ * of the list for features overlapping a region.
+ * Contact features are not included in this list.
+ */
+ List<SequenceFeature> nonNestedFeatures;
+
+ /*
+ * contact features ordered by first contact position
+ */
+ List<SequenceFeature> contactFeatureStarts;
+
+ /*
+ * contact features ordered by second contact position
+ */
+ List<SequenceFeature> contactFeatureEnds;
+
+ /*
+ * Nested Containment List is used to hold any features that are nested
+ * within (properly contained by) any other feature. This is a recursive tree
+ * which supports depth-first scan for features overlapping a range.
+ * It is used here as a 'catch-all' fallback for features that cannot be put
+ * into a simple ordered list without invalidating the search methods.
+ */
+ NCList<SequenceFeature> nestedFeatures;
+
+ /*
+ * Feature groups represented in stored positional features
+ * (possibly including null)
+ */
+ Set<String> positionalFeatureGroups;
+
+ /*
+ * Feature groups represented in stored non-positional features
+ * (possibly including null)
+ */
+ Set<String> nonPositionalFeatureGroups;
+
+ /*
+ * the total length of all positional features; contact features count 1 to
+ * the total and 1 to size(), consistent with an average 'feature length' of 1
+ */
+ int totalExtent;
+
+ float positionalMinScore;
+
+ float positionalMaxScore;
+
+ float nonPositionalMinScore;
+
+ float nonPositionalMaxScore;
+
+ /**
+ * Constructor
+ */
+ public FeatureStore()
+ {
+ nonNestedFeatures = new ArrayList<SequenceFeature>();
+ positionalFeatureGroups = new HashSet<String>();
+ nonPositionalFeatureGroups = new HashSet<String>();
+ positionalMinScore = Float.NaN;
+ positionalMaxScore = Float.NaN;
+ nonPositionalMinScore = Float.NaN;
+ nonPositionalMaxScore = Float.NaN;
+
+ // we only construct nonPositionalFeatures, contactFeatures
+ // or the NCList if we need to
+ }
+
+ /**
+ * Adds one sequence feature to the store, and returns true, unless the
+ * feature is already contained in the store, in which case this method
+ * returns false. Containment is determined by SequenceFeature.equals()
+ * comparison.
+ *
+ * @param feature
+ */
+ public boolean addFeature(SequenceFeature feature)
+ {
+ if (contains(feature))
+ {
+ return false;
+ }
+
+ /*
+ * keep a record of feature groups
+ */
+ if (!feature.isNonPositional())
+ {
+ positionalFeatureGroups.add(feature.getFeatureGroup());
+ }
+
+ boolean added = false;
+
+ if (feature.isContactFeature())
+ {
+ added = addContactFeature(feature);
+ }
+ else if (feature.isNonPositional())
+ {
+ added = addNonPositionalFeature(feature);
+ }
+ else
+ {
+ added = addNonNestedFeature(feature);
+ if (!added)
+ {
+ /*
+ * detected a nested feature - put it in the NCList structure
+ */
+ added = addNestedFeature(feature);
+ }
+ }
+
+ if (added)
+ {
+ /*
+ * record the total extent of positional features, to make
+ * getTotalFeatureLength possible; we count the length of a
+ * contact feature as 1
+ */
+ totalExtent += getFeatureLength(feature);
+
+ /*
+ * record the minimum and maximum score for positional
+ * and non-positional features
+ */
+ float score = feature.getScore();
+ if (!Float.isNaN(score))
+ {
+ if (feature.isNonPositional())
+ {
+ nonPositionalMinScore = min(nonPositionalMinScore, score);
+ nonPositionalMaxScore = max(nonPositionalMaxScore, score);
+ }
+ else
+ {
+ positionalMinScore = min(positionalMinScore, score);
+ positionalMaxScore = max(positionalMaxScore, score);
+ }
+ }
+ }
+
+ return added;
+ }
+
+ /**
+ * Answers true if this store contains the given feature (testing by
+ * SequenceFeature.equals), else false
+ *
+ * @param feature
+ * @return
+ */
+ public boolean contains(SequenceFeature feature)
+ {
+ if (feature.isNonPositional())
+ {
+ return nonPositionalFeatures == null ? false : nonPositionalFeatures
+ .contains(feature);
+ }
+
+ if (feature.isContactFeature())
+ {
+ return contactFeatureStarts == null ? false : listContains(
+ contactFeatureStarts, feature);
+ }
+
+ if (listContains(nonNestedFeatures, feature))
+ {
+ return true;
+ }
+
+ return nestedFeatures == null ? false : nestedFeatures
+ .contains(feature);
+ }
+
+ /**
+ * Answers the 'length' of the feature, counting 0 for non-positional features
+ * and 1 for contact features
+ *
+ * @param feature
+ * @return
+ */
+ protected static int getFeatureLength(SequenceFeature feature)
+ {
+ if (feature.isNonPositional())
+ {
+ return 0;
+ }
+ if (feature.isContactFeature())
+ {
+ return 1;
+ }
+ return 1 + feature.getEnd() - feature.getBegin();
+ }
+
+ /**
+ * Adds the feature to the list of non-positional features (with lazy
+ * instantiation of the list if it is null), and returns true. The feature
+ * group is added to the set of distinct feature groups for non-positional
+ * features. This method allows duplicate features, so test before calling to
+ * prevent this.
+ *
+ * @param feature
+ */
+ protected boolean addNonPositionalFeature(SequenceFeature feature)
+ {
+ if (nonPositionalFeatures == null)
+ {
+ nonPositionalFeatures = new ArrayList<SequenceFeature>();
+ }
+
+ nonPositionalFeatures.add(feature);
+
+ nonPositionalFeatureGroups.add(feature.getFeatureGroup());
+
+ return true;
+ }
+
+ /**
+ * Adds one feature to the NCList that can manage nested features (creating
+ * the NCList if necessary), and returns true. If the feature is already
+ * stored in the NCList (by equality test), then it is not added, and this
+ * method returns false.
+ */
+ protected synchronized boolean addNestedFeature(SequenceFeature feature)
+ {
+ if (nestedFeatures == null)
+ {
+ nestedFeatures = new NCList<>(feature);
+ return true;
+ }
+ return nestedFeatures.add(feature, false);
+ }
+
+ /**
+ * Add a feature to the list of non-nested features, maintaining the ordering
+ * of the list. A check is made for whether the feature is nested in (properly
+ * contained by) an existing feature. If there is no nesting, the feature is
+ * added to the list and the method returns true. If nesting is found, the
+ * feature is not added and the method returns false.
+ *
+ * @param feature
+ * @return
+ */
+ protected boolean addNonNestedFeature(SequenceFeature feature)
+ {
+ synchronized (nonNestedFeatures)
+ {
+ /*
+ * find the first stored feature which doesn't precede the new one
+ */
+ int insertPosition = binarySearch(nonNestedFeatures,
+ SearchCriterion.byFeature(feature, RangeComparator.BY_START_POSITION));
+
+ /*
+ * fail if we detect feature enclosure - of the new feature by
+ * the one preceding it, or of the next feature by the new one
+ */
+ if (insertPosition > 0)
+ {
+ if (encloses(nonNestedFeatures.get(insertPosition - 1), feature))
+ {
+ return false;
+ }
+ }
+ if (insertPosition < nonNestedFeatures.size())
+ {
+ if (encloses(feature, nonNestedFeatures.get(insertPosition)))
+ {
+ return false;
+ }
+ }
+
+ /*
+ * checks passed - add the feature
+ */
+ nonNestedFeatures.add(insertPosition, feature);
+
+ return true;
+ }
+ }
+
+ /**
+ * Answers true if range1 properly encloses range2, else false
+ *
+ * @param range1
+ * @param range2
+ * @return
+ */
+ protected boolean encloses(ContiguousI range1, ContiguousI range2)
+ {
+ int begin1 = range1.getBegin();
+ int begin2 = range2.getBegin();
+ int end1 = range1.getEnd();
+ int end2 = range2.getEnd();
+ if (begin1 == begin2 && end1 > end2)
+ {
+ return true;
+ }
+ if (begin1 < begin2 && end1 >= end2)
+ {
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Add a contact feature to the lists that hold them ordered by start (first
+ * contact) and by end (second contact) position, ensuring the lists remain
+ * ordered, and returns true. This method allows duplicate features to be
+ * added, so test before calling to avoid this.
+ *
+ * @param feature
+ * @return
+ */
+ protected synchronized boolean addContactFeature(SequenceFeature feature)
+ {
+ if (contactFeatureStarts == null)
+ {
+ contactFeatureStarts = new ArrayList<SequenceFeature>();
+ }
+ if (contactFeatureEnds == null)
+ {
+ contactFeatureEnds = new ArrayList<SequenceFeature>();
+ }
+
+ /*
+ * binary search the sorted list to find the insertion point
+ */
+ int insertPosition = binarySearch(contactFeatureStarts,
+ SearchCriterion.byFeature(feature,
+ RangeComparator.BY_START_POSITION));
+ contactFeatureStarts.add(insertPosition, feature);
+ // and resort to mak siccar...just in case insertion point not quite right
+ Collections.sort(contactFeatureStarts, RangeComparator.BY_START_POSITION);
+
+ insertPosition = binarySearch(contactFeatureStarts,
+ SearchCriterion.byFeature(feature,
+ RangeComparator.BY_END_POSITION));
+ contactFeatureEnds.add(feature);
+ Collections.sort(contactFeatureEnds, RangeComparator.BY_END_POSITION);
+
+ return true;
+ }
+
+ /**
+ * Answers true if the list contains the feature, else false. This method is
+ * optimised for the condition that the list is sorted on feature start
+ * position ascending, and will give unreliable results if this does not hold.
+ *
+ * @param features
+ * @param feature
+ * @return
+ */
+ protected static boolean listContains(List<SequenceFeature> features,
+ SequenceFeature feature)
+ {
+ if (features == null || feature == null)
+ {
+ return false;
+ }
+
+ /*
+ * locate the first entry in the list which does not precede the feature
+ */
+ int pos = binarySearch(features,
+ SearchCriterion.byFeature(feature, RangeComparator.BY_START_POSITION));
+ int len = features.size();
+ while (pos < len)
+ {
+ SequenceFeature sf = features.get(pos);
+ if (sf.getBegin() > feature.getBegin())
+ {
+ return false; // no match found
+ }
+ if (sf.equals(feature))
+ {
+ return true;
+ }
+ pos++;
+ }
+ return false;
+ }
+
+ /**
+ * Returns a (possibly empty) list of features whose extent overlaps the given
+ * range. The returned list is not ordered. Contact features are included if
+ * either of the contact points lies within the range.
+ *
+ * @param start
+ * start position of overlap range (inclusive)
+ * @param end
+ * end position of overlap range (inclusive)
+ * @return
+ */
+ public List<SequenceFeature> findOverlappingFeatures(long start, long end)
+ {
+ List<SequenceFeature> result = new ArrayList<>();
+
+ findNonNestedFeatures(start, end, result);
+
+ findContactFeatures(start, end, result);
+
+ if (nestedFeatures != null)
+ {
+ result.addAll(nestedFeatures.findOverlaps(start, end));
+ }
+
+ return result;
+ }
+
+ /**
+ * Adds contact features to the result list where either the second or the
+ * first contact position lies within the target range
+ *
+ * @param from
+ * @param to
+ * @param result
+ */
+ protected void findContactFeatures(long from, long to,
+ List<SequenceFeature> result)
+ {
+ if (contactFeatureStarts != null)
+ {
+ findContactStartFeatures(from, to, result);
+ }
+ if (contactFeatureEnds != null)
+ {
+ findContactEndFeatures(from, to, result);
+ }
+ }
+
+ /**
+ * Adds to the result list any contact features whose end (second contact
+ * point), but not start (first contact point), lies in the query from-to
+ * range
+ *
+ * @param from
+ * @param to
+ * @param result
+ */
+ protected void findContactEndFeatures(long from, long to,
+ List<SequenceFeature> result)
+ {
+ /*
+ * find the first contact feature (if any) that does not lie
+ * entirely before the target range
+ */
+ int startPosition = binarySearch(contactFeatureEnds,
+ SearchCriterion.byEnd(from));
+ for (; startPosition < contactFeatureEnds.size(); startPosition++)
+ {
+ SequenceFeature sf = contactFeatureEnds.get(startPosition);
+ if (!sf.isContactFeature())
+ {
+ System.err.println("Error! non-contact feature type "
+ + sf.getType() + " in contact features list");
+ continue;
+ }
+
+ int begin = sf.getBegin();
+ if (begin >= from && begin <= to)
+ {
+ /*
+ * this feature's first contact position lies in the search range
+ * so we don't include it in results a second time
+ */
+ continue;
+ }
+
+ int end = sf.getEnd();
+ if (end >= from && end <= to)
+ {
+ result.add(sf);
+ }
+ if (end > to)
+ {
+ break;
+ }
+ }
+ }
+
+ /**
+ * Adds non-nested features to the result list that lie within the target
+ * range. Non-positional features (start=end=0), contact features and nested
+ * features are excluded.
+ *
+ * @param from
+ * @param to
+ * @param result
+ */
+ protected void findNonNestedFeatures(long from, long to,
+ List<SequenceFeature> result)
+ {
+ /*
+ * find the first feature whose end position is
+ * after the target range start
+ */
+ int startIndex = binarySearch(nonNestedFeatures,
+ SearchCriterion.byEnd(from));
+
+ final int startIndex1 = startIndex;
+ int i = startIndex1;
+ while (i < nonNestedFeatures.size())
+ {
+ SequenceFeature sf = nonNestedFeatures.get(i);
+ if (sf.getBegin() > to)
+ {
+ break;
+ }
+ if (sf.getBegin() <= to && sf.getEnd() >= from)
+ {
+ result.add(sf);
+ }
+ i++;
+ }
+ }
+
+ /**
+ * Adds contact features whose start position lies in the from-to range to the
+ * result list
+ *
+ * @param from
+ * @param to
+ * @param result
+ */
+ protected void findContactStartFeatures(long from, long to,
+ List<SequenceFeature> result)
+ {
+ int startPosition = binarySearch(contactFeatureStarts,
+ SearchCriterion.byStart(from));
+
+ for (; startPosition < contactFeatureStarts.size(); startPosition++)
+ {
+ SequenceFeature sf = contactFeatureStarts.get(startPosition);
+ if (!sf.isContactFeature())
+ {
+ System.err.println("Error! non-contact feature type "
+ + sf.getType() + " in contact features list");
+ continue;
+ }
+ int begin = sf.getBegin();
+ if (begin >= from && begin <= to)
+ {
+ result.add(sf);
+ }
+ }
+ }
+
+ /**
+ * Answers a list of all positional features stored, in no guaranteed order
+ *
+ * @return
+ */
+ public List<SequenceFeature> getPositionalFeatures()
+ {
+ /*
+ * add non-nested features (may be all features for many cases)
+ */
+ List<SequenceFeature> result = new ArrayList<>();
+ result.addAll(nonNestedFeatures);
+
+ /*
+ * add any contact features - from the list by start position
+ */
+ if (contactFeatureStarts != null)
+ {
+ result.addAll(contactFeatureStarts);
+ }
+
+ /*
+ * add any nested features
+ */
+ if (nestedFeatures != null)
+ {
+ result.addAll(nestedFeatures.getEntries());
+ }
+
+ return result;
+ }
+
+ /**
+ * Answers a list of all contact features. If there are none, returns an
+ * immutable empty list.
+ *
+ * @return
+ */
+ public List<SequenceFeature> getContactFeatures()
+ {
+ if (contactFeatureStarts == null)
+ {
+ return Collections.emptyList();
+ }
+ return new ArrayList<>(contactFeatureStarts);
+ }
+
+ /**
+ * Answers a list of all non-positional features. If there are none, returns
+ * an immutable empty list.
+ *
+ * @return
+ */
+ public List<SequenceFeature> getNonPositionalFeatures()
+ {
+ if (nonPositionalFeatures == null)
+ {
+ return Collections.emptyList();
+ }
+ return new ArrayList<>(nonPositionalFeatures);
+ }
+
+ /**
+ * Deletes the given feature from the store, returning true if it was found
+ * (and deleted), else false. This method makes no assumption that the feature
+ * is in the 'expected' place in the store, in case it has been modified since
+ * it was added.
+ *
+ * @param sf
+ */
+ public synchronized boolean delete(SequenceFeature sf)
+ {
+ /*
+ * try the non-nested positional features first
+ */
+ boolean removed = nonNestedFeatures.remove(sf);
+
+ /*
+ * if not found, try contact positions (and if found, delete
+ * from both lists of contact positions)
+ */
+ if (!removed && contactFeatureStarts != null)
+ {
+ removed = contactFeatureStarts.remove(sf);
+ if (removed)
+ {
+ contactFeatureEnds.remove(sf);
+ }
+ }
+
+ boolean removedNonPositional = false;
+
+ /*
+ * if not found, try non-positional features
+ */
+ if (!removed && nonPositionalFeatures != null)
+ {
+ removedNonPositional = nonPositionalFeatures.remove(sf);
+ removed = removedNonPositional;
+ }
+
+ /*
+ * if not found, try nested features
+ */
+ if (!removed && nestedFeatures != null)
+ {
+ removed = nestedFeatures.delete(sf);
+ }
+
+ if (removed)
+ {
+ rescanAfterDelete();
+ }
+
+ return removed;
+ }
+
+ /**
+ * Rescan all features to recompute any cached values after an entry has been
+ * deleted. This is expected to be an infrequent event, so performance here is
+ * not critical.
+ */
+ protected synchronized void rescanAfterDelete()
+ {
+ positionalFeatureGroups.clear();
+ nonPositionalFeatureGroups.clear();
+ totalExtent = 0;
+ positionalMinScore = Float.NaN;
+ positionalMaxScore = Float.NaN;
+ nonPositionalMinScore = Float.NaN;
+ nonPositionalMaxScore = Float.NaN;
+
+ /*
+ * scan non-positional features for groups and scores
+ */
+ for (SequenceFeature sf : getNonPositionalFeatures())
+ {
+ nonPositionalFeatureGroups.add(sf.getFeatureGroup());
+ float score = sf.getScore();
+ nonPositionalMinScore = min(nonPositionalMinScore, score);
+ nonPositionalMaxScore = max(nonPositionalMaxScore, score);
+ }
+
+ /*
+ * scan positional features for groups, scores and extents
+ */
+ for (SequenceFeature sf : getPositionalFeatures())
+ {
+ positionalFeatureGroups.add(sf.getFeatureGroup());
+ float score = sf.getScore();
+ positionalMinScore = min(positionalMinScore, score);
+ positionalMaxScore = max(positionalMaxScore, score);
+ totalExtent += getFeatureLength(sf);
+ }
+ }
+
+ /**
+ * A helper method to return the minimum of two floats, where a non-NaN value
+ * is treated as 'less than' a NaN value (unlike Math.min which does the
+ * opposite)
+ *
+ * @param f1
+ * @param f2
+ */
+ protected static float min(float f1, float f2)
+ {
+ if (Float.isNaN(f1))
+ {
+ return Float.isNaN(f2) ? f1 : f2;
+ }
+ else
+ {
+ return Float.isNaN(f2) ? f1 : Math.min(f1, f2);
+ }
+ }
+
+ /**
+ * A helper method to return the maximum of two floats, where a non-NaN value
+ * is treated as 'greater than' a NaN value (unlike Math.max which does the
+ * opposite)
+ *
+ * @param f1
+ * @param f2
+ */
+ protected static float max(float f1, float f2)
+ {
+ if (Float.isNaN(f1))
+ {
+ return Float.isNaN(f2) ? f1 : f2;
+ }
+ else
+ {
+ return Float.isNaN(f2) ? f1 : Math.max(f1, f2);
+ }
+ }
+
+ /**
+ * Answers true if this store has no features, else false
+ *
+ * @return
+ */
+ public boolean isEmpty()
+ {
+ boolean hasFeatures = !nonNestedFeatures.isEmpty()
+ || (contactFeatureStarts != null && !contactFeatureStarts
+ .isEmpty())
+ || (nonPositionalFeatures != null && !nonPositionalFeatures
+ .isEmpty())
+ || (nestedFeatures != null && nestedFeatures.size() > 0);
+
+ return !hasFeatures;
+ }
+
+ /**
+ * Answers the set of distinct feature groups stored, possibly including null,
+ * as an unmodifiable view of the set. The parameter determines whether the
+ * groups for positional or for non-positional features are returned.
+ *
+ * @param positionalFeatures
+ * @return
+ */
+ public Set<String> getFeatureGroups(boolean positionalFeatures)
+ {
+ if (positionalFeatures)
+ {
+ return Collections.unmodifiableSet(positionalFeatureGroups);
+ }
+ else
+ {
+ return nonPositionalFeatureGroups == null ? Collections
+ .<String> emptySet() : Collections
+ .unmodifiableSet(nonPositionalFeatureGroups);
+ }
+ }
+
+ /**
+ * Performs a binary search of the (sorted) list to find the index of the
+ * first entry which returns true for the given comparator function. Returns
+ * the length of the list if there is no such entry.
+ *
+ * @param features
+ * @param sc
+ * @return
+ */
+ protected static int binarySearch(List<SequenceFeature> features,
+ SearchCriterion sc)
+ {
+ int start = 0;
+ int end = features.size() - 1;
+ int matched = features.size();
+
+ while (start <= end)
+ {
+ int mid = (start + end) / 2;
+ SequenceFeature entry = features.get(mid);
+ boolean compare = sc.compare(entry);
+ if (compare)
+ {
+ matched = mid;
+ end = mid - 1;
+ }
+ else
+ {
+ start = mid + 1;
+ }
+ }
+
+ return matched;
+ }
+
+ /**
+ * Answers the number of positional (or non-positional) features stored.
+ * Contact features count as 1.
+ *
+ * @param positional
+ * @return
+ */
+ public int getFeatureCount(boolean positional)
+ {
+ if (!positional)
+ {
+ return nonPositionalFeatures == null ? 0 : nonPositionalFeatures
+ .size();
+ }
+
+ int size = nonNestedFeatures.size();
+
+ if (contactFeatureStarts != null)
+ {
+ // note a contact feature (start/end) counts as one
+ size += contactFeatureStarts.size();
+ }
+
+ if (nestedFeatures != null)
+ {
+ size += nestedFeatures.size();
+ }
+
+ return size;
+ }
+
+ /**
+ * Answers the total length of positional features (or zero if there are
+ * none). Contact features contribute a value of 1 to the total.
+ *
+ * @return
+ */
+ public int getTotalFeatureLength()
+ {
+ return totalExtent;
+ }
+
+ /**
+ * Answers the minimum score held for positional or non-positional features.
+ * This may be Float.NaN if there are no features, are none has a non-NaN
+ * score.
+ *
+ * @param positional
+ * @return
+ */
+ public float getMinimumScore(boolean positional)
+ {
+ return positional ? positionalMinScore : nonPositionalMinScore;
+ }
+
+ /**
+ * Answers the maximum score held for positional or non-positional features.
+ * This may be Float.NaN if there are no features, are none has a non-NaN
+ * score.
+ *
+ * @param positional
+ * @return
+ */
+ public float getMaximumScore(boolean positional)
+ {
+ return positional ? positionalMaxScore : nonPositionalMaxScore;
+ }
+
+ /**
+ * Answers a list of all either positional or non-positional features whose
+ * feature group matches the given group (which may be null)
+ *
+ * @param positional
+ * @param group
+ * @return
+ */
+ public List<SequenceFeature> getFeaturesForGroup(boolean positional,
+ String group)
+ {
+ List<SequenceFeature> result = new ArrayList<>();
+
+ /*
+ * if we know features don't include the target group, no need
+ * to inspect them for matches
+ */
+ if (positional && !positionalFeatureGroups.contains(group)
+ || !positional && !nonPositionalFeatureGroups.contains(group))
+ {
+ return result;
+ }
+
+ List<SequenceFeature> sfs = positional ? getPositionalFeatures()
+ : getNonPositionalFeatures();
+ for (SequenceFeature sf : sfs)
+ {
+ String featureGroup = sf.getFeatureGroup();
+ if (group == null && featureGroup == null || group != null
+ && group.equals(featureGroup))
+ {
+ result.add(sf);
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Adds the shift value to the start and end of all positional features.
+ * Returns true if at least one feature was updated, else false.
+ *
+ * @param shift
+ * @return
+ */
+ public synchronized boolean shiftFeatures(int shift)
+ {
+ /*
+ * Because begin and end are final fields (to ensure the data store's
+ * integrity), we have to delete each feature and re-add it as amended.
+ * (Although a simple shift of all values would preserve data integrity!)
+ */
+ boolean modified = false;
+ for (SequenceFeature sf : getPositionalFeatures())
+ {
+ modified = true;
+ int newBegin = sf.getBegin() + shift;
+ int newEnd = sf.getEnd() + shift;
+
+ /*
+ * sanity check: don't shift left of the first residue
+ */
+ if (newEnd > 0)
+ {
+ newBegin = Math.max(1, newBegin);
+ SequenceFeature sf2 = new SequenceFeature(sf, newBegin, newEnd,
+ sf.getFeatureGroup(), sf.getScore());
+ addFeature(sf2);
+ }
+ delete(sf);
+ }
+ return modified;
+ }
+}
--- /dev/null
+package jalview.datamodel.features;
+
+import jalview.datamodel.ContiguousI;
+import jalview.datamodel.Range;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * An adapted implementation of NCList as described in the paper
+ *
+ * <pre>
+ * Nested Containment List (NCList): a new algorithm for accelerating
+ * interval query of genome alignment and interval databases
+ * - Alexander V. Alekseyenko, Christopher J. Lee
+ * https://doi.org/10.1093/bioinformatics/btl647
+ * </pre>
+ */
+public class NCList<T extends ContiguousI>
+{
+ /*
+ * the number of ranges represented
+ */
+ private int size;
+
+ /*
+ * a list, in start position order, of sublists of ranges ordered so
+ * that each contains (or is the same as) the one that follows it
+ */
+ private List<NCNode<T>> subranges;
+
+ /**
+ * Constructor given a list of things that are each located on a contiguous
+ * interval. Note that the constructor may reorder the list.
+ * <p>
+ * We assume here that for each range, start <= end. Behaviour for reverse
+ * ordered ranges is undefined.
+ *
+ * @param ranges
+ */
+ public NCList(List<T> ranges)
+ {
+ this();
+ build(ranges);
+ }
+
+ /**
+ * Sort and group ranges into sublists where each sublist represents a region
+ * and its contained subregions
+ *
+ * @param ranges
+ */
+ protected void build(List<T> ranges)
+ {
+ /*
+ * sort by start ascending so that contained intervals
+ * follow their containing interval
+ */
+ Collections.sort(ranges, RangeComparator.BY_START_POSITION);
+
+ List<Range> sublists = buildSubranges(ranges);
+
+ /*
+ * convert each subrange to an NCNode consisting of a range and
+ * (possibly) its contained NCList
+ */
+ for (Range sublist : sublists)
+ {
+ subranges.add(new NCNode<T>(ranges.subList(sublist.start,
+ sublist.end + 1)));
+ }
+
+ size = ranges.size();
+ }
+
+ public NCList(T entry)
+ {
+ this();
+ subranges.add(new NCNode<>(entry));
+ size = 1;
+ }
+
+ public NCList()
+ {
+ subranges = new ArrayList<NCNode<T>>();
+ }
+
+ /**
+ * Traverses the sorted ranges to identify sublists, within which each
+ * interval contains the one that follows it
+ *
+ * @param ranges
+ * @return
+ */
+ protected List<Range> buildSubranges(List<T> ranges)
+ {
+ List<Range> sublists = new ArrayList<>();
+
+ if (ranges.isEmpty())
+ {
+ return sublists;
+ }
+
+ int listStartIndex = 0;
+ long lastEndPos = Long.MAX_VALUE;
+
+ for (int i = 0; i < ranges.size(); i++)
+ {
+ ContiguousI nextInterval = ranges.get(i);
+ long nextStart = nextInterval.getBegin();
+ long nextEnd = nextInterval.getEnd();
+ if (nextStart > lastEndPos || nextEnd > lastEndPos)
+ {
+ /*
+ * this interval is not contained in the preceding one
+ * close off the last sublist
+ */
+ sublists.add(new Range(listStartIndex, i - 1));
+ listStartIndex = i;
+ }
+ lastEndPos = nextEnd;
+ }
+
+ sublists.add(new Range(listStartIndex, ranges.size() - 1));
+ return sublists;
+ }
+
+ /**
+ * Adds one entry to the stored set (with duplicates allowed)
+ *
+ * @param entry
+ */
+ public void add(T entry)
+ {
+ add(entry, true);
+ }
+
+ /**
+ * Adds one entry to the stored set, and returns true, unless allowDuplicates
+ * is set to false and it is already contained (by object equality test), in
+ * which case it is not added and this method returns false.
+ *
+ * @param entry
+ * @param allowDuplicates
+ * @return
+ */
+ public synchronized boolean add(T entry, boolean allowDuplicates)
+ {
+ if (!allowDuplicates && contains(entry))
+ {
+ return false;
+ }
+
+ size++;
+ long start = entry.getBegin();
+ long end = entry.getEnd();
+
+ /*
+ * cases:
+ * - precedes all subranges: add as NCNode on front of list
+ * - follows all subranges: add as NCNode on end of list
+ * - enclosed by a subrange - add recursively to subrange
+ * - encloses one or more subranges - push them inside it
+ * - none of the above - add as a new node and resort nodes list (?)
+ */
+
+ /*
+ * find the first subrange whose end does not precede entry's start
+ */
+ int candidateIndex = findFirstOverlap(start);
+ if (candidateIndex == -1)
+ {
+ /*
+ * all subranges precede this one - add it on the end
+ */
+ subranges.add(new NCNode<>(entry));
+ return true;
+ }
+
+ /*
+ * search for maximal span of subranges i-k that the new entry
+ * encloses; or a subrange that encloses the new entry
+ */
+ boolean enclosing = false;
+ int firstEnclosed = 0;
+ int lastEnclosed = 0;
+ boolean overlapping = false;
+
+ for (int j = candidateIndex; j < subranges.size(); j++)
+ {
+ NCNode<T> subrange = subranges.get(j);
+
+ if (end < subrange.getBegin() && !overlapping && !enclosing)
+ {
+ /*
+ * new entry lies between subranges j-1 j
+ */
+ subranges.add(j, new NCNode<>(entry));
+ return true;
+ }
+
+ if (subrange.getBegin() <= start && subrange.getEnd() >= end)
+ {
+ /*
+ * push new entry inside this subrange as it encloses it
+ */
+ subrange.add(entry);
+ return true;
+ }
+
+ if (start <= subrange.getBegin())
+ {
+ if (end >= subrange.getEnd())
+ {
+ /*
+ * new entry encloses this subrange (and possibly preceding ones);
+ * continue to find the maximal list it encloses
+ */
+ if (!enclosing)
+ {
+ firstEnclosed = j;
+ }
+ lastEnclosed = j;
+ enclosing = true;
+ continue;
+ }
+ else
+ {
+ /*
+ * entry spans from before this subrange to inside it
+ */
+ if (enclosing)
+ {
+ /*
+ * entry encloses one or more preceding subranges
+ */
+ addEnclosingRange(entry, firstEnclosed, lastEnclosed);
+ return true;
+ }
+ else
+ {
+ /*
+ * entry spans two subranges but doesn't enclose any
+ * so just add it
+ */
+ subranges.add(j, new NCNode<>(entry));
+ return true;
+ }
+ }
+ }
+ else
+ {
+ overlapping = true;
+ }
+ }
+
+ /*
+ * drops through to here if new range encloses all others
+ * or overlaps the last one
+ */
+ if (enclosing)
+ {
+ addEnclosingRange(entry, firstEnclosed, lastEnclosed);
+ }
+ else
+ {
+ subranges.add(new NCNode<>(entry));
+ }
+
+ return true;
+ }
+
+ /**
+ * Answers true if this NCList contains the given entry (by object equality
+ * test), else false
+ *
+ * @param entry
+ * @return
+ */
+ public boolean contains(T entry)
+ {
+ /*
+ * find the first sublist that might overlap, i.e.
+ * the first whose end position is >= from
+ */
+ int candidateIndex = findFirstOverlap(entry.getBegin());
+
+ if (candidateIndex == -1)
+ {
+ return false;
+ }
+
+ int to = entry.getEnd();
+
+ for (int i = candidateIndex; i < subranges.size(); i++)
+ {
+ NCNode<T> candidate = subranges.get(i);
+ if (candidate.getBegin() > to)
+ {
+ /*
+ * we are past the end of our target range
+ */
+ break;
+ }
+ if (candidate.contains(entry))
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Update the tree so that the range of the new entry encloses subranges i to
+ * j (inclusive). That is, replace subranges i-j (inclusive) with a new
+ * subrange that contains them.
+ *
+ * @param entry
+ * @param i
+ * @param j
+ */
+ protected synchronized void addEnclosingRange(T entry, final int i,
+ final int j)
+ {
+ NCList<T> newNCList = new NCList<>();
+ newNCList.addNodes(subranges.subList(i, j + 1));
+ NCNode<T> newNode = new NCNode<>(entry, newNCList);
+ for (int k = j; k >= i; k--)
+ {
+ subranges.remove(k);
+ }
+ subranges.add(i, newNode);
+ }
+
+ protected void addNodes(List<NCNode<T>> nodes)
+ {
+ for (NCNode<T> node : nodes)
+ {
+ subranges.add(node);
+ size += node.size();
+ }
+ }
+
+ /**
+ * Returns a (possibly empty) list of items whose extent overlaps the given
+ * range
+ *
+ * @param from
+ * start of overlap range (inclusive)
+ * @param to
+ * end of overlap range (inclusive)
+ * @return
+ */
+ public List<T> findOverlaps(long from, long to)
+ {
+ List<T> result = new ArrayList<>();
+
+ findOverlaps(from, to, result);
+
+ return result;
+ }
+
+ /**
+ * Recursively searches the NCList adding any items that overlap the from-to
+ * range to the result list
+ *
+ * @param from
+ * @param to
+ * @param result
+ */
+ protected void findOverlaps(long from, long to, List<T> result)
+ {
+ /*
+ * find the first sublist that might overlap, i.e.
+ * the first whose end position is >= from
+ */
+ int candidateIndex = findFirstOverlap(from);
+
+ if (candidateIndex == -1)
+ {
+ return;
+ }
+
+ for (int i = candidateIndex; i < subranges.size(); i++)
+ {
+ NCNode<T> candidate = subranges.get(i);
+ if (candidate.getBegin() > to)
+ {
+ /*
+ * we are past the end of our target range
+ */
+ break;
+ }
+ candidate.findOverlaps(from, to, result);
+ }
+
+ }
+
+ /**
+ * Search subranges for the first one whose end position is not before the
+ * target range's start position, i.e. the first one that may overlap the
+ * target range. Returns the index in the list of the first such range found,
+ * or -1 if none found.
+ *
+ * @param from
+ * @return
+ */
+ protected int findFirstOverlap(long from)
+ {
+ /*
+ * The NCList paper describes binary search for this step,
+ * but this not implemented here as (a) I haven't understood it yet
+ * and (b) it seems to imply complications for adding to an NCList
+ */
+
+ int i = 0;
+ if (subranges != null)
+ {
+ for (NCNode<T> subrange : subranges)
+ {
+ if (subrange.getEnd() >= from)
+ {
+ return i;
+ }
+ i++;
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Formats the tree as a bracketed list e.g.
+ *
+ * <pre>
+ * [1-100 [10-30 [10-20]], 15-30 [20-20]]
+ * </pre>
+ */
+ @Override
+ public String toString()
+ {
+ return subranges.toString();
+ }
+
+ /**
+ * Returns a string representation of the data where containment is shown by
+ * indentation on new lines
+ *
+ * @return
+ */
+ public String prettyPrint()
+ {
+ StringBuilder sb = new StringBuilder(512);
+ int offset = 0;
+ int indent = 2;
+ prettyPrint(sb, offset, indent);
+ sb.append(System.lineSeparator());
+ return sb.toString();
+ }
+
+ /**
+ * @param sb
+ * @param offset
+ * @param indent
+ */
+ void prettyPrint(StringBuilder sb, int offset, int indent)
+ {
+ boolean first = true;
+ for (NCNode<T> subrange : subranges)
+ {
+ if (!first)
+ {
+ sb.append(System.lineSeparator());
+ }
+ first = false;
+ subrange.prettyPrint(sb, offset, indent);
+ }
+ }
+
+ /**
+ * Answers true if the data held satisfy the rules of construction of an
+ * NCList, else false.
+ *
+ * @return
+ */
+ public boolean isValid()
+ {
+ return isValid(Integer.MIN_VALUE, Integer.MAX_VALUE);
+ }
+
+ /**
+ * Answers true if the data held satisfy the rules of construction of an
+ * NCList bounded within the given start-end range, else false.
+ * <p>
+ * Each subrange must lie within start-end (inclusive). Subranges must be
+ * ordered by start position ascending.
+ * <p>
+ *
+ * @param start
+ * @param end
+ * @return
+ */
+ boolean isValid(final int start, final int end)
+ {
+ int lastStart = start;
+ for (NCNode<T> subrange : subranges)
+ {
+ if (subrange.getBegin() < lastStart)
+ {
+ System.err.println("error in NCList: range " + subrange.toString()
+ + " starts before " + lastStart);
+ return false;
+ }
+ if (subrange.getEnd() > end)
+ {
+ System.err.println("error in NCList: range " + subrange.toString()
+ + " ends after " + end);
+ return false;
+ }
+ lastStart = subrange.getBegin();
+
+ if (!subrange.isValid())
+ {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ /**
+ * Answers the lowest start position enclosed by the ranges
+ *
+ * @return
+ */
+ public int getStart()
+ {
+ return subranges.isEmpty() ? 0 : subranges.get(0).getBegin();
+ }
+
+ /**
+ * Returns the number of ranges held (deep count)
+ *
+ * @return
+ */
+ public int size()
+ {
+ return size;
+ }
+
+ /**
+ * Returns a list of all entries stored
+ *
+ * @return
+ */
+ public List<T> getEntries()
+ {
+ List<T> result = new ArrayList<>();
+ getEntries(result);
+ return result;
+ }
+
+ /**
+ * Adds all contained entries to the given list
+ *
+ * @param result
+ */
+ void getEntries(List<T> result)
+ {
+ for (NCNode<T> subrange : subranges)
+ {
+ subrange.getEntries(result);
+ }
+ }
+
+ /**
+ * Deletes the given entry from the store, returning true if it was found (and
+ * deleted), else false. This method makes no assumption that the entry is in
+ * the 'expected' place in the store, in case it has been modified since it
+ * was added. Only the first 'same object' match is deleted, not 'equal' or
+ * multiple objects.
+ *
+ * @param entry
+ */
+ public synchronized boolean delete(T entry)
+ {
+ if (entry == null)
+ {
+ return false;
+ }
+ for (int i = 0; i < subranges.size(); i++)
+ {
+ NCNode<T> subrange = subranges.get(i);
+ NCList<T> subRegions = subrange.getSubRegions();
+
+ if (subrange.getRegion() == entry)
+ {
+ /*
+ * if the subrange is rooted on this entry, promote its
+ * subregions (if any) to replace the subrange here;
+ * NB have to resort subranges after doing this since e.g.
+ * [10-30 [12-20 [16-18], 13-19]]
+ * after deleting 12-20, 16-18 is promoted to sibling of 13-19
+ * but should follow it in the list of subranges of 10-30
+ */
+ subranges.remove(i);
+ if (subRegions != null)
+ {
+ subranges.addAll(subRegions.subranges);
+ Collections.sort(subranges, RangeComparator.BY_START_POSITION);
+ }
+ size--;
+ return true;
+ }
+ else
+ {
+ if (subRegions != null && subRegions.delete(entry))
+ {
+ size--;
+ subrange.deleteSubRegionsIfEmpty();
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+}
--- /dev/null
+package jalview.datamodel.features;
+
+import jalview.datamodel.ContiguousI;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Each node of the NCList tree consists of a range, and (optionally) the NCList
+ * of ranges it encloses
+ *
+ * @param <V>
+ */
+class NCNode<V extends ContiguousI> implements ContiguousI
+{
+ /*
+ * deep size (number of ranges included)
+ */
+ private int size;
+
+ private V region;
+
+ /*
+ * null, or an object holding contained subregions of this nodes region
+ */
+ private NCList<V> subregions;
+
+ /**
+ * Constructor given a list of ranges
+ *
+ * @param ranges
+ */
+ NCNode(List<V> ranges)
+ {
+ build(ranges);
+ }
+
+ /**
+ * Constructor given a single range
+ *
+ * @param range
+ */
+ NCNode(V range)
+ {
+ List<V> ranges = new ArrayList<>();
+ ranges.add(range);
+ build(ranges);
+ }
+
+ NCNode(V entry, NCList<V> newNCList)
+ {
+ region = entry;
+ subregions = newNCList;
+ size = 1 + newNCList.size();
+ }
+
+ /**
+ * @param ranges
+ */
+ protected void build(List<V> ranges)
+ {
+ size = ranges.size();
+
+ if (!ranges.isEmpty())
+ {
+ region = ranges.get(0);
+ }
+ if (ranges.size() > 1)
+ {
+ subregions = new NCList<V>(ranges.subList(1, ranges.size()));
+ }
+ }
+
+ @Override
+ public int getBegin()
+ {
+ return region.getBegin();
+ }
+
+ @Override
+ public int getEnd()
+ {
+ return region.getEnd();
+ }
+
+ /**
+ * Formats the node as a bracketed list e.g.
+ *
+ * <pre>
+ * [1-100 [10-30 [10-20]], 15-30 [20-20]]
+ * </pre>
+ */
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder(10 * size);
+ sb.append(region.getBegin()).append("-").append(region.getEnd());
+ if (subregions != null)
+ {
+ sb.append(" ").append(subregions.toString());
+ }
+ return sb.toString();
+ }
+
+ void prettyPrint(StringBuilder sb, int offset, int indent) {
+ for (int i = 0 ; i < offset ; i++) {
+ sb.append(" ");
+ }
+ sb.append(region.getBegin()).append("-").append(region.getEnd());
+ if (subregions != null)
+ {
+ sb.append(System.lineSeparator());
+ subregions.prettyPrint(sb, offset + 2, indent);
+ }
+ }
+ /**
+ * Add any ranges that overlap the from-to range to the result list
+ *
+ * @param from
+ * @param to
+ * @param result
+ */
+ void findOverlaps(long from, long to, List<V> result)
+ {
+ if (region.getBegin() <= to && region.getEnd() >= from)
+ {
+ result.add(region);
+ }
+ if (subregions != null)
+ {
+ subregions.findOverlaps(from, to, result);
+ }
+ }
+
+ /**
+ * Add one range to this subrange
+ *
+ * @param entry
+ */
+ synchronized void add(V entry)
+ {
+ if (entry.getBegin() < region.getBegin() || entry.getEnd() > region.getEnd()) {
+ throw new IllegalArgumentException(String.format(
+ "adding improper subrange %d-%d to range %d-%d",
+ entry.getBegin(), entry.getEnd(), region.getBegin(),
+ region.getEnd()));
+ }
+ if (subregions == null)
+ {
+ subregions = new NCList<V>(entry);
+ }
+ else
+ {
+ subregions.add(entry);
+ }
+ size++;
+ }
+
+ /**
+ * Answers true if the data held satisfy the rules of construction of an
+ * NCList, else false.
+ *
+ * @return
+ */
+ boolean isValid()
+ {
+ /*
+ * we don't handle reverse ranges
+ */
+ if (region != null && region.getBegin() > region.getEnd())
+ {
+ return false;
+ }
+ if (subregions == null)
+ {
+ return true;
+ }
+ return subregions.isValid(getBegin(), getEnd());
+ }
+
+ /**
+ * Adds all contained entries to the given list
+ *
+ * @param entries
+ */
+ void getEntries(List<V> entries)
+ {
+ entries.add(region);
+ if (subregions != null)
+ {
+ subregions.getEntries(entries);
+ }
+ }
+
+ /**
+ * Answers true if this object contains the given entry (by object equals
+ * test), else false
+ *
+ * @param entry
+ * @return
+ */
+ boolean contains(V entry)
+ {
+ if (entry == null)
+ {
+ return false;
+ }
+ if (entry.equals(region))
+ {
+ return true;
+ }
+ return subregions == null ? false : subregions.contains(entry);
+ }
+
+ /**
+ * Answers the 'root' region modelled by this object
+ *
+ * @return
+ */
+ V getRegion()
+ {
+ return region;
+ }
+
+ /**
+ * Answers the (possibly null) contained regions within this object
+ *
+ * @return
+ */
+ NCList<V> getSubRegions()
+ {
+ return subregions;
+ }
+
+ /**
+ * Nulls the subregion reference if it is empty (after a delete entry
+ * operation)
+ */
+ void deleteSubRegionsIfEmpty()
+ {
+ if (subregions != null && subregions.size() == 0)
+ {
+ subregions = null;
+ }
+ }
+
+ /**
+ * Answers the (deep) size of this node i.e. the number of ranges it models
+ *
+ * @return
+ */
+ int size()
+ {
+ return size;
+ }
+}
--- /dev/null
+package jalview.datamodel.features;
+
+import jalview.datamodel.ContiguousI;
+
+import java.util.Comparator;
+
+/**
+ * A comparator that orders ranges by either start position or end position
+ * ascending. If the position matches, ordering is resolved by end position (or
+ * start position).
+ *
+ * @author gmcarstairs
+ *
+ */
+public class RangeComparator implements Comparator<ContiguousI>
+{
+ public static final Comparator<ContiguousI> BY_START_POSITION = new RangeComparator(
+ true);
+
+ public static final Comparator<ContiguousI> BY_END_POSITION = new RangeComparator(
+ false);
+
+ boolean byStart;
+
+ /**
+ * Constructor
+ *
+ * @param byStartPosition
+ * if true, order based on start position, if false by end position
+ */
+ RangeComparator(boolean byStartPosition)
+ {
+ byStart = byStartPosition;
+ }
+
+ @Override
+ public int compare(ContiguousI o1, ContiguousI o2)
+ {
+ int len1 = o1.getEnd() - o1.getBegin();
+ int len2 = o2.getEnd() - o2.getBegin();
+
+ if (byStart)
+ {
+ return compare(o1.getBegin(), o2.getBegin(), len1, len2);
+ }
+ else
+ {
+ return compare(o1.getEnd(), o2.getEnd(), len1, len2);
+ }
+ }
+
+ /**
+ * Compares two ranges for ordering
+ *
+ * @param pos1
+ * first range positional ordering criterion
+ * @param pos2
+ * second range positional ordering criterion
+ * @param len1
+ * first range length ordering criterion
+ * @param len2
+ * second range length ordering criterion
+ * @return
+ */
+ public int compare(long pos1, long pos2, int len1, int len2)
+ {
+ int order = Long.compare(pos1, pos2);
+ if (order == 0)
+ {
+ /*
+ * if tied on position order, longer length sorts to left
+ * i.e. the negation of normal ordering by length
+ */
+ order = -Integer.compare(len1, len2);
+ }
+ return order;
+ }
+}
--- /dev/null
+package jalview.datamodel.features;
+
+import jalview.datamodel.ContiguousI;
+import jalview.datamodel.SequenceFeature;
+import jalview.io.gff.SequenceOntologyFactory;
+import jalview.io.gff.SequenceOntologyI;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+import java.util.TreeMap;
+
+/**
+ * A class that stores sequence features in a way that supports efficient
+ * querying by type and location (overlap). Intended for (but not limited to)
+ * storage of features for one sequence.
+ *
+ * @author gmcarstairs
+ *
+ */
+public class SequenceFeatures implements SequenceFeaturesI
+{
+ /**
+ * a comparator for sorting features by start position ascending
+ */
+ private static Comparator<ContiguousI> FORWARD_STRAND = new Comparator<ContiguousI>()
+ {
+ @Override
+ public int compare(ContiguousI o1, ContiguousI o2)
+ {
+ return Integer.compare(o1.getBegin(), o2.getBegin());
+ }
+ };
+
+ /**
+ * a comparator for sorting features by end position descending
+ */
+ private static Comparator<ContiguousI> REVERSE_STRAND = new Comparator<ContiguousI>()
+ {
+ @Override
+ public int compare(ContiguousI o1, ContiguousI o2)
+ {
+ return Integer.compare(o2.getEnd(), o1.getEnd());
+ }
+ };
+
+ /*
+ * map from feature type to structured store of features for that type
+ * null types are permitted (but not a good idea!)
+ */
+ private Map<String, FeatureStore> featureStore;
+
+ /**
+ * Constructor
+ */
+ public SequenceFeatures()
+ {
+ /*
+ * use a TreeMap so that features are returned in alphabetical order of type
+ * ? wrap as a synchronized map for add and delete operations
+ */
+ // featureStore = Collections
+ // .synchronizedSortedMap(new TreeMap<String, FeatureStore>());
+ featureStore = new TreeMap<String, FeatureStore>();
+ }
+
+ /**
+ * Constructor given a list of features
+ */
+ public SequenceFeatures(List<SequenceFeature> features)
+ {
+ this();
+ if (features != null)
+ {
+ for (SequenceFeature feature : features)
+ {
+ add(feature);
+ }
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean add(SequenceFeature sf)
+ {
+ String type = sf.getType();
+ if (type == null)
+ {
+ System.err.println("Feature type may not be null: " + sf.toString());
+ return false;
+ }
+
+ if (featureStore.get(type) == null)
+ {
+ featureStore.put(type, new FeatureStore());
+ }
+ return featureStore.get(type).addFeature(sf);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public List<SequenceFeature> findFeatures(int from, int to,
+ String... type)
+ {
+ List<SequenceFeature> result = new ArrayList<>();
+
+ for (FeatureStore featureSet : varargToTypes(type))
+ {
+ result.addAll(featureSet.findOverlappingFeatures(from, to));
+ }
+
+ return result;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public List<SequenceFeature> getAllFeatures(String... type)
+ {
+ List<SequenceFeature> result = new ArrayList<>();
+
+ result.addAll(getPositionalFeatures(type));
+
+ result.addAll(getNonPositionalFeatures());
+
+ return result;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public List<SequenceFeature> getFeaturesByOntology(String... ontologyTerm)
+ {
+ if (ontologyTerm == null || ontologyTerm.length == 0)
+ {
+ return new ArrayList<>();
+ }
+
+ Set<String> featureTypes = getFeatureTypes(ontologyTerm);
+ return getAllFeatures(featureTypes.toArray(new String[featureTypes
+ .size()]));
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public int getFeatureCount(boolean positional, String... type)
+ {
+ int result = 0;
+
+ for (FeatureStore featureSet : varargToTypes(type))
+ {
+ result += featureSet.getFeatureCount(positional);
+ }
+ return result;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public int getTotalFeatureLength(String... type)
+ {
+ int result = 0;
+
+ for (FeatureStore featureSet : varargToTypes(type))
+ {
+ result += featureSet.getTotalFeatureLength();
+ }
+ return result;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public List<SequenceFeature> getPositionalFeatures(String... type)
+ {
+ List<SequenceFeature> result = new ArrayList<>();
+
+ for (FeatureStore featureSet : varargToTypes(type))
+ {
+ result.addAll(featureSet.getPositionalFeatures());
+ }
+ return result;
+ }
+
+ /**
+ * A convenience method that converts a vararg for feature types to an
+ * Iterable over matched feature sets in key order
+ *
+ * @param type
+ * @return
+ */
+ protected Iterable<FeatureStore> varargToTypes(String... type)
+ {
+ if (type == null || type.length == 0)
+ {
+ /*
+ * no vararg parameter supplied - return all
+ */
+ return featureStore.values();
+ }
+
+ List<FeatureStore> types = new ArrayList<>();
+ List<String> args = Arrays.asList(type);
+ for (Entry<String, FeatureStore> featureType : featureStore.entrySet())
+ {
+ if (args.contains(featureType.getKey()))
+ {
+ types.add(featureType.getValue());
+ }
+ }
+ return types;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public List<SequenceFeature> getContactFeatures(String... type)
+ {
+ List<SequenceFeature> result = new ArrayList<>();
+
+ for (FeatureStore featureSet : varargToTypes(type))
+ {
+ result.addAll(featureSet.getContactFeatures());
+ }
+ return result;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public List<SequenceFeature> getNonPositionalFeatures(String... type)
+ {
+ List<SequenceFeature> result = new ArrayList<>();
+
+ for (FeatureStore featureSet : varargToTypes(type))
+ {
+ result.addAll(featureSet.getNonPositionalFeatures());
+ }
+ return result;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean delete(SequenceFeature sf)
+ {
+ for (FeatureStore featureSet : featureStore.values())
+ {
+ if (featureSet.delete(sf))
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean hasFeatures()
+ {
+ for (FeatureStore featureSet : featureStore.values())
+ {
+ if (!featureSet.isEmpty())
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Set<String> getFeatureGroups(boolean positionalFeatures,
+ String... type)
+ {
+ Set<String> groups = new HashSet<>();
+
+ for (FeatureStore featureSet : varargToTypes(type))
+ {
+ groups.addAll(featureSet.getFeatureGroups(positionalFeatures));
+ }
+
+ return groups;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Set<String> getFeatureTypesForGroups(boolean positionalFeatures,
+ String... groups)
+ {
+ Set<String> result = new HashSet<>();
+
+ for (Entry<String, FeatureStore> featureType : featureStore.entrySet())
+ {
+ Set<String> featureGroups = featureType.getValue().getFeatureGroups(
+ positionalFeatures);
+ for (String group : groups)
+ {
+ if (featureGroups.contains(group))
+ {
+ /*
+ * yes this feature type includes one of the query groups
+ */
+ result.add(featureType.getKey());
+ break;
+ }
+ }
+ }
+
+ return result;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public Set<String> getFeatureTypes(String... soTerm)
+ {
+ Set<String> types = new HashSet<>();
+ for (Entry<String, FeatureStore> entry : featureStore.entrySet())
+ {
+ String type = entry.getKey();
+ if (!entry.getValue().isEmpty() && isOntologyTerm(type, soTerm))
+ {
+ types.add(type);
+ }
+ }
+ return types;
+ }
+
+ /**
+ * Answers true if the given type is one of the specified sequence ontology
+ * terms (or a sub-type of one), or if no terms are supplied. Answers false if
+ * filter terms are specified and the given term does not match any of them.
+ *
+ * @param type
+ * @param soTerm
+ * @return
+ */
+ protected boolean isOntologyTerm(String type, String... soTerm)
+ {
+ if (soTerm == null || soTerm.length == 0)
+ {
+ return true;
+ }
+ SequenceOntologyI so = SequenceOntologyFactory.getInstance();
+ for (String term : soTerm)
+ {
+ if (so.isA(type, term))
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public float getMinimumScore(String type, boolean positional)
+ {
+ return featureStore.containsKey(type) ? featureStore.get(type)
+ .getMinimumScore(positional) : Float.NaN;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public float getMaximumScore(String type, boolean positional)
+ {
+ return featureStore.containsKey(type) ? featureStore.get(type)
+ .getMaximumScore(positional) : Float.NaN;
+ }
+
+ /**
+ * A convenience method to sort features by start position ascending (if on
+ * forward strand), or end position descending (if on reverse strand)
+ *
+ * @param features
+ * @param forwardStrand
+ */
+ public static void sortFeatures(List<SequenceFeature> features,
+ final boolean forwardStrand)
+ {
+ Collections.sort(features, forwardStrand ? FORWARD_STRAND
+ : REVERSE_STRAND);
+ }
+
+ /**
+ * {@inheritDoc} This method is 'semi-optimised': it only inspects features
+ * for types that include the specified group, but has to inspect every
+ * feature of those types for matching feature group. This is efficient unless
+ * a sequence has features that share the same type but are in different
+ * groups - an unlikely case.
+ * <p>
+ * For example, if RESNUM feature is created with group = PDBID, then features
+ * would only be retrieved for those sequences associated with the target
+ * PDBID (group).
+ */
+ @Override
+ public List<SequenceFeature> getFeaturesForGroup(boolean positional,
+ String group, String... type)
+ {
+ List<SequenceFeature> result = new ArrayList<>();
+ for (FeatureStore featureSet : varargToTypes(type))
+ {
+ if (featureSet.getFeatureGroups(positional).contains(group))
+ {
+ result.addAll(featureSet.getFeaturesForGroup(positional, group));
+ }
+ }
+ return result;
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public boolean shiftFeatures(int shift)
+ {
+ boolean modified = false;
+ for (FeatureStore fs : featureStore.values())
+ {
+ modified |= fs.shiftFeatures(shift);
+ }
+ return modified;
+ }
+}
\ No newline at end of file
--- /dev/null
+package jalview.datamodel.features;
+
+import jalview.datamodel.SequenceFeature;
+
+import java.util.List;
+import java.util.Set;
+
+public interface SequenceFeaturesI
+{
+
+ /**
+ * Adds one sequence feature to the store, and returns true, unless the
+ * feature is already contained in the store, in which case this method
+ * returns false. Containment is determined by SequenceFeature.equals()
+ * comparison. Answers false, and does not add the feature, if feature type is
+ * null.
+ *
+ * @param sf
+ */
+ boolean add(SequenceFeature sf);
+
+ /**
+ * Returns a (possibly empty) list of features, optionally restricted to
+ * specified types, which overlap the given (inclusive) sequence position
+ * range
+ *
+ * @param from
+ * @param to
+ * @param type
+ * @return
+ */
+ List<SequenceFeature> findFeatures(int from, int to,
+ String... type);
+
+ /**
+ * Answers a list of all features stored, in no particular guaranteed order.
+ * Positional features may optionally be restricted to specified types, but
+ * all non-positional features (if any) are always returned.
+ * <p>
+ * To filter non-positional features by type, use
+ * getNonPositionalFeatures(type).
+ *
+ * @param type
+ * @return
+ */
+ List<SequenceFeature> getAllFeatures(String... type);
+
+ /**
+ * Answers a list of all positional (or non-positional) features which are in
+ * the specified feature group, optionally restricted to features of specified
+ * types.
+ *
+ * @param positional
+ * if true returns positional features, else non-positional features
+ * @param group
+ * the feature group to be matched (which may be null)
+ * @param type
+ * optional feature types to filter by
+ * @return
+ */
+ List<SequenceFeature> getFeaturesForGroup(boolean positional,
+ String group, String... type);
+
+ /**
+ * Answers a list of all features stored, whose type either matches one of the
+ * given ontology terms, or is a specialisation of a term in the Sequence
+ * Ontology. Results are returned in no particular guaranteed order.
+ *
+ * @param ontologyTerm
+ * @return
+ */
+ List<SequenceFeature> getFeaturesByOntology(String... ontologyTerm);
+
+ /**
+ * Answers the number of (positional or non-positional) features, optionally
+ * restricted to specified feature types. Contact features are counted as 1.
+ *
+ * @param positional
+ * @param type
+ * @return
+ */
+ int getFeatureCount(boolean positional, String... type);
+
+ /**
+ * Answers the total length of positional features, optionally restricted to
+ * specified feature types. Contact features are counted as length 1.
+ *
+ * @param type
+ * @return
+ */
+ int getTotalFeatureLength(String... type);
+
+ /**
+ * Answers a list of all positional features, optionally restricted to
+ * specified types, in no particular guaranteed order
+ *
+ * @param type
+ * @return
+ */
+ List<SequenceFeature> getPositionalFeatures(
+ String... type);
+
+ /**
+ * Answers a list of all contact features, optionally restricted to specified
+ * types, in no particular guaranteed order
+ *
+ * @return
+ */
+ List<SequenceFeature> getContactFeatures(String... type);
+
+ /**
+ * Answers a list of all non-positional features, optionally restricted to
+ * specified types, in no particular guaranteed order
+ *
+ * @param type
+ * if no type is specified, all are returned
+ * @return
+ */
+ List<SequenceFeature> getNonPositionalFeatures(
+ String... type);
+
+ /**
+ * Deletes the given feature from the store, returning true if it was found
+ * (and deleted), else false. This method makes no assumption that the feature
+ * is in the 'expected' place in the store, in case it has been modified since
+ * it was added.
+ *
+ * @param sf
+ */
+ boolean delete(SequenceFeature sf);
+
+ /**
+ * Answers true if this store contains at least one feature, else false
+ *
+ * @return
+ */
+ boolean hasFeatures();
+
+ /**
+ * Returns a set of the distinct feature groups present in the collection. The
+ * set may include null. The boolean parameter determines whether the groups
+ * for positional or for non-positional features are returned. The optional
+ * type parameter may be used to restrict to groups for specified feature
+ * types.
+ *
+ * @param positionalFeatures
+ * @param type
+ * @return
+ */
+ Set<String> getFeatureGroups(boolean positionalFeatures,
+ String... type);
+
+ /**
+ * Answers the set of distinct feature types for which there is at least one
+ * feature with one of the given feature group(s). The boolean parameter
+ * determines whether the groups for positional or for non-positional features
+ * are returned.
+ *
+ * @param positionalFeatures
+ * @param groups
+ * @return
+ */
+ Set<String> getFeatureTypesForGroups(
+ boolean positionalFeatures, String... groups);
+
+ /**
+ * Answers a set of the distinct feature types for which a feature is stored.
+ * The types may optionally be restricted to those which match, or are a
+ * subtype of, given sequence ontology terms
+ *
+ * @return
+ */
+ Set<String> getFeatureTypes(String... soTerm);
+
+ /**
+ * Answers the minimum score held for positional or non-positional features
+ * for the specified type. This may be Float.NaN if there are no features, or
+ * none has a non-NaN score.
+ *
+ * @param type
+ * @param positional
+ * @return
+ */
+ float getMinimumScore(String type, boolean positional);
+
+ /**
+ * Answers the maximum score held for positional or non-positional features
+ * for the specified type. This may be Float.NaN if there are no features, or
+ * none has a non-NaN score.
+ *
+ * @param type
+ * @param positional
+ * @return
+ */
+ float getMaximumScore(String type, boolean positional);
+
+ /**
+ * Adds the shift amount to the start and end of all positional features,
+ * returning true if at least one feature was shifted, else false
+ *
+ * @param shift
+ */
+ abstract boolean shiftFeatures(int shift);
+}
\ No newline at end of file
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... !");
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!");
/*
* 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);
}
}
/**
* 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)
- {
- 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);
+ protected SequenceFeature makeCdsFeature(String type, String desc,
+ int begin, int end, String group, Map<String, String> vals)
+ {
+ SequenceFeature sf = new SequenceFeature(type, desc, begin, end, group);
if (!vals.isEmpty())
{
StringBuilder sb = new StringBuilder();
* along with Jalview. If not, see <http://www.gnu.org/licenses/>.
* The Jalview Authors are detailed in the 'AUTHORS' file.
*/
-package jalview.datamodel;
+package jalview.datamodel.xdb.uniprot;
+
+import jalview.datamodel.PDBEntry;
import java.util.Vector;
Vector<String> accession;
- Vector<SequenceFeature> feature;
+ Vector<UniprotFeature> feature;
Vector<PDBEntry> dbrefs;
accession = items;
}
- public void setFeature(Vector<SequenceFeature> items)
+ public void setFeature(Vector<UniprotFeature> items)
{
feature = items;
}
- public Vector<SequenceFeature> getFeature()
+ public Vector<UniprotFeature> getFeature()
{
return feature;
}
--- /dev/null
+package jalview.datamodel.xdb.uniprot;
+
+/**
+ * A data model class for binding from Uniprot XML via uniprot_mapping.xml
+ */
+public class UniprotFeature
+{
+ private String type;
+
+ private String description;
+
+ private String status;
+
+ private int begin;
+
+ private int end;
+
+ public String getType()
+ {
+ return type;
+ }
+
+ public void setType(String t)
+ {
+ this.type = t;
+ }
+
+ public String getDescription()
+ {
+ return description;
+ }
+
+ public void setDescription(String d)
+ {
+ this.description = d;
+ }
+
+ public String getStatus()
+ {
+ return status;
+ }
+
+ public void setStatus(String s)
+ {
+ this.status = s;
+ }
+
+ public int getBegin()
+ {
+ return begin;
+ }
+
+ public void setBegin(int b)
+ {
+ this.begin = b;
+ }
+
+ public int getEnd()
+ {
+ return end;
+ }
+
+ public void setEnd(int e)
+ {
+ this.end = e;
+ }
+
+ public int getPosition()
+ {
+ return begin;
+ }
+
+ public void setPosition(int p)
+ {
+ this.begin = p;
+ this.end = p;
+ }
+}
* along with Jalview. If not, see <http://www.gnu.org/licenses/>.
* The Jalview Authors are detailed in the 'AUTHORS' file.
*/
-package jalview.datamodel;
+package jalview.datamodel.xdb.uniprot;
import java.util.Vector;
* along with Jalview. If not, see <http://www.gnu.org/licenses/>.
* The Jalview Authors are detailed in the 'AUTHORS' file.
*/
-package jalview.datamodel;
+package jalview.datamodel.xdb.uniprot;
import java.util.Vector;
* along with Jalview. If not, see <http://www.gnu.org/licenses/>.
* The Jalview Authors are detailed in the 'AUTHORS' file.
*/
-package jalview.datamodel;
+package jalview.datamodel.xdb.uniprot;
/**
* Data model for the sequence returned by a Uniprot query
import jalview.io.gff.SequenceOntologyFactory;
import jalview.io.gff.SequenceOntologyI;
+import java.util.HashMap;
+import java.util.Map;
+
import com.stevesoft.pat.Regex;
/**
private static final Regex ACCESSION_REGEX = new Regex(
"(ENS([A-Z]{3}|)[TG][0-9]{11}$)" + "|" + "(CCDS[0-9.]{3,}$)");
+ private static Map<String, String> params = new HashMap<String, String>();
+
+ static
+ {
+ params.put("object_type", "transcript");
+ }
+
/*
* fetch exon features on genomic sequence (to identify the cdna regions)
* and cds and variation features (to retain)
return false;
}
+ /**
+ * Parameter object_type=cdna added to ensure cdna and not peptide is returned
+ * (JAL-2529)
+ */
+ @Override
+ protected Map<String, String> getAdditionalParameters()
+ {
+ return params;
+ }
+
}
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;
*/
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);
}
}
{
splices = findFeatures(gene, SequenceOntologyI.CDS, parentId);
}
+ SequenceFeatures.sortFeatures(splices, true);
int transcriptLength = 0;
final char[] geneChars = gene.getSequence();
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);
}
}
}
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;
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;
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
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;
}
* 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[]
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);
/*
return false;
}
- // long start = System.currentTimeMillis();
- SequenceFeature[] sfs = sourceSequence.getSequenceFeatures();
+// long start = System.currentTimeMillis();
+ 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;
}
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')
{
*/
package jalview.ext.rbvi.chimera;
-import jalview.util.RangeComparator;
+import jalview.util.IntRangeComparator;
import java.util.ArrayList;
import java.util.Collections;
/*
* 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];
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;
}
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
// Java's Transferable for native dnd
evt.acceptDrop(DnDConstants.ACTION_COPY_OR_MOVE);
Transferable t = evt.getTransferable();
- List<String> files = new ArrayList<>();
+ final AlignFrame thisaf = this;
+ final List<String> files = new ArrayList<>();
List<DataSourceType> protocols = new ArrayList<>();
try
}
if (files != null)
{
- try
+ new Thread(new Runnable()
{
- // check to see if any of these files have names matching sequences in
- // the alignment
- SequenceIdMatcher idm = new SequenceIdMatcher(
- viewport.getAlignment().getSequencesArray());
- /**
- * Object[] { String,SequenceI}
- */
- ArrayList<Object[]> filesmatched = new ArrayList<>();
- ArrayList<String> filesnotmatched = new ArrayList<>();
- for (int i = 0; i < files.size(); i++)
+ @Override
+ public void run()
{
- String file = files.get(i).toString();
- String pdbfn = "";
- DataSourceType protocol = FormatAdapter.checkProtocol(file);
- if (protocol == DataSourceType.FILE)
- {
- File fl = new File(file);
- pdbfn = fl.getName();
- }
- else if (protocol == DataSourceType.URL)
- {
- URL url = new URL(file);
- pdbfn = url.getFile();
- }
- if (pdbfn.length() > 0)
+ try
{
- // attempt to find a match in the alignment
- SequenceI[] mtch = idm.findAllIdMatches(pdbfn);
- int l = 0, c = pdbfn.indexOf(".");
- while (mtch == null && c != -1)
+ // check to see if any of these files have names matching sequences
+ // in
+ // the alignment
+ SequenceIdMatcher idm = new SequenceIdMatcher(
+ viewport.getAlignment().getSequencesArray());
+ /**
+ * Object[] { String,SequenceI}
+ */
+ ArrayList<Object[]> filesmatched = new ArrayList<>();
+ ArrayList<String> filesnotmatched = new ArrayList<>();
+ for (int i = 0; i < files.size(); i++)
{
- do
+ String file = files.get(i).toString();
+ String pdbfn = "";
+ DataSourceType protocol = FormatAdapter.checkProtocol(file);
+ if (protocol == DataSourceType.FILE)
{
- l = c;
- } while ((c = pdbfn.indexOf(".", l)) > l);
- if (l > -1)
- {
- pdbfn = pdbfn.substring(0, l);
+ File fl = new File(file);
+ pdbfn = fl.getName();
}
- mtch = idm.findAllIdMatches(pdbfn);
- }
- if (mtch != null)
- {
- FileFormatI type = null;
- try
+ else if (protocol == DataSourceType.URL)
{
- type = new IdentifyFile().identify(file, protocol);
- } catch (Exception ex)
- {
- type = null;
+ URL url = new URL(file);
+ pdbfn = url.getFile();
}
- if (type != null && type.isStructureFile())
+ if (pdbfn.length() > 0)
{
- filesmatched.add(new Object[] { file, protocol, mtch });
- continue;
+ // attempt to find a match in the alignment
+ SequenceI[] mtch = idm.findAllIdMatches(pdbfn);
+ int l = 0, c = pdbfn.indexOf(".");
+ while (mtch == null && c != -1)
+ {
+ do
+ {
+ l = c;
+ } while ((c = pdbfn.indexOf(".", l)) > l);
+ if (l > -1)
+ {
+ pdbfn = pdbfn.substring(0, l);
+ }
+ mtch = idm.findAllIdMatches(pdbfn);
+ }
+ if (mtch != null)
+ {
+ FileFormatI type = null;
+ try
+ {
+ type = new IdentifyFile().identify(file, protocol);
+ } catch (Exception ex)
+ {
+ type = null;
+ }
+ if (type != null && type.isStructureFile())
+ {
+ filesmatched.add(new Object[] { file, protocol, mtch });
+ continue;
+ }
+ }
+ // File wasn't named like one of the sequences or wasn't a PDB
+ // file.
+ filesnotmatched.add(file);
}
}
- // File wasn't named like one of the sequences or wasn't a PDB file.
- filesnotmatched.add(file);
- }
- }
- int assocfiles = 0;
- 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)
-
- {
- for (Object[] fm : filesmatched)
+ int assocfiles = 0;
+ if (filesmatched.size() > 0)
{
- // try and associate
- // TODO: may want to set a standard ID naming formalism for
- // associating PDB files which have no IDs.
- for (SequenceI toassoc : (SequenceI[]) fm[2])
+ if (Cache.getDefault("AUTOASSOCIATE_PDBANDSEQS", false)
+ || JvOptionPane.showConfirmDialog(thisaf,
+ 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)
+
{
- PDBEntry pe = new AssociatePdbFileWithSeq()
- .associatePdbWithSeq((String) fm[0],
- (DataSourceType) fm[1], toassoc, false,
- Desktop.instance);
- if (pe != null)
+ for (Object[] fm : filesmatched)
{
- System.err.println("Associated file : " + ((String) fm[0])
- + " with " + toassoc.getDisplayId(true));
- assocfiles++;
+ // try and associate
+ // TODO: may want to set a standard ID naming formalism for
+ // associating PDB files which have no IDs.
+ for (SequenceI toassoc : (SequenceI[]) fm[2])
+ {
+ PDBEntry pe = new AssociatePdbFileWithSeq()
+ .associatePdbWithSeq((String) fm[0],
+ (DataSourceType) fm[1], toassoc, false,
+ Desktop.instance);
+ if (pe != null)
+ {
+ System.err.println("Associated file : "
+ + ((String) fm[0]) + " with "
+ + toassoc.getDisplayId(true));
+ assocfiles++;
+ }
+ }
+ alignPanel.paintAlignment(true);
}
}
- alignPanel.paintAlignment(true);
}
- }
- }
- 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))
- {
- return;
- }
- for (String fn : filesnotmatched)
+ if (filesnotmatched.size() > 0)
+ {
+ if (assocfiles > 0 && (Cache.getDefault(
+ "AUTOASSOCIATE_PDBANDSEQS_IGNOREOTHERS", false)
+ || JvOptionPane.showConfirmDialog(thisaf,
+ "<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;
+ }
+ for (String fn : filesnotmatched)
+ {
+ loadJalviewDataFile(fn, null, null, null);
+ }
+
+ }
+ } catch (Exception ex)
{
- loadJalviewDataFile(fn, null, null, null);
+ ex.printStackTrace();
}
-
}
- } catch (Exception ex)
- {
- ex.printStackTrace();
- }
+ }).start();
}
}
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;
*/
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;
}
/**
{
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("\"", """);
+ 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
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;
.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
ShiftList offset = new ShiftList();
int ofstart = -1;
int sleng = seq.getLength();
- char[] seqChars = seq.getSequence();
for (int i = 0; i < sleng; i++)
{
- if (Comparison.isGap(seqChars[i]))
+ if (Comparison.isGap(seq.getCharAt(i)))
{
if (ofstart == -1)
{
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);
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;
thresholdValue_actionPerformed();
}
});
+ thresholdValue.addFocusListener(new FocusAdapter()
+ {
+ @Override
+ public void focusLost(FocusEvent e)
+ {
+ thresholdValue_actionPerformed();
+ }
+ });
slider.setPaintLabels(false);
slider.setPaintTicks(true);
slider.setBackground(Color.white);
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;
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") + ":"));
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)
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);
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;
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][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)
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,
{
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);
}
}
}
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;
// 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)
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);
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;
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);
}
}
import jalview.renderer.OverviewResColourFinder;
import jalview.viewmodel.OverviewDimensions;
-import java.awt.AlphaComposite;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
-import java.awt.Graphics2D;
-import java.awt.event.ActionEvent;
-import java.awt.event.ActionListener;
import java.awt.image.BufferedImage;
import javax.swing.JComponent;
-import javax.swing.Timer;
public class OverviewCanvas extends JComponent
{
- private static final long RUNNING_TIME = 1000;
-
- private static final int SPEED = 40;
-
private static final Color TRANS_GREY = new Color(100, 100, 100, 25);
// This is set true if the alignment view changes whilst
private BufferedImage lastMiniMe = null;
- private BufferedImage veryLastMiniMe = null;
-
// Can set different properties in this seqCanvas than
// main visible SeqCanvas
private SequenceRenderer sr;
private AlignViewportI av;
-
private OverviewResColourFinder cf;
- private float alpha = 0f;
-
- private long startTime = -1;
-
- private final Timer timer;
-
private ProgressPanel progressPanel;
public OverviewCanvas(OverviewDimensions overviewDims,
cf = new OverviewResColourFinder(useLegacy, gapCol, hiddenCol);
setSize(od.getWidth(), od.getHeight());
-
- timer = new Timer(SPEED, new ActionListener()
- {
-
- @Override
- public void actionPerformed(ActionEvent e)
- {
- if (startTime < 0)
- {
- startTime = System.currentTimeMillis();
- }
- else
- {
-
- long time = System.currentTimeMillis();
- long duration = time - startTime;
- if (duration >= RUNNING_TIME)
- {
- startTime = -1;
- ((Timer) e.getSource()).stop();
- alpha = 0f;
- }
- else
- {
- alpha = 1f - ((float) duration / (float) RUNNING_TIME);
- }
- repaint();
- }
- }
- });
-
}
/**
FeatureRenderer transferRenderer)
{
miniMe = null;
- veryLastMiniMe = lastMiniMe;
if (showSequenceFeatures)
{
{
updaterunning = false;
lastMiniMe = miniMe;
- alpha = 1f;
- timer.start();
}
}
}
else // not a resize
{
- if (alpha != 0) // this is a timer triggered dissolve
- {
- Graphics2D g2d = (Graphics2D) g.create();
-
- // draw the original image
- g2d.drawImage(veryLastMiniMe, 0, 0, getWidth(), getHeight(),
- this);
-
- // draw the new image on top with varying degrees of transparency
- g2d.setComposite(AlphaComposite.SrcOver.derive(1f - alpha));
- g2d.drawImage(lastMiniMe, 0, 0, getWidth(), getHeight(), this);
-
- g2d.dispose();
- }
- else
- {
- // fall back to normal behaviour
- g.drawImage(lastMiniMe, 0, 0, getWidth(), getHeight(), this);
- }
+ // fall back to normal behaviour
+ g.drawImage(lastMiniMe, 0, 0, getWidth(), getHeight(), this);
}
}
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));
}
}
.amendFeatures(seqs, features, true, ap))
{
ap.alignFrame.setShowSeqFeatures(true);
- ap.highlightSearchResults(null);
+ ap.av.setSearchResults(null); // clear highlighting
+ ap.repaint(); // draw new/amended features
}
}
}
*/
public class SeqCanvas extends JComponent implements ViewportListenerI
{
+ private static String ZEROS = "0000000000";
+
final FeatureRenderer fr;
final SequenceRenderer seqRdr;
boolean fastPaint = false;
- int LABEL_WEST;
+ int labelWidthWest;
- int LABEL_EAST;
+ int labelWidthEast;
int cursorX = 0;
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));
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)
{
}
/**
- * 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())
{
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;
}
hgap += charHeight;
}
- int cWidth = (canvasWidth - LABEL_EAST - LABEL_WEST) / charWidth;
+ int cWidth = (canvasWidth - labelWidthEast - labelWidthWest)
+ / charWidth;
int cHeight = av.getAlignment().getHeight() * charHeight;
int startx = startRes;
endx = maxwidth;
}
- g.translate(LABEL_WEST, 0);
+ g.translate(labelWidthWest, 0);
drawUnwrappedSelection(g, group, startx, endx, 0,
av.getAlignment().getHeight() - 1,
ypos);
- g.translate(-LABEL_WEST, 0);
+ g.translate(-labelWidthWest, 0);
// update vertical offset
ypos += cHeight + getAnnotationHeight() + hgap;
return annotations.adjustPanelHeight();
}
- /*
- * Draw an alignment panel for printing
+ /**
+ * 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
* Graphics object to draw with
* @param startRes
- * start residue of print area
+ * offset of the first column in the visible region (0..)
* @param endRes
- * end residue of print area
+ * offset of the last column in the visible region (0..)
* @param startSeq
- * start sequence of print area
+ * offset of the first sequence in the visible region (0..)
* @param endSeq
- * end sequence of print area
- * @param offset
- * vertical offset
+ * offset of the last sequence in the visible region (0..)
+ * @param yOffset
+ * vertical offset at which to draw (for wrapped alignments)
*/
- private 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);
}
}
+ /**
+ * 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)
{
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)
{
}
/**
- * 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;
+ }
}
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
-import java.util.ListIterator;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;
/** 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)
}
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 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)
+ String textString = tooltipText.toString();
+ if (lastTooltip == null || !lastTooltip.equals(textString))
{
- it.remove();
+ String formattedTooltipText = JvSwingUtils.wrapTooltip(true,
+ textString);
+ setToolTipText(formattedTooltipText);
+ lastTooltip = textString;
}
}
}
* 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);
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;
}
}
// 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;
}
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
}
}
}
*/
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)
{
{
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;
}
-
}
}
}
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();
}
}
}
out.append(newline);
- if (s[i].getSequence().length > max)
- {
- max = s[i].getSequence().length;
- }
+ max = Math.max(max, s[i].getLength());
i++;
}
+++ /dev/null
-/*
- * 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.io;
-
-/**
- * Read or write a CLANS style score matrix file.
- */
-
-public class ClansFile extends FileParse
-{
-
-}
{
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)
{
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));
}
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.HashMap;
-import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
/**
* 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,
+ 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);
}
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);
}
}
// 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);
// 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;
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;
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;
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);
}
// 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))
}
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);
+++ /dev/null
-/*
- * 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.io;
-
-/**
- * IO for asymmetric matrix with arbitrary dimension with labels, as displayed
- * by PCA viewer. Form is: tab separated entity defs header line TITLE\ttitle
- * DESC\tdesc PROPERTY\t<id or empty for whole matrix>\tname\ttype\tvalue
- * ROW\tRow i label (ID)/tPrinciple text/tprinciple description/t...
- * COLUMN\t(similar, optional).. .. <float>\t<float>...(column-wise data for row
- * i)
- */
-
-public class MatrixFile extends FileParse
-{
-
-}
{
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)
{
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,
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);
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);
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>()
{
{
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,
*/
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("(>|\\])", ")");
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);
}
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
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)
{
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;
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;
}
* 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);
/*
*/
@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)
{
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]);
}
/**
- *
- */
+ * 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;
}
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;
// 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();
}
doJvUpdate();
}
+ @Override
public void addFromDocument()
{
Sequence vseq = (Sequence) vobj;
modified = true;
}
+ @Override
public void updateFromDoc()
{
Sequence sq = (Sequence) vobj;
*/
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;
return modifiedtheseq;
}
+ @Override
public void conflict()
{
log.warn(
boolean modified = false;
+ @Override
public void updateToDoc()
{
SequenceI sq = (SequenceI) jvobj;
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());
}
* @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;
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;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.Graphics2D;
+import java.util.List;
public class FeatureRenderer extends FeatureRendererModel
{
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)
{
}
/**
+>>>>>>> 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
*/
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;
+ }
}
}
}
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]++;
}
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()
{
{
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++;
--- /dev/null
+package jalview.util;
+
+import java.util.Comparator;
+
+/**
+ * A comparator to order [from, to] ranges into ascending or descending order of
+ * their start position
+ */
+public class IntRangeComparator implements Comparator<int[]>
+{
+ public static final Comparator<int[]> ASCENDING = new IntRangeComparator(
+ true);
+
+ public static final Comparator<int[]> DESCENDING = new IntRangeComparator(
+ false);
+
+ boolean forwards;
+
+ IntRangeComparator(boolean forward)
+ {
+ forwards = forward;
+ }
+
+ @Override
+ public int compare(int[] o1, int[] o2)
+ {
+ int compared = Integer.compare(o1[0], o2[0]);
+ return forwards ? compared : -compared;
+ }
+
+}
\ No newline at end of file
+++ /dev/null
-/*
- * 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.util;
-
-import java.util.Comparator;
-
-/**
- * A comparator to order [from, to] ranges into ascending or descending order of
- * their start position
- */
-public class RangeComparator implements Comparator<int[]>
-{
- boolean forwards;
-
- public RangeComparator(boolean forward)
- {
- forwards = forward;
- }
-
- @Override
- public int compare(int[] o1, int[] o2)
- {
- int compared = Integer.compare(o1[0], o2[0]);
- return forwards ? compared : -compared;
- }
-
-}
\ No newline at end of file
}
/**
- * 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;
}
/**
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;
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);
}
}
}
}
@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?
}
}
- index++;
}
}
- /*
- * 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;
/**
* 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) }
{
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;
}
}
import jalview.api.FeaturesDisplayedI;
import java.util.Collection;
+import java.util.Collections;
import java.util.HashSet;
-import java.util.Iterator;
+import java.util.Set;
public class FeaturesDisplayed implements FeaturesDisplayedI
{
- private HashSet<String> featuresDisplayed = new HashSet<String>();
+ private Set<String> featuresDisplayed = new HashSet<String>();
- private HashSet<String> featuresRegistered = new HashSet<String>();
+ private Set<String> featuresRegistered = new HashSet<String>();
public FeaturesDisplayed(FeaturesDisplayedI featuresDisplayed2)
{
- Iterator<String> fdisp = featuresDisplayed2.getVisibleFeatures();
- String ftype;
- while (fdisp.hasNext())
+ Set<String> fdisp = featuresDisplayed2.getVisibleFeatures();
+ for (String ftype : fdisp)
{
- ftype = fdisp.next();
featuresDisplayed.add(ftype);
featuresRegistered.add(ftype);
}
public FeaturesDisplayed()
{
- // TODO Auto-generated constructor stub
}
@Override
- public Iterator<String> getVisibleFeatures()
+ public Set<String> getVisibleFeatures()
{
- return featuresDisplayed.iterator();
+ return Collections.unmodifiableSet(featuresDisplayed);
}
@Override
*
* @param alignment
* @param col
+ * (0..)
* @param row
* @param fr
*/
{
return null;
}
- int pos = seq.findPosition(col);
/*
* compute a count for any displayed features at residue
*/
- // NB have to adjust pos if using AlignmentView.getVisibleAlignment
// see JAL-2075
- List<SequenceFeature> features = fr.findFeaturesAtRes(seq, pos);
+ List<SequenceFeature> features = fr.findFeaturesAtColumn(seq, col + 1);
int[] count = this.counter.count(String.valueOf(res), features);
return count;
}
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;
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);
}
}
}
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;
}
}
-
}
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)
*/
package jalview.ws.jws2;
-import jalview.api.AlignCalcWorkerI;
import jalview.api.FeatureColourI;
import jalview.bin.Cache;
import jalview.datamodel.AlignmentAnnotation;
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;
}
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);
{
idvector.append(sep);
}
- idvector.append(seq.getSequence());
+ idvector.append(seq.getSequenceAsString());
}
return new StringBody(idvector.toString());
}
import jalview.structure.StructureImportSettings;
import java.awt.Color;
+import java.util.List;
import java.util.Vector;
import org.testng.annotations.BeforeClass;
/*
* check sequence features
*/
- SequenceFeature[] sfs = c.sequence.getSequenceFeatures();
- assertEquals(3, sfs.length);
- assertEquals("RESNUM", sfs[0].type);
- assertEquals("MET:4 1gaqA", sfs[0].description);
- assertEquals(4, sfs[0].begin);
- assertEquals(4, sfs[0].end);
- assertEquals("RESNUM", sfs[0].type);
- assertEquals("LYS:5 1gaqA", sfs[1].description);
- assertEquals(5, sfs[1].begin);
- assertEquals(5, sfs[1].end);
- assertEquals("LEU:6 1gaqA", sfs[2].description);
- assertEquals(6, sfs[2].begin);
- assertEquals(6, sfs[2].end);
+ List<SequenceFeature> sfs = c.sequence.getSequenceFeatures();
+ assertEquals(3, sfs.size());
+ assertEquals("RESNUM", sfs.get(0).type);
+ assertEquals("MET:4 1gaqA", sfs.get(0).description);
+ assertEquals(4, sfs.get(0).begin);
+ assertEquals(4, sfs.get(0).end);
+ assertEquals("RESNUM", sfs.get(0).type);
+ assertEquals("LYS:5 1gaqA", sfs.get(1).description);
+ assertEquals(5, sfs.get(1).begin);
+ assertEquals(5, sfs.get(1).end);
+ assertEquals("LEU:6 1gaqA", sfs.get(2).description);
+ assertEquals(6, sfs.get(2).begin);
+ assertEquals(6, sfs.get(2).end);
}
private Atom makeAtom(int resnum, String name, String resname)
/*
* sort with no score features does nothing
*/
- PA.setValue(AlignmentSorter.class, "lastSortByFeatureScore", null);
+ PA.setValue(AlignmentSorter.class, "sortByFeatureCriteria", null);
- AlignmentSorter.sortByFeature((String) null, null, 0, al.getWidth(),
- al,
+ AlignmentSorter.sortByFeature(null, null, 0, al.getWidth(), al,
AlignmentSorter.FEATURE_SCORE);
assertSame(al.getSequenceAt(0), seq1);
assertSame(al.getSequenceAt(1), seq2);
* sort by ascending score, no filter on feature type or group
* NB sort order for the same feature set (none) gets toggled, so descending
*/
- PA.setValue(AlignmentSorter.class, "sortByFeatureScoreAscending", true);
- AlignmentSorter.sortByFeature((String) null, null, 0, al.getWidth(),
- al, AlignmentSorter.FEATURE_SCORE);
+ PA.setValue(AlignmentSorter.class, "sortByFeatureAscending", true);
+ AlignmentSorter.sortByFeature(null, null, 0, al.getWidth(), al,
+ AlignmentSorter.FEATURE_SCORE);
assertSame(al.getSequenceAt(3), seq3); // -0.5
assertSame(al.getSequenceAt(2), seq2); // 2.5
assertSame(al.getSequenceAt(1), seq1); // 3.0
/*
* repeat sort toggles order - now ascending
*/
- AlignmentSorter.sortByFeature((String) null, null, 0, al.getWidth(),
- al, AlignmentSorter.FEATURE_SCORE);
+ AlignmentSorter.sortByFeature(null, null, 0, al.getWidth(), al,
+ AlignmentSorter.FEATURE_SCORE);
assertSame(al.getSequenceAt(0), seq3); // -0.5
assertSame(al.getSequenceAt(1), seq2); // 2.5
assertSame(al.getSequenceAt(2), seq1); // 3.0
*/
// fails because seq1.findPosition(4) returns 4
// although residue 4 is in column 5! - JAL-2544
- AlignmentSorter.sortByFeature((String) null, null, 0, 4, al,
+ AlignmentSorter.sortByFeature(null, null, 0, 4, al,
AlignmentSorter.FEATURE_SCORE);
assertSame(al.getSequenceAt(0), seq3); // -4
assertSame(al.getSequenceAt(1), seq1); // 2.0
import jalview.datamodel.Sequence;
import jalview.datamodel.SequenceFeature;
import jalview.datamodel.SequenceI;
+import jalview.datamodel.features.SequenceFeatures;
import jalview.gui.JvOptionPane;
import jalview.io.AppletFormatAdapter;
import jalview.io.DataSourceType;
/*
* check cds2 acquired a variant feature in position 5
*/
- SequenceFeature[] sfs = cds2Dss.getSequenceFeatures();
+ List<SequenceFeature> sfs = cds2Dss.getSequenceFeatures();
assertNotNull(sfs);
- assertEquals(1, sfs.length);
- assertEquals("variant", sfs[0].type);
- assertEquals(5, sfs[0].begin);
- assertEquals(5, sfs[0].end);
+ assertEquals(1, sfs.size());
+ assertEquals("variant", sfs.get(0).type);
+ assertEquals(5, sfs.get(0).begin);
+ assertEquals(5, sfs.get(0).end);
}
/**
* that partially overlap 5' or 3' (start or end) of target sequence
*/
AlignmentUtils.transferFeatures(dna, cds, map, null);
- SequenceFeature[] sfs = cds.getSequenceFeatures();
- assertEquals(6, sfs.length);
+ List<SequenceFeature> sfs = cds.getSequenceFeatures();
+ assertEquals(6, sfs.size());
- SequenceFeature sf = sfs[0];
+ SequenceFeature sf = sfs.get(0);
assertEquals("type2", sf.getType());
assertEquals("desc2", sf.getDescription());
assertEquals(2f, sf.getScore());
assertEquals(1, sf.getBegin());
assertEquals(1, sf.getEnd());
- sf = sfs[1];
+ sf = sfs.get(1);
assertEquals("type3", sf.getType());
assertEquals("desc3", sf.getDescription());
assertEquals(3f, sf.getScore());
assertEquals(1, sf.getBegin());
assertEquals(3, sf.getEnd());
- sf = sfs[2];
+ sf = sfs.get(2);
assertEquals("type4", sf.getType());
assertEquals(2, sf.getBegin());
assertEquals(5, sf.getEnd());
- sf = sfs[3];
+ sf = sfs.get(3);
assertEquals("type5", sf.getType());
assertEquals(1, sf.getBegin());
assertEquals(6, sf.getEnd());
- sf = sfs[4];
+ sf = sfs.get(4);
assertEquals("type8", sf.getType());
assertEquals(6, sf.getBegin());
assertEquals(6, sf.getEnd());
- sf = sfs[5];
+ sf = sfs.get(5);
assertEquals("type9", sf.getType());
assertEquals(6, sf.getBegin());
assertEquals(6, sf.getEnd());
// desc4 and desc8 are the 'omit these' varargs
AlignmentUtils.transferFeatures(dna, cds, map, null, "type4", "type8");
- SequenceFeature[] sfs = cds.getSequenceFeatures();
- assertEquals(1, sfs.length);
+ List<SequenceFeature> sfs = cds.getSequenceFeatures();
+ assertEquals(1, sfs.size());
- SequenceFeature sf = sfs[0];
+ SequenceFeature sf = sfs.get(0);
assertEquals("type5", sf.getType());
assertEquals(1, sf.getBegin());
assertEquals(6, sf.getEnd());
// "type5" is the 'select this type' argument
AlignmentUtils.transferFeatures(dna, cds, map, "type5");
- SequenceFeature[] sfs = cds.getSequenceFeatures();
- assertEquals(1, sfs.length);
+ List<SequenceFeature> sfs = cds.getSequenceFeatures();
+ assertEquals(1, sfs.size());
- SequenceFeature sf = sfs[0];
+ SequenceFeature sf = sfs.get(0);
assertEquals("type5", sf.getType());
assertEquals(1, sf.getBegin());
assertEquals(6, sf.getEnd());
* var6 P -> H COSMIC
* var6 P -> R COSMIC
*/
- SequenceFeature[] sfs = peptide.getSequenceFeatures();
- assertEquals(5, sfs.length);
+ List<SequenceFeature> sfs = peptide.getSequenceFeatures();
+ SequenceFeatures.sortFeatures(sfs, true);
+ assertEquals(5, sfs.size());
- SequenceFeature sf = sfs[0];
+ /*
+ * features are sorted by start position ascending, but in no
+ * particular order where start positions match; asserts here
+ * simply match the data returned (the order is not important)
+ */
+ SequenceFeature sf = sfs.get(0);
assertEquals(1, sf.getBegin());
assertEquals(1, sf.getEnd());
- assertEquals("p.Lys1Glu", sf.getDescription());
- assertEquals("var1.125A>G", sf.getValue("ID"));
- assertNull(sf.getValue("clinical_significance"));
- assertEquals("ID=var1.125A>G", sf.getAttributes());
+ assertEquals("p.Lys1Asn", sf.getDescription());
+ assertEquals("var4", sf.getValue("ID"));
+ assertEquals("Benign", sf.getValue("clinical_significance"));
+ assertEquals("ID=var4;clinical_significance=Benign", sf.getAttributes());
assertEquals(1, sf.links.size());
- // link to variation is urlencoded
assertEquals(
- "p.Lys1Glu var1.125A>G|http://www.ensembl.org/Homo_sapiens/Variation/Summary?v=var1.125A%3EG",
+ "p.Lys1Asn var4|http://www.ensembl.org/Homo_sapiens/Variation/Summary?v=var4",
sf.links.get(0));
assertEquals(ensembl, sf.getFeatureGroup());
- sf = sfs[1];
+ sf = sfs.get(1);
assertEquals(1, sf.getBegin());
assertEquals(1, sf.getEnd());
assertEquals("p.Lys1Gln", sf.getDescription());
sf.links.get(0));
assertEquals(dbSnp, sf.getFeatureGroup());
- sf = sfs[2];
+ sf = sfs.get(2);
assertEquals(1, sf.getBegin());
assertEquals(1, sf.getEnd());
- assertEquals("p.Lys1Asn", sf.getDescription());
- assertEquals("var4", sf.getValue("ID"));
- assertEquals("Benign", sf.getValue("clinical_significance"));
- assertEquals("ID=var4;clinical_significance=Benign", sf.getAttributes());
+ assertEquals("p.Lys1Glu", sf.getDescription());
+ assertEquals("var1.125A>G", sf.getValue("ID"));
+ assertNull(sf.getValue("clinical_significance"));
+ assertEquals("ID=var1.125A>G", sf.getAttributes());
assertEquals(1, sf.links.size());
+ // link to variation is urlencoded
assertEquals(
- "p.Lys1Asn var4|http://www.ensembl.org/Homo_sapiens/Variation/Summary?v=var4",
+ "p.Lys1Glu var1.125A>G|http://www.ensembl.org/Homo_sapiens/Variation/Summary?v=var1.125A%3EG",
sf.links.get(0));
assertEquals(ensembl, sf.getFeatureGroup());
- // var5 generates two distinct protein variant features
- sf = sfs[3];
+ sf = sfs.get(3);
assertEquals(3, sf.getBegin());
assertEquals(3, sf.getEnd());
- assertEquals("p.Pro3His", sf.getDescription());
+ assertEquals("p.Pro3Arg", sf.getDescription());
assertEquals("var6", sf.getValue("ID"));
assertEquals("Good", sf.getValue("clinical_significance"));
assertEquals("ID=var6;clinical_significance=Good", sf.getAttributes());
assertEquals(1, sf.links.size());
assertEquals(
- "p.Pro3His var6|http://www.ensembl.org/Homo_sapiens/Variation/Summary?v=var6",
+ "p.Pro3Arg var6|http://www.ensembl.org/Homo_sapiens/Variation/Summary?v=var6",
sf.links.get(0));
assertEquals(cosmic, sf.getFeatureGroup());
- sf = sfs[4];
+ // var5 generates two distinct protein variant features
+ sf = sfs.get(4);
assertEquals(3, sf.getBegin());
assertEquals(3, sf.getEnd());
- assertEquals("p.Pro3Arg", sf.getDescription());
+ assertEquals("p.Pro3His", sf.getDescription());
assertEquals("var6", sf.getValue("ID"));
assertEquals("Good", sf.getValue("clinical_significance"));
assertEquals("ID=var6;clinical_significance=Good", sf.getAttributes());
assertEquals(1, sf.links.size());
assertEquals(
- "p.Pro3Arg var6|http://www.ensembl.org/Homo_sapiens/Variation/Summary?v=var6",
+ "p.Pro3His var6|http://www.ensembl.org/Homo_sapiens/Variation/Summary?v=var6",
sf.links.get(0));
assertEquals(cosmic, sf.getFeatureGroup());
}
import static org.testng.AssertJUnit.fail;
import jalview.analysis.SecStrConsensus.SimpleBP;
+import jalview.datamodel.SequenceFeature;
import jalview.gui.JvOptionPane;
-import java.util.Vector;
+import java.util.List;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
public void testGetSimpleBPs() throws WUSSParseException
{
String rna = "([{})]"; // JAL-1081 example
- Vector<SimpleBP> bps = Rna.getSimpleBPs(rna);
+ List<SimpleBP> bps = Rna.getSimpleBPs(rna);
assertEquals(3, bps.size());
/*
.valueOf((char) i) + " "));
}
}
+
+ @Test(groups = "Functional")
+ public void testGetHelixMap_oneHelix() throws WUSSParseException
+ {
+ String rna = ".(..[{.<..>}..].)";
+ SequenceFeature[] sfs = Rna.getHelixMap(rna);
+ assertEquals(4, sfs.length);
+
+ /*
+ * pairs are added in the order in which the closing bracket is found
+ * (see testGetSimpleBPs)
+ */
+ assertEquals(7, sfs[0].getBegin());
+ assertEquals(10, sfs[0].getEnd());
+ assertEquals("0", sfs[0].getFeatureGroup());
+ assertEquals(5, sfs[1].getBegin());
+ assertEquals(11, sfs[1].getEnd());
+ assertEquals("0", sfs[1].getFeatureGroup());
+ assertEquals(4, sfs[2].getBegin());
+ assertEquals(14, sfs[2].getEnd());
+ assertEquals("0", sfs[2].getFeatureGroup());
+ assertEquals(1, sfs[3].getBegin());
+ assertEquals(16, sfs[3].getEnd());
+ assertEquals("0", sfs[3].getFeatureGroup());
+ }
+
+ @Test(groups = "Functional")
+ public void testGetHelixMap_twoHelices() throws WUSSParseException
+ {
+ String rna = ".([.)]..{.<}.>";
+ SequenceFeature[] sfs = Rna.getHelixMap(rna);
+ assertEquals(4, sfs.length);
+
+ /*
+ * pairs are added in the order in which the closing bracket is found
+ * (see testGetSimpleBPs)
+ */
+ assertEquals(1, sfs[0].getBegin());
+ assertEquals(4, sfs[0].getEnd());
+ assertEquals("0", sfs[0].getFeatureGroup());
+ assertEquals(2, sfs[1].getBegin());
+ assertEquals(5, sfs[1].getEnd());
+ assertEquals("0", sfs[1].getFeatureGroup());
+ assertEquals(8, sfs[2].getBegin());
+ assertEquals(11, sfs[2].getEnd());
+ assertEquals("1", sfs[2].getFeatureGroup());
+ assertEquals(10, sfs[3].getBegin());
+ assertEquals(13, sfs[3].getEnd());
+ assertEquals("1", sfs[3].getFeatureGroup());
+ }
}
AlignmentI al = new Alignment(sqset);
al.setDataset(null);
AlignmentI ds = al.getDataset();
- SequenceFeature sf1 = new SequenceFeature("f1", "foo", "bleh", 2, 3,
- "far"), sf2 = new SequenceFeature("f2", "foo", "bleh", 2, 3,
- "far");
+ SequenceFeature sf1 = new SequenceFeature("f1", "foo", 2, 3, "far");
+ SequenceFeature sf2 = new SequenceFeature("f2", "foo", 2, 3, "far");
ds.getSequenceAt(0).addSequenceFeature(sf1);
Hashtable unq = SeqsetUtils.uniquify(sqset, true);
SequenceI[] sqset2 = new SequenceI[] {
new Sequence(sqset[0].getName(), sqset[0].getSequenceAsString()),
new Sequence(sqset[1].getName(), sqset[1].getSequenceAsString()) };
- Assert.assertTrue(sqset[0].getSequenceFeatures()[0] == sf1);
- Assert.assertEquals(sqset2[0].getSequenceFeatures(), null);
+ Assert.assertSame(sqset[0].getSequenceFeatures().get(0), sf1);
+ Assert.assertTrue(sqset2[0].getSequenceFeatures().isEmpty());
ds.getSequenceAt(0).addSequenceFeature(sf2);
- Assert.assertEquals(sqset[0].getSequenceFeatures().length, 2);
+ Assert.assertEquals(sqset[0].getSequenceFeatures().size(), 2);
SeqsetUtils.deuniquify(unq, sqset2);
// explicitly test that original sequence features still exist because they
// are on the shared dataset sequence
- Assert.assertEquals(sqset[0].getSequenceFeatures().length, 2);
- Assert.assertEquals(sqset2[0].getSequenceFeatures().length, 2);
- Assert.assertTrue(sqset[0].getSequenceFeatures()[0] == sqset2[0]
- .getSequenceFeatures()[0]);
- Assert.assertTrue(sqset[0].getSequenceFeatures()[1] == sqset2[0]
- .getSequenceFeatures()[1]);
+ Assert.assertEquals(sqset[0].getSequenceFeatures().size(), 2);
+ Assert.assertEquals(sqset2[0].getSequenceFeatures().size(), 2);
+ Assert.assertSame(sqset[0].getSequenceFeatures().get(0), sqset2[0]
+ .getSequenceFeatures().get(0));
+ Assert.assertSame(sqset[0].getSequenceFeatures().get(1), sqset2[0]
+ .getSequenceFeatures().get(1));
}
}
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertTrue;
+import jalview.api.analysis.ScoreModelI;
import jalview.api.analysis.SimilarityParamsI;
import jalview.datamodel.Alignment;
import jalview.datamodel.AlignmentI;
SequenceI ds = al.getSequenceAt(i).getDatasetSequence();
if (sf1[i * 2] > 0)
{
- ds.addSequenceFeature(new SequenceFeature("sf1", "sf1", "sf1",
- sf1[i * 2], sf1[i * 2 + 1], "sf1"));
+ ds.addSequenceFeature(new SequenceFeature("sf1", "sf1", sf1[i * 2],
+ sf1[i * 2 + 1], "sf1"));
}
if (sf2[i * 2] > 0)
{
- ds.addSequenceFeature(new SequenceFeature("sf2", "sf2", "sf2",
- sf2[i * 2], sf2[i * 2 + 1], "sf2"));
+ ds.addSequenceFeature(new SequenceFeature("sf2", "sf2", sf2[i * 2],
+ sf2[i * 2 + 1], "sf2"));
}
if (sf3[i * 2] > 0)
{
- ds.addSequenceFeature(new SequenceFeature("sf3", "sf3", "sf3",
- sf3[i * 2], sf3[i * 2 + 1], "sf3"));
+ ds.addSequenceFeature(new SequenceFeature("sf3", "sf3", sf3[i * 2],
+ sf3[i * 2 + 1], "sf3"));
}
}
alf.setShowSeqFeatures(true);
public void testFeatureScoreModel() throws Exception
{
AlignFrame alf = getTestAlignmentFrame();
- FeatureDistanceModel fsm = new FeatureDistanceModel();
- assertTrue(fsm.configureFromAlignmentView(alf.getCurrentView()
- .getAlignPanel()));
+ ScoreModelI sm = new FeatureDistanceModel();
+ sm = ScoreModels.getInstance().getScoreModel(sm.getName(),
+ alf.getCurrentView().getAlignPanel());
alf.selectAllSequenceMenuItem_actionPerformed(null);
- MatrixI dm = fsm.findDistances(
+ MatrixI dm = sm.findDistances(
alf.getViewport().getAlignmentView(true),
SimilarityParams.Jalview);
assertEquals(dm.getValue(0, 2), 0d,
AlignFrame alf = getTestAlignmentFrame();
// hiding first two columns shouldn't affect the tree
alf.getViewport().hideColumns(0, 1);
- FeatureDistanceModel fsm = new FeatureDistanceModel();
- assertTrue(fsm.configureFromAlignmentView(alf.getCurrentView()
- .getAlignPanel()));
+ ScoreModelI sm = new FeatureDistanceModel();
+ sm = ScoreModels.getInstance().getScoreModel(sm.getName(),
+ alf.getCurrentView().getAlignPanel());
alf.selectAllSequenceMenuItem_actionPerformed(null);
- MatrixI dm = fsm.findDistances(
+ MatrixI dm = sm.findDistances(
alf.getViewport().getAlignmentView(true),
SimilarityParams.Jalview);
assertEquals(dm.getValue(0, 2), 0d,
// hide columns and check tree changes
alf.getViewport().hideColumns(3, 4);
alf.getViewport().hideColumns(0, 1);
- FeatureDistanceModel fsm = new FeatureDistanceModel();
- assertTrue(fsm.configureFromAlignmentView(alf.getCurrentView()
- .getAlignPanel()));
+ // getName() can become static in Java 8
+ ScoreModelI sm = new FeatureDistanceModel();
+ sm = ScoreModels.getInstance().getScoreModel(sm.getName(),
+ alf.getCurrentView().getAlignPanel());
alf.selectAllSequenceMenuItem_actionPerformed(null);
- MatrixI dm = fsm.findDistances(
+ MatrixI dm = sm.findDistances(
alf.getViewport().getAlignmentView(true),
SimilarityParams.Jalview);
assertEquals(
Assert.assertEquals(af.getFeatureRenderer().getDisplayedFeatureTypes()
.size(), 1, "Should be just one feature type displayed");
// step through and check for pointwise feature presence/absence
- Assert.assertEquals(af.getFeatureRenderer().findFeaturesAtRes(aseq, 1)
+ Assert.assertEquals(af.getFeatureRenderer().findFeaturesAtColumn(aseq, 1)
.size(), 0);
// step through and check for pointwise feature presence/absence
- Assert.assertEquals(af.getFeatureRenderer().findFeaturesAtRes(aseq, 2)
+ Assert.assertEquals(af.getFeatureRenderer().findFeaturesAtColumn(aseq, 2)
.size(), 1);
// step through and check for pointwise feature presence/absence
- Assert.assertEquals(af.getFeatureRenderer().findFeaturesAtRes(aseq, 3)
+ Assert.assertEquals(af.getFeatureRenderer().findFeaturesAtColumn(aseq, 3)
.size(), 0);
// step through and check for pointwise feature presence/absence
- Assert.assertEquals(af.getFeatureRenderer().findFeaturesAtRes(aseq, 4)
+ Assert.assertEquals(af.getFeatureRenderer().findFeaturesAtColumn(aseq, 4)
.size(), 0);
// step through and check for pointwise feature presence/absence
- Assert.assertEquals(af.getFeatureRenderer().findFeaturesAtRes(aseq, 5)
+ Assert.assertEquals(af.getFeatureRenderer().findFeaturesAtColumn(aseq, 5)
.size(), 1);
// step through and check for pointwise feature presence/absence
- Assert.assertEquals(af.getFeatureRenderer().findFeaturesAtRes(aseq, 6)
+ Assert.assertEquals(af.getFeatureRenderer().findFeaturesAtColumn(aseq, 6)
.size(), 0);
}
alf.setShowSeqFeatures(true);
alf.getFeatureRenderer().findAllFeatures(true);
- FeatureDistanceModel fsm = new FeatureDistanceModel();
- assertTrue(fsm.configureFromAlignmentView(alf.getCurrentView()
- .getAlignPanel()));
+ ScoreModelI sm = new FeatureDistanceModel();
+ sm = ScoreModels.getInstance().getScoreModel(sm.getName(),
+ alf.getCurrentView().getAlignPanel());
alf.selectAllSequenceMenuItem_actionPerformed(null);
- MatrixI distances = fsm.findDistances(alf.getViewport()
- .getAlignmentView(true), SimilarityParams.Jalview);
+ AlignmentView alignmentView = alf.getViewport()
+ .getAlignmentView(true);
+ MatrixI distances = sm.findDistances(alignmentView,
+ SimilarityParams.Jalview);
assertEquals(distances.width(), 2);
assertEquals(distances.height(), 2);
assertEquals(distances.getValue(0, 0), 0d);
AlignViewport viewport = af.getViewport();
AlignmentView view = viewport.getAlignmentView(false);
- FeatureDistanceModel sm = new FeatureDistanceModel();
- sm.configureFromAlignmentView(af.alignPanel);
-
+ ScoreModelI sm = new FeatureDistanceModel();
+ sm = ScoreModels.getInstance().getScoreModel(sm.getName(),
+ af.alignPanel);
+
/*
* feature distance model always normalises by region width
* gap-gap is always included (but scores zero)
package jalview.commands;
import static org.testng.AssertJUnit.assertEquals;
+import static org.testng.AssertJUnit.assertNull;
import static org.testng.AssertJUnit.assertSame;
import jalview.commands.EditCommand.Action;
import jalview.datamodel.Alignment;
import jalview.datamodel.AlignmentI;
import jalview.datamodel.Sequence;
+import jalview.datamodel.SequenceFeature;
import jalview.datamodel.SequenceI;
+import jalview.datamodel.features.SequenceFeatures;
import jalview.gui.JvOptionPane;
+import java.util.List;
import java.util.Map;
+import org.testng.Assert;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
*/
public class EditCommandTest
{
+ /*
+ * compute n(n+1)/2 e.g.
+ * func(5) = 5 + 4 + 3 + 2 + 1 = 15
+ */
+ private static int func(int i)
+ {
+ return i * (i + 1) / 2;
+ }
@BeforeClass(alwaysRun = true)
public void setUpJvOptionPane()
assertEquals(ds2, unwound.get(ds2).getDatasetSequence());
assertEquals(ds3, unwound.get(ds3).getDatasetSequence());
}
+
+ /**
+ * Test a cut action's relocation of sequence features
+ */
+ @Test(groups = { "Functional" })
+ public void testCut_withFeatures()
+ {
+ /*
+ * create sequence features before, after and overlapping
+ * a cut of columns/residues 4-7
+ */
+ SequenceI seq0 = seqs[0];
+ seq0.addSequenceFeature(new SequenceFeature("before", "", 1, 3, 0f,
+ null));
+ seq0.addSequenceFeature(new SequenceFeature("overlap left", "", 2, 6,
+ 0f, null));
+ seq0.addSequenceFeature(new SequenceFeature("internal", "", 5, 6, 0f,
+ null));
+ seq0.addSequenceFeature(new SequenceFeature("overlap right", "", 7, 8,
+ 0f, null));
+ seq0.addSequenceFeature(new SequenceFeature("after", "", 8, 10, 0f,
+ null));
+
+ Edit ec = testee.new Edit(Action.CUT, seqs, 3, 4, al); // cols 3-6 base 0
+ EditCommand.cut(ec, new AlignmentI[] { al });
+
+ List<SequenceFeature> sfs = seq0.getSequenceFeatures();
+ SequenceFeatures.sortFeatures(sfs, true);
+
+ assertEquals(4, sfs.size()); // feature internal to cut has been deleted
+ SequenceFeature sf = sfs.get(0);
+ assertEquals("before", sf.getType());
+ assertEquals(1, sf.getBegin());
+ assertEquals(3, sf.getEnd());
+ sf = sfs.get(1);
+ assertEquals("overlap left", sf.getType());
+ assertEquals(2, sf.getBegin());
+ assertEquals(3, sf.getEnd()); // truncated by cut
+ sf = sfs.get(2);
+ assertEquals("overlap right", sf.getType());
+ assertEquals(4, sf.getBegin()); // shifted left by cut
+ assertEquals(5, sf.getEnd()); // truncated by cut
+ sf = sfs.get(3);
+ assertEquals("after", sf.getType());
+ assertEquals(4, sf.getBegin()); // shifted left by cut
+ assertEquals(6, sf.getEnd()); // shifted left by cut
+ }
+
+ /**
+ * Test a cut action's relocation of sequence features, with full coverage of
+ * all possible feature and cut locations for a 5-position ungapped sequence
+ */
+ @Test(groups = { "Functional" })
+ public void testCut_withFeatures_exhaustive()
+ {
+ /*
+ * create a sequence features on each subrange of 1-5
+ */
+ SequenceI seq0 = new Sequence("seq", "ABCDE");
+ AlignmentI alignment = new Alignment(new SequenceI[] { seq0 });
+ alignment.setDataset(null);
+ for (int from = 1; from <= seq0.getLength(); from++)
+ {
+ for (int to = from; to <= seq0.getLength(); to++)
+ {
+ String desc = String.format("%d-%d", from, to);
+ SequenceFeature sf = new SequenceFeature("test", desc, from, to,
+ 0f,
+ null);
+ sf.setValue("from", Integer.valueOf(from));
+ sf.setValue("to", Integer.valueOf(to));
+ seq0.addSequenceFeature(sf);
+ }
+ }
+ // sanity check
+ List<SequenceFeature> sfs = seq0.getSequenceFeatures();
+ assertEquals(func(5), sfs.size());
+
+ /*
+ * now perform all possible cuts of subranges of 1-5 (followed by Undo)
+ * and validate the resulting remaining sequence features!
+ */
+ SequenceI[] sqs = new SequenceI[] { seq0 };
+
+ // goal is to have this passing for all from/to values!!
+ // for (int from = 0; from < seq0.getLength(); from++)
+ // {
+ // for (int to = from; to < seq0.getLength(); to++)
+ for (int from = 1; from < 3; from++)
+ {
+ for (int to = 2; to < 3; to++)
+ {
+ testee.appendEdit(Action.CUT, sqs, from, (to - from + 1),
+ alignment, true);
+
+ sfs = seq0.getSequenceFeatures();
+
+ /*
+ * confirm the number of features has reduced by the
+ * number of features within the cut region i.e. by
+ * func(length of cut)
+ */
+ String msg = String.format("Cut %d-%d ", from, to);
+ if (to - from == 4)
+ {
+ // all columns cut
+ assertNull(sfs);
+ }
+ else
+ {
+ assertEquals(msg + "wrong number of features left", func(5)
+ - func(to - from + 1), sfs.size());
+ }
+
+ /*
+ * inspect individual features
+ */
+ if (sfs != null)
+ {
+ for (SequenceFeature sf : sfs)
+ {
+ checkFeatureRelocation(sf, from + 1, to + 1);
+ }
+ }
+ /*
+ * undo ready for next cut
+ */
+ testee.undoCommand(new AlignmentI[] { alignment });
+ assertEquals(func(5), seq0.getSequenceFeatures().size());
+ }
+ }
+ }
+
+ /**
+ * Helper method to check a feature has been correctly relocated after a cut
+ *
+ * @param sf
+ * @param from
+ * start of cut (first residue cut)
+ * @param to
+ * end of cut (last residue cut)
+ */
+ private void checkFeatureRelocation(SequenceFeature sf, int from, int to)
+ {
+ // TODO handle the gapped sequence case as well
+ int cutSize = to - from + 1;
+ int oldFrom = ((Integer) sf.getValue("from")).intValue();
+ int oldTo = ((Integer) sf.getValue("to")).intValue();
+
+ String msg = String.format(
+ "Feature %s relocated to %d-%d after cut of %d-%d",
+ sf.getDescription(), sf.getBegin(), sf.getEnd(), from, to);
+ if (oldTo < from)
+ {
+ // before cut region so unchanged
+ assertEquals("1: " + msg, oldFrom, sf.getBegin());
+ assertEquals("2: " + msg, oldTo, sf.getEnd());
+ }
+ else if (oldFrom > to)
+ {
+ // follows cut region - shift by size of cut
+ assertEquals("3: " + msg, oldFrom - cutSize, sf.getBegin());
+ assertEquals("4: " + msg, oldTo - cutSize, sf.getEnd());
+ }
+ else if (oldFrom < from && oldTo > to)
+ {
+ // feature encloses cut region - shrink it right
+ assertEquals("5: " + msg, oldFrom, sf.getBegin());
+ assertEquals("6: " + msg, oldTo - cutSize, sf.getEnd());
+ }
+ else if (oldFrom < from)
+ {
+ // feature overlaps left side of cut region - truncated right
+ assertEquals("7: " + msg, from - 1, sf.getEnd());
+ }
+ else if (oldTo > to)
+ {
+ // feature overlaps right side of cut region - truncated left
+ assertEquals("8: " + msg, from, sf.getBegin());
+ assertEquals("9: " + msg, from + oldTo - to - 1, sf.getEnd());
+ }
+ else
+ {
+ // feature internal to cut - should have been deleted!
+ Assert.fail(msg + " - should have been deleted");
+ }
+ }
+
+ /**
+ * Test a cut action's relocation of sequence features
+ */
+ @Test(groups = { "Functional" })
+ public void testCut_gappedWithFeatures()
+ {
+ /*
+ * create sequence features before, after and overlapping
+ * a cut of columns/residues 4-7
+ */
+ SequenceI seq0 = new Sequence("seq", "A-BCC");
+ seq0.addSequenceFeature(new SequenceFeature("", "", 3, 4, 0f,
+ null));
+ AlignmentI alignment = new Alignment(new SequenceI[] { seq0 });
+ // cut columns of A-B
+ Edit ec = testee.new Edit(Action.CUT, seqs, 0, 3, alignment); // cols 0-3
+ // base 0
+ EditCommand.cut(ec, new AlignmentI[] { alignment });
+
+ /*
+ * feature on CC(3-4) should now be on CC(1-2)
+ */
+ List<SequenceFeature> sfs = seq0.getSequenceFeatures();
+ assertEquals(1, sfs.size());
+ SequenceFeature sf = sfs.get(0);
+ assertEquals(1, sf.getBegin());
+ assertEquals(2, sf.getEnd());
+
+ // TODO add further cases including Undo - see JAL-2541
+ }
}
AlignmentI alignment = new Alignment(new SequenceI[] { seq });
alignment.setDataset(alignment);
}
+
+ @Test(groups = "Functional")
+ public void testAppend()
+ {
+ SequenceI seq = new Sequence("seq1", "FRMLPSRT-A--L-");
+ AlignmentI alignment = new Alignment(new SequenceI[] { seq });
+ alignment.setGapCharacter('-');
+ SequenceI seq2 = new Sequence("seq1", "KP..L.FQII.");
+ AlignmentI alignment2 = new Alignment(new SequenceI[] { seq2 });
+ alignment2.setGapCharacter('.');
+
+ alignment.append(alignment2);
+
+ assertEquals('-', alignment.getGapCharacter());
+ assertSame(seq, alignment.getSequenceAt(0));
+ assertEquals("KP--L-FQII-", alignment.getSequenceAt(1)
+ .getSequenceAsString());
+
+ // todo test coverage for annotations, mappings, groups,
+ // hidden sequences, properties
+ }
}
/*
* TODO: can we add assertions to the sysouts that follow?
*/
- System.out.println("Original sequence align:\n" + sub_gapped_s
+ System.out.println("\nOriginal sequence align:\n" + sub_gapped_s
+ "\nReconstructed window from 8 to 48\n" + "XXXXXXXX"
+ sub_se_gp.getSequenceString('-') + "..." + "\nCigar String:"
+ sub_se_gp.getCigarstring() + "\n");
SequenceI gen_sgapped_s = gen_sgapped.getSeq('-');
// assertEquals("Couldn't reconstruct sequence", s_gapped.getSequence(),
// gen_sgapped_s);
- if (!gen_sgapped_s.getSequence().equals(s_gapped.getSequence()))
+ if (!gen_sgapped_s.getSequenceAsString().equals(
+ s_gapped.getSequenceAsString()))
{
// TODO: investigate errors reported here, to allow full conversion to
// passing JUnit assertion form
}
@Test(groups = { "Functional" })
- public void testCopyConstructor()
+ public void testCopyConstructors()
{
SequenceFeature sf1 = new SequenceFeature("type", "desc", 22, 33,
12.5f, "group");
assertEquals("desc", sf2.getDescription());
assertEquals(22, sf2.getBegin());
assertEquals(33, sf2.getEnd());
+ assertEquals(12.5f, sf2.getScore());
assertEquals("+", sf2.getValue("STRAND"));
assertEquals("Testing", sf2.getValue("Note"));
// shallow clone of otherDetails map - contains the same object values!
assertSame(count, sf2.getValue("Count"));
+
+ /*
+ * copy constructor modifying begin/end/group/score
+ */
+ SequenceFeature sf3 = new SequenceFeature(sf1, 11, 14, "group2", 17.4f);
+ assertEquals("type", sf3.getType());
+ assertEquals("desc", sf3.getDescription());
+ assertEquals(11, sf3.getBegin());
+ assertEquals(14, sf3.getEnd());
+ assertEquals(17.4f, sf3.getScore());
+ assertEquals("+", sf3.getValue("STRAND"));
+ assertEquals("Testing", sf3.getValue("Note"));
+ // shallow clone of otherDetails map - contains the same object values!
+ assertSame(count, sf3.getValue("Count"));
+
+ /*
+ * copy constructor modifying type/begin/end/group/score
+ */
+ SequenceFeature sf4 = new SequenceFeature(sf1, "Disulfide bond", 12,
+ 15, "group3", -9.1f);
+ assertEquals("Disulfide bond", sf4.getType());
+ assertTrue(sf4.isContactFeature());
+ assertEquals("desc", sf4.getDescription());
+ assertEquals(12, sf4.getBegin());
+ assertEquals(15, sf4.getEnd());
+ assertEquals(-9.1f, sf4.getScore());
+ assertEquals("+", sf4.getValue("STRAND"));
+ assertEquals("Testing", sf4.getValue("Note"));
+ // shallow clone of otherDetails map - contains the same object values!
+ assertSame(count, sf4.getValue("Count"));
}
/**
assertEquals(sf1.hashCode(), sf2.hashCode());
// changing type breaks equals:
- String restores = sf2.getType();
- sf2.setType("Type");
- assertFalse(sf1.equals(sf2));
- sf2.setType(restores);
+ SequenceFeature sf3 = new SequenceFeature("type", "desc", 22, 33,
+ 12.5f, "group");
+ SequenceFeature sf4 = new SequenceFeature("Type", "desc", 22, 33,
+ 12.5f, "group");
+ assertFalse(sf3.equals(sf4));
// changing description breaks equals:
- restores = sf2.getDescription();
+ String restores = sf2.getDescription();
sf2.setDescription("Desc");
assertFalse(sf1.equals(sf2));
sf2.setDescription(restores);
// changing score breaks equals:
float restoref = sf2.getScore();
- sf2.setScore(12.4f);
+ sf2 = new SequenceFeature(sf2, sf2.getBegin(), sf2.getEnd(),
+ sf2.getFeatureGroup(), 10f);
assertFalse(sf1.equals(sf2));
- sf2.setScore(restoref);
+ sf2 = new SequenceFeature(sf2, sf2.getBegin(), sf2.getEnd(),
+ sf2.getFeatureGroup(), restoref);
// NaN doesn't match a number
restoref = sf2.getScore();
- sf2.setScore(Float.NaN);
+ sf2 = new SequenceFeature(sf2, sf2.getBegin(), sf2.getEnd(),
+ sf2.getFeatureGroup(), Float.NaN);
assertFalse(sf1.equals(sf2));
// NaN matches NaN
- sf1.setScore(Float.NaN);
+ sf1 = new SequenceFeature(sf1, sf1.getBegin(), sf1.getEnd(),
+ sf1.getFeatureGroup(), Float.NaN);
assertTrue(sf1.equals(sf2));
- sf1.setScore(restoref);
- sf2.setScore(restoref);
+ sf1 = new SequenceFeature(sf1, sf1.getBegin(), sf1.getEnd(),
+ sf1.getFeatureGroup(), restoref);
+ sf2 = new SequenceFeature(sf2, sf2.getBegin(), sf2.getEnd(),
+ sf2.getFeatureGroup(), restoref);
// changing start position breaks equals:
int restorei = sf2.getBegin();
- sf2.setBegin(21);
+ sf2 = new SequenceFeature(sf2, 21, sf2.getEnd(), sf2.getFeatureGroup(), sf2.getScore());
assertFalse(sf1.equals(sf2));
- sf2.setBegin(restorei);
+ sf2 = new SequenceFeature(sf2, restorei, sf2.getEnd(),
+ sf2.getFeatureGroup(), sf2.getScore());
// changing end position breaks equals:
restorei = sf2.getEnd();
- sf2.setEnd(32);
+ sf2 = new SequenceFeature(sf2, sf2.getBegin(), 32,
+ sf2.getFeatureGroup(), sf2.getScore());
assertFalse(sf1.equals(sf2));
- sf2.setEnd(restorei);
+ sf2 = new SequenceFeature(sf2, sf2.getBegin(), restorei,
+ sf2.getFeatureGroup(), sf2.getScore());
// changing feature group breaks equals:
restores = sf2.getFeatureGroup();
- sf2.setFeatureGroup("Group");
+ sf2 = new SequenceFeature(sf2, sf2.getBegin(), sf2.getEnd(), "Group", sf2.getScore());
assertFalse(sf1.equals(sf2));
- sf2.setFeatureGroup(restores);
+ sf2 = new SequenceFeature(sf2, sf2.getBegin(), sf2.getEnd(), restores, sf2.getScore());
// changing ID breaks equals:
restores = (String) sf2.getValue("ID");
SequenceFeature sf = new SequenceFeature("type", "desc", 22, 33, 12.5f,
"group");
assertFalse(sf.isContactFeature());
- sf.setType("");
+ sf = new SequenceFeature("", "desc", 22, 33, 12.5f, "group");
assertFalse(sf.isContactFeature());
- sf.setType(null);
+ sf = new SequenceFeature(null, "desc", 22, 33, 12.5f, "group");
assertFalse(sf.isContactFeature());
- sf.setType("Disulfide Bond");
+ sf = new SequenceFeature("Disulfide Bond", "desc", 22, 33, 12.5f,
+ "group");
assertTrue(sf.isContactFeature());
- sf.setType("disulfide bond");
+ sf = new SequenceFeature("disulfide bond", "desc", 22, 33, 12.5f,
+ "group");
assertTrue(sf.isContactFeature());
- sf.setType("Disulphide Bond");
+ sf = new SequenceFeature("Disulphide Bond", "desc", 22, 33, 12.5f,
+ "group");
assertTrue(sf.isContactFeature());
- sf.setType("disulphide bond");
+ sf = new SequenceFeature("disulphide bond", "desc", 22, 33, 12.5f,
+ "group");
assertTrue(sf.isContactFeature());
}
}
import static org.testng.AssertJUnit.assertEquals;
import static org.testng.AssertJUnit.assertFalse;
import static org.testng.AssertJUnit.assertNotNull;
+import static org.testng.AssertJUnit.assertNotSame;
import static org.testng.AssertJUnit.assertNull;
import static org.testng.AssertJUnit.assertSame;
import static org.testng.AssertJUnit.assertTrue;
-import static org.testng.internal.junit.ArrayAsserts.assertArrayEquals;
+import jalview.commands.EditCommand;
+import jalview.commands.EditCommand.Action;
import jalview.datamodel.PDBEntry.Type;
import jalview.gui.JvOptionPane;
import jalview.util.MapList;
import java.util.List;
import java.util.Vector;
+import junit.extensions.PA;
+
import org.testng.Assert;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.BeforeMethod;
// change sequence, should trigger an update of cached result
sq.setSequence("ASDFASDFADSF");
assertTrue(sq.isProtein());
- /*
- * in situ change of sequence doesn't change hashcode :-O
- * (sequence should not expose internal implementation)
- */
- for (int i = 0; i < sq.getSequence().length; i++)
- {
- sq.getSequence()[i] = "acgtu".charAt(i % 5);
- }
- assertTrue(sq.isProtein()); // but it isn't
}
@Test(groups = { "Functional" })
@Test(groups = { "Functional" })
public void testFindIndex()
{
+ /*
+ * call sequenceChanged() after each test to invalidate any cursor,
+ * forcing the 1-arg findIndex to be executed
+ */
SequenceI sq = new Sequence("test", "ABCDEF");
assertEquals(0, sq.findIndex(0));
+ sq.sequenceChanged();
assertEquals(1, sq.findIndex(1));
+ sq.sequenceChanged();
assertEquals(5, sq.findIndex(5));
+ sq.sequenceChanged();
assertEquals(6, sq.findIndex(6));
+ sq.sequenceChanged();
assertEquals(6, sq.findIndex(9));
- sq = new Sequence("test", "-A--B-C-D-E-F--");
- assertEquals(2, sq.findIndex(1));
- assertEquals(5, sq.findIndex(2));
- assertEquals(7, sq.findIndex(3));
+ sq = new Sequence("test/8-13", "-A--B-C-D-E-F--");
+ assertEquals(2, sq.findIndex(8));
+ sq.sequenceChanged();
+ assertEquals(5, sq.findIndex(9));
+ sq.sequenceChanged();
+ assertEquals(7, sq.findIndex(10));
// before start returns 0
+ sq.sequenceChanged();
assertEquals(0, sq.findIndex(0));
+ sq.sequenceChanged();
assertEquals(0, sq.findIndex(-1));
// beyond end returns last residue column
+ sq.sequenceChanged();
assertEquals(13, sq.findIndex(99));
-
}
/**
- * Tests for the method that returns a dataset sequence position (base 1) for
+ * Tests for the method that returns a dataset sequence position (start..) for
* an aligned column position (base 0).
*/
@Test(groups = { "Functional" })
public void testFindPosition()
{
- SequenceI sq = new Sequence("test", "ABCDEF");
- assertEquals(1, sq.findPosition(0));
- assertEquals(6, sq.findPosition(5));
+ /*
+ * call sequenceChanged() after each test to invalidate any cursor,
+ * forcing the 1-arg findPosition to be executed
+ */
+ SequenceI sq = new Sequence("test/8-13", "ABCDEF");
+ assertEquals(8, sq.findPosition(0));
+ // Sequence should now hold a cursor at [8, 0]
+ assertEquals("test:Pos8:Col1:startCol1:endCol0:tok0",
+ PA.getValue(sq, "cursor").toString());
+ SequenceCursor cursor = (SequenceCursor) PA.getValue(sq, "cursor");
+ int token = (int) PA.getValue(sq, "changeCount");
+ assertEquals(new SequenceCursor(sq, 8, 1, token), cursor);
+
+ sq.sequenceChanged();
+
+ /*
+ * find F13 at column offset 5, cursor should update to [13, 6]
+ * endColumn is found and saved in cursor
+ */
+ assertEquals(13, sq.findPosition(5));
+ cursor = (SequenceCursor) PA.getValue(sq, "cursor");
+ assertEquals(++token, (int) PA.getValue(sq, "changeCount"));
+ assertEquals(new SequenceCursor(sq, 13, 6, token), cursor);
+ assertEquals("test:Pos13:Col6:startCol1:endCol6:tok1",
+ PA.getValue(sq, "cursor").toString());
+
// assertEquals(-1, seq.findPosition(6)); // fails
- sq = new Sequence("test", "AB-C-D--");
- assertEquals(1, sq.findPosition(0));
- assertEquals(2, sq.findPosition(1));
+ sq = new Sequence("test/8-11", "AB-C-D--");
+ token = (int) PA.getValue(sq, "changeCount"); // 0
+ assertEquals(8, sq.findPosition(0));
+ cursor = (SequenceCursor) PA.getValue(sq, "cursor");
+ assertEquals(new SequenceCursor(sq, 8, 1, token), cursor);
+ assertEquals("test:Pos8:Col1:startCol1:endCol0:tok0",
+ PA.getValue(sq, "cursor").toString());
+
+ sq.sequenceChanged();
+ assertEquals(9, sq.findPosition(1));
+ cursor = (SequenceCursor) PA.getValue(sq, "cursor");
+ assertEquals(new SequenceCursor(sq, 9, 2, ++token), cursor);
+ assertEquals("test:Pos9:Col2:startCol1:endCol0:tok1",
+ PA.getValue(sq, "cursor").toString());
+
+ sq.sequenceChanged();
// gap position 'finds' residue to the right (not the left as per javadoc)
- assertEquals(3, sq.findPosition(2));
- assertEquals(3, sq.findPosition(3));
- assertEquals(4, sq.findPosition(4));
- assertEquals(4, sq.findPosition(5));
+ // cursor is set to the last residue position found [B 2]
+ assertEquals(10, sq.findPosition(2));
+ cursor = (SequenceCursor) PA.getValue(sq, "cursor");
+ assertEquals(new SequenceCursor(sq, 9, 2, ++token), cursor);
+ assertEquals("test:Pos9:Col2:startCol1:endCol0:tok2",
+ PA.getValue(sq, "cursor").toString());
+
+ sq.sequenceChanged();
+ assertEquals(10, sq.findPosition(3));
+ cursor = (SequenceCursor) PA.getValue(sq, "cursor");
+ assertEquals(new SequenceCursor(sq, 10, 4, ++token), cursor);
+ assertEquals("test:Pos10:Col4:startCol1:endCol0:tok3",
+ PA.getValue(sq, "cursor").toString());
+
+ sq.sequenceChanged();
+ // column[4] is the gap after C - returns D11
+ // cursor is set to [C 4]
+ assertEquals(11, sq.findPosition(4));
+ cursor = (SequenceCursor) PA.getValue(sq, "cursor");
+ assertEquals(new SequenceCursor(sq, 10, 4, ++token), cursor);
+ assertEquals("test:Pos10:Col4:startCol1:endCol0:tok4",
+ PA.getValue(sq, "cursor").toString());
+
+ sq.sequenceChanged();
+ assertEquals(11, sq.findPosition(5)); // D
+ cursor = (SequenceCursor) PA.getValue(sq, "cursor");
+ assertEquals(new SequenceCursor(sq, 11, 6, ++token), cursor);
+ // lastCol has been found and saved in the cursor
+ assertEquals("test:Pos11:Col6:startCol1:endCol6:tok5",
+ PA.getValue(sq, "cursor").toString());
+
+ sq.sequenceChanged();
// returns 1 more than sequence length if off the end ?!?
- assertEquals(5, sq.findPosition(6));
- assertEquals(5, sq.findPosition(7));
+ assertEquals(12, sq.findPosition(6));
- sq = new Sequence("test", "--AB-C-DEF--");
- assertEquals(1, sq.findPosition(0));
- assertEquals(1, sq.findPosition(1));
- assertEquals(1, sq.findPosition(2));
- assertEquals(2, sq.findPosition(3));
- assertEquals(3, sq.findPosition(4));
- assertEquals(3, sq.findPosition(5));
- assertEquals(4, sq.findPosition(6));
- assertEquals(4, sq.findPosition(7));
- assertEquals(5, sq.findPosition(8));
- assertEquals(6, sq.findPosition(9));
- assertEquals(7, sq.findPosition(10));
- assertEquals(7, sq.findPosition(11));
+ sq.sequenceChanged();
+ assertEquals(12, sq.findPosition(7));
+
+ /*
+ * first findPosition should also set firstResCol in cursor
+ */
+ sq = new Sequence("test/8-13", "--AB-C-DEF--");
+ assertEquals(8, sq.findPosition(0));
+ assertNull(PA.getValue(sq, "cursor"));
+
+ sq.sequenceChanged();
+ assertEquals(8, sq.findPosition(1));
+ assertNull(PA.getValue(sq, "cursor"));
+
+ sq.sequenceChanged();
+ assertEquals(8, sq.findPosition(2));
+ assertEquals("test:Pos8:Col3:startCol3:endCol0:tok2",
+ PA.getValue(sq, "cursor").toString());
+
+ sq.sequenceChanged();
+ assertEquals(9, sq.findPosition(3));
+ assertEquals("test:Pos9:Col4:startCol3:endCol0:tok3",
+ PA.getValue(sq, "cursor").toString());
+
+ sq.sequenceChanged();
+ // column[4] is a gap, returns next residue pos (C10)
+ // cursor is set to last residue found [B]
+ assertEquals(10, sq.findPosition(4));
+ assertEquals("test:Pos9:Col4:startCol3:endCol0:tok4",
+ PA.getValue(sq, "cursor").toString());
+
+ sq.sequenceChanged();
+ assertEquals(10, sq.findPosition(5));
+ assertEquals("test:Pos10:Col6:startCol3:endCol0:tok5",
+ PA.getValue(sq, "cursor").toString());
+
+ sq.sequenceChanged();
+ // column[6] is a gap, returns next residue pos (D11)
+ // cursor is set to last residue found [C]
+ assertEquals(11, sq.findPosition(6));
+ assertEquals("test:Pos10:Col6:startCol3:endCol0:tok6",
+ PA.getValue(sq, "cursor").toString());
+
+ sq.sequenceChanged();
+ assertEquals(11, sq.findPosition(7));
+ assertEquals("test:Pos11:Col8:startCol3:endCol0:tok7",
+ PA.getValue(sq, "cursor").toString());
+
+ sq.sequenceChanged();
+ assertEquals(12, sq.findPosition(8));
+ assertEquals("test:Pos12:Col9:startCol3:endCol0:tok8",
+ PA.getValue(sq, "cursor").toString());
+
+ /*
+ * when the last residue column is found, it is set in the cursor
+ */
+ sq.sequenceChanged();
+ assertEquals(13, sq.findPosition(9));
+ assertEquals("test:Pos13:Col10:startCol3:endCol10:tok9",
+ PA.getValue(sq, "cursor").toString());
+
+ sq.sequenceChanged();
+ assertEquals(14, sq.findPosition(10));
+ assertEquals("test:Pos13:Col10:startCol3:endCol10:tok10",
+ PA.getValue(sq, "cursor").toString());
+
+ /*
+ * findPosition for column beyond sequence length
+ * returns 1 more than last residue position
+ */
+ sq.sequenceChanged();
+ assertEquals(14, sq.findPosition(11));
+ assertEquals("test:Pos13:Col10:startCol3:endCol10:tok11",
+ PA.getValue(sq, "cursor").toString());
+
+ sq.sequenceChanged();
+ assertEquals(14, sq.findPosition(99));
+ assertEquals("test:Pos13:Col10:startCol3:endCol10:tok12",
+ PA.getValue(sq, "cursor").toString());
+
+ /*
+ * gapped sequence ending in non-gap
+ */
+ sq = new Sequence("test/8-13", "--AB-C-DEF");
+ assertEquals(13, sq.findPosition(9));
+ assertEquals("test:Pos13:Col10:startCol3:endCol10:tok0",
+ PA.getValue(sq, "cursor").toString());
+ sq.sequenceChanged();
+ assertEquals(12, sq.findPosition(8));
+ cursor = (SequenceCursor) PA.getValue(sq, "cursor");
+ // sequenceChanged() invalidates cursor.lastResidueColumn
+ cursor = (SequenceCursor) PA.getValue(sq, "cursor");
+ assertEquals("test:Pos12:Col9:startCol3:endCol0:tok1",
+ cursor.toString());
+ // findPosition with cursor accepts base 1 column values
+ assertEquals(13, ((Sequence) sq).findPosition(10, cursor));
+ assertEquals(13, sq.findPosition(9)); // F13
+ // lastResidueColumn has now been found and saved in cursor
+ assertEquals("test:Pos13:Col10:startCol3:endCol10:tok1",
+ PA.getValue(sq, "cursor").toString());
}
@Test(groups = { "Functional" })
public void testDeleteChars()
{
+ /*
+ * internal delete
+ */
+ SequenceI sq = new Sequence("test", "ABCDEF");
+ assertNull(PA.getValue(sq, "datasetSequence"));
+ assertEquals(1, sq.getStart());
+ assertEquals(6, sq.getEnd());
+ sq.deleteChars(2, 3);
+ assertEquals("ABDEF", sq.getSequenceAsString());
+ assertEquals(1, sq.getStart());
+ assertEquals(5, sq.getEnd());
+ assertNull(PA.getValue(sq, "datasetSequence"));
+
+ /*
+ * delete at start
+ */
+ sq = new Sequence("test", "ABCDEF");
+ sq.deleteChars(0, 2);
+ assertEquals("CDEF", sq.getSequenceAsString());
+ assertEquals(3, sq.getStart());
+ assertEquals(6, sq.getEnd());
+ assertNull(PA.getValue(sq, "datasetSequence"));
+
+ /*
+ * delete at end
+ */
+ sq = new Sequence("test", "ABCDEF");
+ sq.deleteChars(4, 6);
+ assertEquals("ABCD", sq.getSequenceAsString());
+ assertEquals(1, sq.getStart());
+ assertEquals(4, sq.getEnd());
+ assertNull(PA.getValue(sq, "datasetSequence"));
+ }
+
+ @Test(groups = { "Functional" })
+ public void testDeleteChars_withDbRefsAndFeatures()
+ {
+ /*
+ * internal delete - new dataset sequence created
+ * gets a copy of any dbrefs
+ */
SequenceI sq = new Sequence("test", "ABCDEF");
+ sq.createDatasetSequence();
+ DBRefEntry dbr1 = new DBRefEntry("Uniprot", "0", "a123");
+ sq.addDBRef(dbr1);
+ Object ds = PA.getValue(sq, "datasetSequence");
+ assertNotNull(ds);
assertEquals(1, sq.getStart());
assertEquals(6, sq.getEnd());
sq.deleteChars(2, 3);
assertEquals("ABDEF", sq.getSequenceAsString());
assertEquals(1, sq.getStart());
assertEquals(5, sq.getEnd());
+ Object newDs = PA.getValue(sq, "datasetSequence");
+ assertNotNull(newDs);
+ assertNotSame(ds, newDs);
+ assertNotNull(sq.getDBRefs());
+ assertEquals(1, sq.getDBRefs().length);
+ assertNotSame(dbr1, sq.getDBRefs()[0]);
+ assertEquals(dbr1, sq.getDBRefs()[0]);
+ /*
+ * internal delete with sequence features
+ * (failure case for JAL-2541)
+ */
sq = new Sequence("test", "ABCDEF");
+ sq.createDatasetSequence();
+ SequenceFeature sf1 = new SequenceFeature("Cath", "desc", 2, 4, 2f,
+ "CathGroup");
+ sq.addSequenceFeature(sf1);
+ ds = PA.getValue(sq, "datasetSequence");
+ assertNotNull(ds);
+ assertEquals(1, sq.getStart());
+ assertEquals(6, sq.getEnd());
+ sq.deleteChars(2, 4);
+ assertEquals("ABEF", sq.getSequenceAsString());
+ assertEquals(1, sq.getStart());
+ assertEquals(4, sq.getEnd());
+ newDs = PA.getValue(sq, "datasetSequence");
+ assertNotNull(newDs);
+ assertNotSame(ds, newDs);
+ List<SequenceFeature> sfs = sq.getSequenceFeatures();
+ assertEquals(1, sfs.size());
+ assertNotSame(sf1, sfs.get(0));
+ assertEquals(sf1, sfs.get(0));
+
+ /*
+ * delete at start - no new dataset sequence created
+ * any sequence features remain as before
+ */
+ sq = new Sequence("test", "ABCDEF");
+ sq.createDatasetSequence();
+ ds = PA.getValue(sq, "datasetSequence");
+ sf1 = new SequenceFeature("Cath", "desc", 2, 4, 2f, "CathGroup");
+ sq.addSequenceFeature(sf1);
sq.deleteChars(0, 2);
assertEquals("CDEF", sq.getSequenceAsString());
assertEquals(3, sq.getStart());
assertEquals(6, sq.getEnd());
+ assertSame(ds, PA.getValue(sq, "datasetSequence"));
+ sfs = sq.getSequenceFeatures();
+ assertNotNull(sfs);
+ assertEquals(1, sfs.size());
+ assertSame(sf1, sfs.get(0));
+
+ /*
+ * delete at end - no new dataset sequence created
+ * any dbrefs remain as before
+ */
+ sq = new Sequence("test", "ABCDEF");
+ sq.createDatasetSequence();
+ ds = PA.getValue(sq, "datasetSequence");
+ dbr1 = new DBRefEntry("Uniprot", "0", "a123");
+ sq.addDBRef(dbr1);
+ sq.deleteChars(4, 6);
+ assertEquals("ABCD", sq.getSequenceAsString());
+ assertEquals(1, sq.getStart());
+ assertEquals(4, sq.getEnd());
+ assertSame(ds, PA.getValue(sq, "datasetSequence"));
+ assertNotNull(sq.getDBRefs());
+ assertEquals(1, sq.getDBRefs().length);
+ assertSame(dbr1, sq.getDBRefs()[0]);
}
@Test(groups = { "Functional" })
SequenceI sq = new Sequence("test", "GATCAT");
sq.createDatasetSequence();
- assertNull(sq.getSequenceFeatures());
+ assertTrue(sq.getSequenceFeatures().isEmpty());
/*
* SequenceFeature on sequence
*/
- SequenceFeature sf = new SequenceFeature();
+ SequenceFeature sf = new SequenceFeature("Cath", "desc", 2, 4, 2f, null);
sq.addSequenceFeature(sf);
- SequenceFeature[] sfs = sq.getSequenceFeatures();
- assertEquals(1, sfs.length);
- assertSame(sf, sfs[0]);
+ List<SequenceFeature> sfs = sq.getSequenceFeatures();
+ assertEquals(1, sfs.size());
+ assertSame(sf, sfs.get(0));
/*
* SequenceFeature on sequence and dataset sequence; returns that on
* Note JAL-2046: spurious: we have no use case for this at the moment.
* This test also buggy - as sf2.equals(sf), no new feature is added
*/
- SequenceFeature sf2 = new SequenceFeature();
+ SequenceFeature sf2 = new SequenceFeature("Cath", "desc", 2, 4, 2f,
+ null);
sq.getDatasetSequence().addSequenceFeature(sf2);
sfs = sq.getSequenceFeatures();
- assertEquals(1, sfs.length);
- assertSame(sf, sfs[0]);
+ assertEquals(1, sfs.size());
+ assertSame(sf, sfs.get(0));
/*
* SequenceFeature on dataset sequence only
* Note JAL-2046: spurious: we have no use case for setting a non-dataset sequence's feature array to null at the moment.
*/
sq.setSequenceFeatures(null);
- assertNull(sq.getDatasetSequence().getSequenceFeatures());
+ assertTrue(sq.getDatasetSequence().getSequenceFeatures().isEmpty());
/*
* Corrupt case - no SequenceFeature, dataset's dataset is the original
assertTrue(e.getMessage().toLowerCase()
.contains("implementation error"));
}
- assertNull(sq.getSequenceFeatures());
+ assertTrue(sq.getSequenceFeatures().isEmpty());
}
/**
public void testCreateDatasetSequence()
{
SequenceI sq = new Sequence("my", "ASDASD");
+ sq.addSequenceFeature(new SequenceFeature("type", "desc", 1, 10, 1f,
+ "group"));
+ sq.addDBRef(new DBRefEntry("source", "version", "accession"));
assertNull(sq.getDatasetSequence());
+ assertNotNull(PA.getValue(sq, "sequenceFeatureStore"));
+ assertNotNull(PA.getValue(sq, "dbrefs"));
+
SequenceI rds = sq.createDatasetSequence();
assertNotNull(rds);
assertNull(rds.getDatasetSequence());
- assertEquals(sq.getDatasetSequence(), rds);
+ assertSame(sq.getDatasetSequence(), rds);
+
+ // sequence features and dbrefs transferred to dataset sequence
+ assertNull(PA.getValue(sq, "sequenceFeatureStore"));
+ assertNull(PA.getValue(sq, "dbrefs"));
+ assertNotNull(PA.getValue(rds, "sequenceFeatureStore"));
+ assertNotNull(PA.getValue(rds, "dbrefs"));
}
/**
assertEquals("CD", derived.getSequenceAsString());
assertSame(sq.getDatasetSequence(), derived.getDatasetSequence());
- assertNull(sq.sequenceFeatures);
- assertNull(derived.sequenceFeatures);
// derived sequence should access dataset sequence features
assertNotNull(sq.getSequenceFeatures());
- assertArrayEquals(sq.getSequenceFeatures(),
- derived.getSequenceFeatures());
+ assertEquals(sq.getSequenceFeatures(), derived.getSequenceFeatures());
/*
* verify we have primary db refs *just* for PDB IDs with associated
assertEquals(anns[0].score, seq1.getAnnotation()[0].score);
// copy has a copy of the sequence feature:
- SequenceFeature[] sfs = copy.getSequenceFeatures();
- assertEquals(1, sfs.length);
+ List<SequenceFeature> sfs = copy.getSequenceFeatures();
+ assertEquals(1, sfs.size());
if (seq1.getDatasetSequence() != null
&& copy.getDatasetSequence() == seq1.getDatasetSequence())
{
- assertTrue(sfs[0] == seq1.getSequenceFeatures()[0]);
+ assertSame(sfs.get(0), seq1.getSequenceFeatures().get(0));
}
else
{
- assertFalse(sfs[0] == seq1.getSequenceFeatures()[0]);
+ assertNotSame(sfs.get(0), seq1.getSequenceFeatures().get(0));
}
- assertTrue(sfs[0].equals(seq1.getSequenceFeatures()[0]));
+ assertEquals(sfs.get(0), seq1.getSequenceFeatures().get(0));
// copy has a copy of the PDB entry
Vector<PDBEntry> pdbs = copy.getAllPDBEntries();
assertEquals(' ', sq.getCharAt(-1));
}
+ @Test(groups = { "Functional" })
+ public void testAddSequenceFeatures()
+ {
+ SequenceI sq = new Sequence("", "abcde");
+ // type may not be null
+ assertFalse(sq.addSequenceFeature(new SequenceFeature(null, "desc", 4,
+ 8, 0f, null)));
+ assertTrue(sq.addSequenceFeature(new SequenceFeature("Cath", "desc", 4,
+ 8, 0f, null)));
+ // can't add a duplicate feature
+ assertFalse(sq.addSequenceFeature(new SequenceFeature("Cath", "desc",
+ 4, 8, 0f, null)));
+ // can add a different feature
+ assertTrue(sq.addSequenceFeature(new SequenceFeature("Scop", "desc", 4,
+ 8, 0f, null))); // different type
+ assertTrue(sq.addSequenceFeature(new SequenceFeature("Cath",
+ "description", 4, 8, 0f, null)));// different description
+ assertTrue(sq.addSequenceFeature(new SequenceFeature("Cath", "desc", 3,
+ 8, 0f, null))); // different start position
+ assertTrue(sq.addSequenceFeature(new SequenceFeature("Cath", "desc", 4,
+ 9, 0f, null))); // different end position
+ assertTrue(sq.addSequenceFeature(new SequenceFeature("Cath", "desc", 4,
+ 8, 1f, null))); // different score
+ assertTrue(sq.addSequenceFeature(new SequenceFeature("Cath", "desc", 4,
+ 8, Float.NaN, null))); // score NaN
+ assertTrue(sq.addSequenceFeature(new SequenceFeature("Cath", "desc", 4,
+ 8, 0f, "Metal"))); // different group
+ assertEquals(8, sq.getFeatures().getAllFeatures().size());
+ }
+
/**
* Tests for adding (or updating) dbrefs
*
seq2.createDatasetSequence();
seq.setDatasetSequence(seq2);
}
+
+ @Test(groups = { "Functional" })
+ public void testFindFeatures()
+ {
+ SequenceI sq = new Sequence("test/8-16", "-ABC--DEF--GHI--");
+ sq.createDatasetSequence();
+
+ assertTrue(sq.findFeatures(1, 99).isEmpty());
+
+ // add non-positional feature
+ SequenceFeature sf0 = new SequenceFeature("Cath", "desc", 0, 0, 2f,
+ null);
+ sq.addSequenceFeature(sf0);
+ // add feature on BCD
+ SequenceFeature sfBCD = new SequenceFeature("Cath", "desc", 9, 11, 2f,
+ null);
+ sq.addSequenceFeature(sfBCD);
+ // add feature on DE
+ SequenceFeature sfDE = new SequenceFeature("Cath", "desc", 11, 12, 2f,
+ null);
+ sq.addSequenceFeature(sfDE);
+ // add contact feature at [B, H]
+ SequenceFeature sfContactBH = new SequenceFeature("Disulphide bond",
+ "desc", 9, 15, 2f, null);
+ sq.addSequenceFeature(sfContactBH);
+ // add contact feature at [F, G]
+ SequenceFeature sfContactFG = new SequenceFeature("Disulfide Bond",
+ "desc", 13, 14, 2f, null);
+ sq.addSequenceFeature(sfContactFG);
+ // add single position feature at [I]
+ SequenceFeature sfI = new SequenceFeature("Disulfide Bond",
+ "desc", 16, 16, null);
+ sq.addSequenceFeature(sfI);
+
+ // no features in columns 1-2 (-A)
+ List<SequenceFeature> found = sq.findFeatures(1, 2);
+ assertTrue(found.isEmpty());
+
+ // columns 1-6 (-ABC--) includes BCD and B/H feature but not DE
+ found = sq.findFeatures(1, 6);
+ assertEquals(2, found.size());
+ assertTrue(found.contains(sfBCD));
+ assertTrue(found.contains(sfContactBH));
+
+ // columns 5-6 (--) includes (enclosing) BCD but not (contact) B/H feature
+ found = sq.findFeatures(5, 6);
+ assertEquals(1, found.size());
+ assertTrue(found.contains(sfBCD));
+
+ // columns 7-10 (DEF-) includes BCD, DE, F/G but not B/H feature
+ found = sq.findFeatures(7, 10);
+ assertEquals(3, found.size());
+ assertTrue(found.contains(sfBCD));
+ assertTrue(found.contains(sfDE));
+ assertTrue(found.contains(sfContactFG));
+
+ // columns 10-11 (--) should find nothing
+ found = sq.findFeatures(10, 11);
+ assertEquals(0, found.size());
+
+ // columns 14-14 (I) should find variant feature
+ found = sq.findFeatures(14, 14);
+ assertEquals(1, found.size());
+ assertTrue(found.contains(sfI));
+ }
+
+ @Test(groups = { "Functional" })
+ public void testFindIndex_withCursor()
+ {
+ Sequence sq = new Sequence("test/8-13", "-A--BCD-EF--");
+
+ // find F given A
+ assertEquals(10, sq.findIndex(13, new SequenceCursor(sq, 8, 2, 0)));
+
+ // find A given F
+ assertEquals(2, sq.findIndex(8, new SequenceCursor(sq, 13, 10, 0)));
+
+ // find C given C
+ assertEquals(6, sq.findIndex(10, new SequenceCursor(sq, 10, 6, 0)));
+ }
+
+ @Test(groups = { "Functional" })
+ public void testFindPosition_withCursor()
+ {
+ Sequence sq = new Sequence("test/8-13", "-A--BCD-EF--");
+
+ // find F pos given A - lastCol gets set in cursor
+ assertEquals(13, sq.findPosition(10, new SequenceCursor(sq, 8, 2, 0)));
+ assertEquals("test:Pos13:Col10:startCol0:endCol10:tok0",
+ PA.getValue(sq, "cursor").toString());
+
+ // find A pos given F - first residue column is saved in cursor
+ assertEquals(8, sq.findPosition(2, new SequenceCursor(sq, 13, 10, 0)));
+ assertEquals("test:Pos8:Col2:startCol2:endCol10:tok0",
+ PA.getValue(sq, "cursor").toString());
+
+ // find C pos given C (neither startCol nor endCol is set)
+ assertEquals(10, sq.findPosition(6, new SequenceCursor(sq, 10, 6, 0)));
+ assertEquals("test:Pos10:Col6:startCol0:endCol0:tok0",
+ PA.getValue(sq, "cursor").toString());
+
+ // now the grey area - what residue position for a gapped column? JAL-2562
+
+ // find 'residue' for column 3 given cursor for D (so working left)
+ // returns B9; cursor is updated to [B 5]
+ assertEquals(9, sq.findPosition(3, new SequenceCursor(sq, 11, 7, 0)));
+ assertEquals("test:Pos9:Col5:startCol0:endCol0:tok0",
+ PA.getValue(sq, "cursor").toString());
+
+ // find 'residue' for column 8 given cursor for D (so working right)
+ // returns E12; cursor is updated to [D 7]
+ assertEquals(12, sq.findPosition(8, new SequenceCursor(sq, 11, 7, 0)));
+ assertEquals("test:Pos11:Col7:startCol0:endCol0:tok0",
+ PA.getValue(sq, "cursor").toString());
+
+ // find 'residue' for column 12 given cursor for B
+ // returns 1 more than last residue position; cursor is updated to [F 10]
+ // lastCol position is saved in cursor
+ assertEquals(14, sq.findPosition(12, new SequenceCursor(sq, 9, 5, 0)));
+ assertEquals("test:Pos13:Col10:startCol0:endCol10:tok0",
+ PA.getValue(sq, "cursor").toString());
+
+ /*
+ * findPosition for column beyond length of sequence
+ * returns 1 more than the last residue position
+ * cursor is set to last real residue position [F 10]
+ */
+ assertEquals(14, sq.findPosition(99, new SequenceCursor(sq, 8, 2, 0)));
+ assertEquals("test:Pos13:Col10:startCol0:endCol10:tok0",
+ PA.getValue(sq, "cursor").toString());
+
+ /*
+ * and the case without a trailing gap
+ */
+ sq = new Sequence("test/8-13", "-A--BCD-EF");
+ // first find C from A
+ assertEquals(10, sq.findPosition(6, new SequenceCursor(sq, 8, 2, 0)));
+ SequenceCursor cursor = (SequenceCursor) PA.getValue(sq, "cursor");
+ assertEquals("test:Pos10:Col6:startCol0:endCol0:tok0",
+ cursor.toString());
+ // now 'find' 99 from C
+ // cursor is set to [F 10] and saved lastCol
+ assertEquals(14, sq.findPosition(99, cursor));
+ assertEquals("test:Pos13:Col10:startCol0:endCol10:tok0",
+ PA.getValue(sq, "cursor").toString());
+ }
+
+ @Test
+ public void testIsValidCursor()
+ {
+ Sequence sq = new Sequence("Seq", "ABC--DE-F", 8, 13);
+ assertFalse(sq.isValidCursor(null));
+
+ /*
+ * cursor is valid if it has valid sequence ref and changeCount token
+ * and positions within the range of the sequence
+ */
+ int changeCount = (int) PA.getValue(sq, "changeCount");
+ SequenceCursor cursor = new SequenceCursor(sq, 13, 1, changeCount);
+ assertTrue(sq.isValidCursor(cursor));
+
+ /*
+ * column position outside [0 - length] is rejected
+ */
+ cursor = new SequenceCursor(sq, 13, -1, changeCount);
+ assertFalse(sq.isValidCursor(cursor));
+ cursor = new SequenceCursor(sq, 13, 10, changeCount);
+ assertFalse(sq.isValidCursor(cursor));
+ cursor = new SequenceCursor(sq, 7, 8, changeCount);
+ assertFalse(sq.isValidCursor(cursor));
+ cursor = new SequenceCursor(sq, 14, 2, changeCount);
+ assertFalse(sq.isValidCursor(cursor));
+
+ /*
+ * wrong sequence is rejected
+ */
+ cursor = new SequenceCursor(null, 13, 1, changeCount);
+ assertFalse(sq.isValidCursor(cursor));
+ cursor = new SequenceCursor(new Sequence("Seq", "abc"), 13, 1,
+ changeCount);
+ assertFalse(sq.isValidCursor(cursor));
+
+ /*
+ * wrong token value is rejected
+ */
+ cursor = new SequenceCursor(sq, 13, 1, changeCount + 1);
+ assertFalse(sq.isValidCursor(cursor));
+ cursor = new SequenceCursor(sq, 13, 1, changeCount - 1);
+ assertFalse(sq.isValidCursor(cursor));
+ }
+
+ @Test(groups = { "Functional" })
+ public void testFindPosition_withCursorAndEdits()
+ {
+ Sequence sq = new Sequence("test/8-13", "-A--BCD-EF--");
+
+ // find F pos given A
+ assertEquals(13, sq.findPosition(10, new SequenceCursor(sq, 8, 2, 0)));
+ int token = (int) PA.getValue(sq, "changeCount"); // 0
+ SequenceCursor cursor = (SequenceCursor) PA.getValue(sq, "cursor");
+ assertEquals(new SequenceCursor(sq, 13, 10, token), cursor);
+
+ /*
+ * setSequence should invalidate the cursor cached by the sequence
+ */
+ sq.setSequence("-A-BCD-EF---"); // one gap removed
+ assertEquals(8, sq.getStart()); // sanity check
+ assertEquals(11, sq.findPosition(5)); // D11
+ // cursor should now be at [D 6]
+ cursor = (SequenceCursor) PA.getValue(sq, "cursor");
+ assertEquals(new SequenceCursor(sq, 11, 6, ++token), cursor);
+
+ /*
+ * deleteChars should invalidate the cached cursor
+ */
+ sq.deleteChars(2, 5); // delete -BC
+ assertEquals("-AD-EF---", sq.getSequenceAsString());
+ assertEquals(8, sq.getStart()); // sanity check
+ assertEquals(10, sq.findPosition(4)); // E10
+ // cursor should now be at [E 5]
+ cursor = (SequenceCursor) PA.getValue(sq, "cursor");
+ assertEquals(new SequenceCursor(sq, 10, 5, ++token), cursor);
+
+ /*
+ * Edit to insert gaps should invalidate the cached cursor
+ * insert 2 gaps at column[3] to make -AD---EF---
+ */
+ SequenceI[] seqs = new SequenceI[] { sq };
+ AlignmentI al = new Alignment(seqs);
+ new EditCommand().appendEdit(Action.INSERT_GAP, seqs, 3, 2, al, true);
+ assertEquals("-AD---EF---", sq.getSequenceAsString());
+ assertEquals(10, sq.findPosition(4)); // E10
+ // cursor should now be at [D 3]
+ cursor = (SequenceCursor) PA.getValue(sq, "cursor");
+ assertEquals(new SequenceCursor(sq, 9, 3, ++token), cursor);
+
+ /*
+ * insertCharAt should invalidate the cached cursor
+ * insert CC at column[4] to make -AD-CC--EF---
+ */
+ sq.insertCharAt(4, 2, 'C');
+ assertEquals("-AD-CC--EF---", sq.getSequenceAsString());
+ assertEquals(13, sq.findPosition(9)); // F13
+ // cursor should now be at [F 10]
+ cursor = (SequenceCursor) PA.getValue(sq, "cursor");
+ assertEquals(new SequenceCursor(sq, 13, 10, ++token), cursor);
+ }
+
+ @Test(groups = { "Functional" })
+ public void testGetSequence()
+ {
+ String seqstring = "-A--BCD-EF--";
+ Sequence sq = new Sequence("test/8-13", seqstring);
+ sq.createDatasetSequence();
+ assertTrue(Arrays.equals(sq.getSequence(), seqstring.toCharArray()));
+ assertTrue(Arrays.equals(sq.getDatasetSequence().getSequence(),
+ "ABCDEF".toCharArray()));
+
+ // verify a copy of the sequence array is returned
+ char[] theSeq = (char[]) PA.getValue(sq, "sequence");
+ assertNotSame(theSeq, sq.getSequence());
+ theSeq = (char[]) PA.getValue(sq.getDatasetSequence(), "sequence");
+ assertNotSame(theSeq, sq.getDatasetSequence().getSequence());
+ }
+
+ @Test(groups = { "Functional" })
+ public void testReplace()
+ {
+ String seqstring = "-A--BCD-EF--";
+ SequenceI sq = new Sequence("test/8-13", seqstring);
+ assertEquals(0, PA.getValue(sq, "changeCount"));
+
+ assertEquals(0, sq.replace('A', 'A')); // same char
+ assertEquals(seqstring, sq.getSequenceAsString());
+ assertEquals(0, PA.getValue(sq, "changeCount"));
+
+ assertEquals(0, sq.replace('X', 'Y')); // not there
+ assertEquals(seqstring, sq.getSequenceAsString());
+ assertEquals(0, PA.getValue(sq, "changeCount"));
+
+ assertEquals(1, sq.replace('A', 'K'));
+ assertEquals("-K--BCD-EF--", sq.getSequenceAsString());
+ assertEquals(1, PA.getValue(sq, "changeCount"));
+
+ assertEquals(6, sq.replace('-', '.'));
+ assertEquals(".K..BCD.EF..", sq.getSequenceAsString());
+ assertEquals(2, PA.getValue(sq, "changeCount"));
+ }
+
+ @Test(groups = { "Functional" })
+ public void testFindPositions()
+ {
+ SequenceI sq = new Sequence("test/8-13", "-ABC---DE-F--");
+
+ /*
+ * invalid inputs
+ */
+ assertNull(sq.findPositions(6, 5));
+ assertNull(sq.findPositions(0, 5));
+ assertNull(sq.findPositions(-1, 5));
+
+ /*
+ * all gapped ranges
+ */
+ assertNull(sq.findPositions(1, 1)); // 1-based columns
+ assertNull(sq.findPositions(5, 5));
+ assertNull(sq.findPositions(5, 6));
+ assertNull(sq.findPositions(5, 7));
+
+ /*
+ * all ungapped ranges
+ */
+ assertEquals(new Range(8, 8), sq.findPositions(2, 2)); // A
+ assertEquals(new Range(8, 9), sq.findPositions(2, 3)); // AB
+ assertEquals(new Range(8, 10), sq.findPositions(2, 4)); // ABC
+ assertEquals(new Range(9, 10), sq.findPositions(3, 4)); // BC
+
+ /*
+ * gap to ungapped range
+ */
+ assertEquals(new Range(8, 10), sq.findPositions(1, 4)); // ABC
+ assertEquals(new Range(11, 12), sq.findPositions(6, 9)); // DE
+
+ /*
+ * ungapped to gapped range
+ */
+ assertEquals(new Range(10, 10), sq.findPositions(4, 5)); // C
+ assertEquals(new Range(9, 13), sq.findPositions(3, 11)); // BCDEF
+
+ /*
+ * ungapped to ungapped enclosing gaps
+ */
+ assertEquals(new Range(10, 11), sq.findPositions(4, 8)); // CD
+ assertEquals(new Range(8, 13), sq.findPositions(2, 11)); // ABCDEF
+
+ /*
+ * gapped to gapped enclosing ungapped
+ */
+ assertEquals(new Range(8, 10), sq.findPositions(1, 5)); // ABC
+ assertEquals(new Range(11, 12), sq.findPositions(5, 10)); // DE
+ assertEquals(new Range(8, 13), sq.findPositions(1, 13)); // the lot
+ assertEquals(new Range(8, 13), sq.findPositions(1, 99));
+ }
}
--- /dev/null
+package jalview.datamodel.features;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertTrue;
+
+import jalview.datamodel.SequenceFeature;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+import org.testng.annotations.Test;
+
+public class FeatureStoreTest
+{
+
+ @Test(groups = "Functional")
+ public void testFindFeatures_nonNested()
+ {
+ FeatureStore fs = new FeatureStore();
+ fs.addFeature(new SequenceFeature("", "", 10, 20, Float.NaN,
+ null));
+ // same range different description
+ fs.addFeature(new SequenceFeature("", "desc", 10, 20, Float.NaN, null));
+ fs.addFeature(new SequenceFeature("", "", 15, 25, Float.NaN, null));
+ fs.addFeature(new SequenceFeature("", "", 20, 35, Float.NaN, null));
+
+ List<SequenceFeature> overlaps = fs.findOverlappingFeatures(1, 9);
+ assertTrue(overlaps.isEmpty());
+
+ overlaps = fs.findOverlappingFeatures(8, 10);
+ assertEquals(overlaps.size(), 2);
+ assertEquals(overlaps.get(0).getEnd(), 20);
+ assertEquals(overlaps.get(1).getEnd(), 20);
+
+ overlaps = fs.findOverlappingFeatures(12, 16);
+ assertEquals(overlaps.size(), 3);
+ assertEquals(overlaps.get(0).getEnd(), 20);
+ assertEquals(overlaps.get(1).getEnd(), 20);
+ assertEquals(overlaps.get(2).getEnd(), 25);
+
+ overlaps = fs.findOverlappingFeatures(33, 33);
+ assertEquals(overlaps.size(), 1);
+ assertEquals(overlaps.get(0).getEnd(), 35);
+ }
+
+ @Test(groups = "Functional")
+ public void testFindFeatures_nested()
+ {
+ FeatureStore fs = new FeatureStore();
+ SequenceFeature sf1 = addFeature(fs, 10, 50);
+ SequenceFeature sf2 = addFeature(fs, 10, 40);
+ SequenceFeature sf3 = addFeature(fs, 20, 30);
+ // fudge feature at same location but different group (so is added)
+ SequenceFeature sf4 = new SequenceFeature("", "", 20, 30, Float.NaN,
+ "different group");
+ fs.addFeature(sf4);
+ SequenceFeature sf5 = addFeature(fs, 35, 36);
+
+ List<SequenceFeature> overlaps = fs.findOverlappingFeatures(1, 9);
+ assertTrue(overlaps.isEmpty());
+
+ overlaps = fs.findOverlappingFeatures(10, 15);
+ assertEquals(overlaps.size(), 2);
+ assertTrue(overlaps.contains(sf1));
+ assertTrue(overlaps.contains(sf2));
+
+ overlaps = fs.findOverlappingFeatures(45, 60);
+ assertEquals(overlaps.size(), 1);
+ assertTrue(overlaps.contains(sf1));
+
+ overlaps = fs.findOverlappingFeatures(32, 38);
+ assertEquals(overlaps.size(), 3);
+ assertTrue(overlaps.contains(sf1));
+ assertTrue(overlaps.contains(sf2));
+ assertTrue(overlaps.contains(sf5));
+
+ overlaps = fs.findOverlappingFeatures(15, 25);
+ assertEquals(overlaps.size(), 4);
+ assertTrue(overlaps.contains(sf1));
+ assertTrue(overlaps.contains(sf2));
+ assertTrue(overlaps.contains(sf3));
+ assertTrue(overlaps.contains(sf4));
+ }
+
+ @Test(groups = "Functional")
+ public void testFindFeatures_mixed()
+ {
+ FeatureStore fs = new FeatureStore();
+ SequenceFeature sf1 = addFeature(fs, 10, 50);
+ SequenceFeature sf2 = addFeature(fs, 1, 15);
+ SequenceFeature sf3 = addFeature(fs, 20, 30);
+ SequenceFeature sf4 = addFeature(fs, 40, 100);
+ SequenceFeature sf5 = addFeature(fs, 60, 100);
+ SequenceFeature sf6 = addFeature(fs, 70, 70);
+
+ List<SequenceFeature> overlaps = fs.findOverlappingFeatures(200, 200);
+ assertTrue(overlaps.isEmpty());
+
+ overlaps = fs.findOverlappingFeatures(1, 9);
+ assertEquals(overlaps.size(), 1);
+ assertTrue(overlaps.contains(sf2));
+
+ overlaps = fs.findOverlappingFeatures(5, 18);
+ assertEquals(overlaps.size(), 2);
+ assertTrue(overlaps.contains(sf1));
+ assertTrue(overlaps.contains(sf2));
+
+ overlaps = fs.findOverlappingFeatures(30, 40);
+ assertEquals(overlaps.size(), 3);
+ assertTrue(overlaps.contains(sf1));
+ assertTrue(overlaps.contains(sf3));
+ assertTrue(overlaps.contains(sf4));
+
+ overlaps = fs.findOverlappingFeatures(80, 90);
+ assertEquals(overlaps.size(), 2);
+ assertTrue(overlaps.contains(sf4));
+ assertTrue(overlaps.contains(sf5));
+
+ overlaps = fs.findOverlappingFeatures(68, 70);
+ assertEquals(overlaps.size(), 3);
+ assertTrue(overlaps.contains(sf4));
+ assertTrue(overlaps.contains(sf5));
+ assertTrue(overlaps.contains(sf6));
+ }
+
+ /**
+ * Helper method to add a feature of no particular type
+ *
+ * @param fs
+ * @param from
+ * @param to
+ * @return
+ */
+ SequenceFeature addFeature(FeatureStore fs, int from, int to)
+ {
+ SequenceFeature sf1 = new SequenceFeature("", "", from, to, Float.NaN,
+ null);
+ fs.addFeature(sf1);
+ return sf1;
+ }
+
+ @Test(groups = "Functional")
+ public void testFindFeatures_contactFeatures()
+ {
+ FeatureStore fs = new FeatureStore();
+
+ SequenceFeature sf = new SequenceFeature("disulphide bond", "bond", 10,
+ 20, Float.NaN, null);
+ fs.addFeature(sf);
+
+ /*
+ * neither contact point in range
+ */
+ List<SequenceFeature> overlaps = fs.findOverlappingFeatures(1, 9);
+ assertTrue(overlaps.isEmpty());
+
+ /*
+ * neither contact point in range
+ */
+ overlaps = fs.findOverlappingFeatures(11, 19);
+ assertTrue(overlaps.isEmpty());
+
+ /*
+ * first contact point in range
+ */
+ overlaps = fs.findOverlappingFeatures(5, 15);
+ assertEquals(overlaps.size(), 1);
+ assertTrue(overlaps.contains(sf));
+
+ /*
+ * second contact point in range
+ */
+ overlaps = fs.findOverlappingFeatures(15, 25);
+ assertEquals(overlaps.size(), 1);
+ assertTrue(overlaps.contains(sf));
+
+ /*
+ * both contact points in range
+ */
+ overlaps = fs.findOverlappingFeatures(5, 25);
+ assertEquals(overlaps.size(), 1);
+ assertTrue(overlaps.contains(sf));
+ }
+
+ /**
+ * Tests for the method that returns false for an attempt to add a feature
+ * that would enclose, or be enclosed by, another feature
+ */
+ @Test(groups = "Functional")
+ public void testAddNonNestedFeature()
+ {
+ FeatureStore fs = new FeatureStore();
+
+ String type = "Domain";
+ SequenceFeature sf1 = new SequenceFeature(type, type, 10, 20,
+ Float.NaN, null);
+ assertTrue(fs.addNonNestedFeature(sf1));
+
+ // co-located feature is ok
+ SequenceFeature sf2 = new SequenceFeature(type, type, 10, 20,
+ Float.NaN, null);
+ assertTrue(fs.addNonNestedFeature(sf2));
+
+ // overlap left is ok
+ SequenceFeature sf3 = new SequenceFeature(type, type, 5, 15, Float.NaN,
+ null);
+ assertTrue(fs.addNonNestedFeature(sf3));
+
+ // overlap right is ok
+ SequenceFeature sf4 = new SequenceFeature(type, type, 15, 25,
+ Float.NaN, null);
+ assertTrue(fs.addNonNestedFeature(sf4));
+
+ // add enclosing feature is not ok
+ SequenceFeature sf5 = new SequenceFeature(type, type, 10, 21,
+ Float.NaN, null);
+ assertFalse(fs.addNonNestedFeature(sf5));
+ SequenceFeature sf6 = new SequenceFeature(type, type, 4, 15, Float.NaN,
+ null);
+ assertFalse(fs.addNonNestedFeature(sf6));
+ SequenceFeature sf7 = new SequenceFeature(type, type, 1, 50, Float.NaN,
+ null);
+ assertFalse(fs.addNonNestedFeature(sf7));
+
+ // add enclosed feature is not ok
+ SequenceFeature sf8 = new SequenceFeature(type, type, 10, 19,
+ Float.NaN, null);
+ assertFalse(fs.addNonNestedFeature(sf8));
+ SequenceFeature sf9 = new SequenceFeature(type, type, 16, 25,
+ Float.NaN, null);
+ assertFalse(fs.addNonNestedFeature(sf9));
+ SequenceFeature sf10 = new SequenceFeature(type, type, 7, 7, Float.NaN,
+ null);
+ assertFalse(fs.addNonNestedFeature(sf10));
+ }
+
+ @Test(groups = "Functional")
+ public void testGetPositionalFeatures()
+ {
+ FeatureStore store = new FeatureStore();
+ SequenceFeature sf1 = new SequenceFeature("Metal", "desc", 10, 20,
+ Float.NaN, null);
+ store.addFeature(sf1);
+ // same range, different description
+ SequenceFeature sf2 = new SequenceFeature("Metal", "desc2", 10, 20,
+ Float.NaN, null);
+ store.addFeature(sf2);
+ // discontiguous range
+ SequenceFeature sf3 = new SequenceFeature("Metal", "desc", 30, 40,
+ Float.NaN, null);
+ store.addFeature(sf3);
+ // overlapping range
+ SequenceFeature sf4 = new SequenceFeature("Metal", "desc", 15, 35,
+ Float.NaN, null);
+ store.addFeature(sf4);
+ // enclosing range
+ SequenceFeature sf5 = new SequenceFeature("Metal", "desc", 5, 50,
+ Float.NaN, null);
+ store.addFeature(sf5);
+ // non-positional feature
+ SequenceFeature sf6 = new SequenceFeature("Metal", "desc", 0, 0,
+ Float.NaN, null);
+ store.addFeature(sf6);
+ // contact feature
+ SequenceFeature sf7 = new SequenceFeature("Disulphide bond", "desc",
+ 18, 45, Float.NaN, null);
+ store.addFeature(sf7);
+
+ List<SequenceFeature> features = store.getPositionalFeatures();
+ assertEquals(features.size(), 6);
+ assertTrue(features.contains(sf1));
+ assertTrue(features.contains(sf2));
+ assertTrue(features.contains(sf3));
+ assertTrue(features.contains(sf4));
+ assertTrue(features.contains(sf5));
+ assertFalse(features.contains(sf6));
+ assertTrue(features.contains(sf7));
+
+ features = store.getNonPositionalFeatures();
+ assertEquals(features.size(), 1);
+ assertTrue(features.contains(sf6));
+ }
+
+ @Test(groups = "Functional")
+ public void testDelete()
+ {
+ FeatureStore store = new FeatureStore();
+ SequenceFeature sf1 = addFeature(store, 10, 20);
+ assertTrue(store.getPositionalFeatures().contains(sf1));
+
+ /*
+ * simple deletion
+ */
+ assertTrue(store.delete(sf1));
+ assertTrue(store.getPositionalFeatures().isEmpty());
+
+ /*
+ * non-positional feature deletion
+ */
+ SequenceFeature sf2 = addFeature(store, 0, 0);
+ assertFalse(store.getPositionalFeatures().contains(sf2));
+ assertTrue(store.getNonPositionalFeatures().contains(sf2));
+ assertTrue(store.delete(sf2));
+ assertTrue(store.getNonPositionalFeatures().isEmpty());
+
+ /*
+ * contact feature deletion
+ */
+ SequenceFeature sf3 = new SequenceFeature("", "Disulphide Bond", 11,
+ 23, Float.NaN, null);
+ store.addFeature(sf3);
+ assertEquals(store.getPositionalFeatures().size(), 1);
+ assertTrue(store.getPositionalFeatures().contains(sf3));
+ assertTrue(store.delete(sf3));
+ assertTrue(store.getPositionalFeatures().isEmpty());
+
+ /*
+ * nested feature deletion
+ */
+ SequenceFeature sf4 = addFeature(store, 20, 30);
+ SequenceFeature sf5 = addFeature(store, 22, 26); // to NCList
+ SequenceFeature sf6 = addFeature(store, 23, 24); // child of sf5
+ SequenceFeature sf7 = addFeature(store, 25, 25); // sibling of sf6
+ SequenceFeature sf8 = addFeature(store, 24, 24); // child of sf6
+ SequenceFeature sf9 = addFeature(store, 23, 23); // child of sf6
+ assertEquals(store.getPositionalFeatures().size(), 6);
+
+ // delete a node with children - they take its place
+ assertTrue(store.delete(sf6)); // sf8, sf9 should become children of sf5
+ assertEquals(store.getPositionalFeatures().size(), 5);
+ assertFalse(store.getPositionalFeatures().contains(sf6));
+
+ // delete a node with no children
+ assertTrue(store.delete(sf7));
+ assertEquals(store.getPositionalFeatures().size(), 4);
+ assertFalse(store.getPositionalFeatures().contains(sf7));
+
+ // delete root of NCList
+ assertTrue(store.delete(sf5));
+ assertEquals(store.getPositionalFeatures().size(), 3);
+ assertFalse(store.getPositionalFeatures().contains(sf5));
+
+ // continue the killing fields
+ assertTrue(store.delete(sf4));
+ assertEquals(store.getPositionalFeatures().size(), 2);
+ assertFalse(store.getPositionalFeatures().contains(sf4));
+
+ assertTrue(store.delete(sf9));
+ assertEquals(store.getPositionalFeatures().size(), 1);
+ assertFalse(store.getPositionalFeatures().contains(sf9));
+
+ assertTrue(store.delete(sf8));
+ assertTrue(store.getPositionalFeatures().isEmpty());
+ }
+
+ @Test(groups = "Functional")
+ public void testAddFeature()
+ {
+ FeatureStore fs = new FeatureStore();
+
+ SequenceFeature sf1 = new SequenceFeature("Cath", "", 10, 20,
+ Float.NaN, null);
+ SequenceFeature sf2 = new SequenceFeature("Cath", "", 10, 20,
+ Float.NaN, null);
+
+ assertTrue(fs.addFeature(sf1));
+ assertEquals(fs.getFeatureCount(true), 1); // positional
+ assertEquals(fs.getFeatureCount(false), 0); // non-positional
+
+ /*
+ * re-adding the same or an identical feature should fail
+ */
+ assertFalse(fs.addFeature(sf1));
+ assertEquals(fs.getFeatureCount(true), 1);
+ assertFalse(fs.addFeature(sf2));
+ assertEquals(fs.getFeatureCount(true), 1);
+
+ /*
+ * add non-positional
+ */
+ SequenceFeature sf3 = new SequenceFeature("Cath", "", 0, 0, Float.NaN,
+ null);
+ assertTrue(fs.addFeature(sf3));
+ assertEquals(fs.getFeatureCount(true), 1); // positional
+ assertEquals(fs.getFeatureCount(false), 1); // non-positional
+ SequenceFeature sf4 = new SequenceFeature("Cath", "", 0, 0, Float.NaN,
+ null);
+ assertFalse(fs.addFeature(sf4)); // already stored
+ assertEquals(fs.getFeatureCount(true), 1); // positional
+ assertEquals(fs.getFeatureCount(false), 1); // non-positional
+
+ /*
+ * add contact
+ */
+ SequenceFeature sf5 = new SequenceFeature("Disulfide bond", "", 10, 20,
+ Float.NaN, null);
+ assertTrue(fs.addFeature(sf5));
+ assertEquals(fs.getFeatureCount(true), 2); // positional - add 1 for contact
+ assertEquals(fs.getFeatureCount(false), 1); // non-positional
+ SequenceFeature sf6 = new SequenceFeature("Disulfide bond", "", 10, 20,
+ Float.NaN, null);
+ assertFalse(fs.addFeature(sf6)); // already stored
+ assertEquals(fs.getFeatureCount(true), 2); // no change
+ assertEquals(fs.getFeatureCount(false), 1); // no change
+ }
+
+ @Test(groups = "Functional")
+ public void testIsEmpty()
+ {
+ FeatureStore fs = new FeatureStore();
+ assertTrue(fs.isEmpty());
+ assertEquals(fs.getFeatureCount(true), 0);
+
+ /*
+ * non-nested feature
+ */
+ SequenceFeature sf1 = new SequenceFeature("Cath", "", 10, 20,
+ Float.NaN, null);
+ fs.addFeature(sf1);
+ assertFalse(fs.isEmpty());
+ assertEquals(fs.getFeatureCount(true), 1);
+ fs.delete(sf1);
+ assertTrue(fs.isEmpty());
+ assertEquals(fs.getFeatureCount(true), 0);
+
+ /*
+ * non-positional feature
+ */
+ sf1 = new SequenceFeature("Cath", "", 0, 0, Float.NaN, null);
+ fs.addFeature(sf1);
+ assertFalse(fs.isEmpty());
+ assertEquals(fs.getFeatureCount(false), 1); // non-positional
+ assertEquals(fs.getFeatureCount(true), 0); // positional
+ fs.delete(sf1);
+ assertTrue(fs.isEmpty());
+ assertEquals(fs.getFeatureCount(false), 0);
+
+ /*
+ * contact feature
+ */
+ sf1 = new SequenceFeature("Disulfide bond", "", 19, 49, Float.NaN, null);
+ fs.addFeature(sf1);
+ assertFalse(fs.isEmpty());
+ assertEquals(fs.getFeatureCount(true), 1);
+ fs.delete(sf1);
+ assertTrue(fs.isEmpty());
+ assertEquals(fs.getFeatureCount(true), 0);
+
+ /*
+ * sf2, sf3 added as nested features
+ */
+ sf1 = new SequenceFeature("Cath", "", 19, 49, Float.NaN, null);
+ SequenceFeature sf2 = new SequenceFeature("Cath", "", 20, 40,
+ Float.NaN, null);
+ SequenceFeature sf3 = new SequenceFeature("Cath", "", 25, 35,
+ Float.NaN, null);
+ fs.addFeature(sf1);
+ fs.addFeature(sf2);
+ fs.addFeature(sf3);
+ assertEquals(fs.getFeatureCount(true), 3);
+ assertTrue(fs.delete(sf1));
+ assertEquals(fs.getFeatureCount(true), 2);
+ // FeatureStore should now only contain features in the NCList
+ assertTrue(fs.nonNestedFeatures.isEmpty());
+ assertEquals(fs.nestedFeatures.size(), 2);
+ assertFalse(fs.isEmpty());
+ assertTrue(fs.delete(sf2));
+ assertEquals(fs.getFeatureCount(true), 1);
+ assertFalse(fs.isEmpty());
+ assertTrue(fs.delete(sf3));
+ assertEquals(fs.getFeatureCount(true), 0);
+ assertTrue(fs.isEmpty()); // all gone
+ }
+
+ @Test(groups = "Functional")
+ public void testGetFeatureGroups()
+ {
+ FeatureStore fs = new FeatureStore();
+ assertTrue(fs.getFeatureGroups(true).isEmpty());
+ assertTrue(fs.getFeatureGroups(false).isEmpty());
+
+ SequenceFeature sf1 = new SequenceFeature("Cath", "desc", 10, 20, 1f, "group1");
+ fs.addFeature(sf1);
+ Set<String> groups = fs.getFeatureGroups(true);
+ assertEquals(groups.size(), 1);
+ assertTrue(groups.contains("group1"));
+
+ /*
+ * add another feature of the same group, delete one, delete both
+ */
+ SequenceFeature sf2 = new SequenceFeature("Cath", "desc", 20, 30, 1f, "group1");
+ fs.addFeature(sf2);
+ groups = fs.getFeatureGroups(true);
+ assertEquals(groups.size(), 1);
+ assertTrue(groups.contains("group1"));
+ fs.delete(sf2);
+ groups = fs.getFeatureGroups(true);
+ assertEquals(groups.size(), 1);
+ assertTrue(groups.contains("group1"));
+ fs.delete(sf1);
+ groups = fs.getFeatureGroups(true);
+ assertTrue(fs.getFeatureGroups(true).isEmpty());
+
+ SequenceFeature sf3 = new SequenceFeature("Cath", "desc", 20, 30, 1f, "group2");
+ fs.addFeature(sf3);
+ SequenceFeature sf4 = new SequenceFeature("Cath", "desc", 20, 30, 1f, "Group2");
+ fs.addFeature(sf4);
+ SequenceFeature sf5 = new SequenceFeature("Cath", "desc", 20, 30, 1f, null);
+ fs.addFeature(sf5);
+ groups = fs.getFeatureGroups(true);
+ assertEquals(groups.size(), 3);
+ assertTrue(groups.contains("group2"));
+ assertTrue(groups.contains("Group2")); // case sensitive
+ assertTrue(groups.contains(null)); // null allowed
+ assertTrue(fs.getFeatureGroups(false).isEmpty()); // non-positional
+
+ fs.delete(sf3);
+ groups = fs.getFeatureGroups(true);
+ assertEquals(groups.size(), 2);
+ assertFalse(groups.contains("group2"));
+ fs.delete(sf4);
+ groups = fs.getFeatureGroups(true);
+ assertEquals(groups.size(), 1);
+ assertFalse(groups.contains("Group2"));
+ fs.delete(sf5);
+ groups = fs.getFeatureGroups(true);
+ assertTrue(groups.isEmpty());
+
+ /*
+ * add non-positional feature
+ */
+ SequenceFeature sf6 = new SequenceFeature("Cath", "desc", 0, 0, 1f,
+ "CathGroup");
+ fs.addFeature(sf6);
+ groups = fs.getFeatureGroups(false);
+ assertEquals(groups.size(), 1);
+ assertTrue(groups.contains("CathGroup"));
+ assertTrue(fs.delete(sf6));
+ assertTrue(fs.getFeatureGroups(false).isEmpty());
+ }
+
+ @Test(groups = "Functional")
+ public void testGetTotalFeatureLength()
+ {
+ FeatureStore fs = new FeatureStore();
+ assertEquals(fs.getTotalFeatureLength(), 0);
+
+ addFeature(fs, 10, 20); // 11
+ assertEquals(fs.getTotalFeatureLength(), 11);
+ addFeature(fs, 17, 37); // 21
+ SequenceFeature sf1 = addFeature(fs, 14, 74); // 61
+ assertEquals(fs.getTotalFeatureLength(), 93);
+
+ // non-positional features don't count
+ SequenceFeature sf2 = new SequenceFeature("Cath", "desc", 0, 0, 1f,
+ "group1");
+ fs.addFeature(sf2);
+ assertEquals(fs.getTotalFeatureLength(), 93);
+
+ // contact features count 1
+ SequenceFeature sf3 = new SequenceFeature("disulphide bond", "desc",
+ 15, 35, 1f, "group1");
+ fs.addFeature(sf3);
+ assertEquals(fs.getTotalFeatureLength(), 94);
+
+ assertTrue(fs.delete(sf1));
+ assertEquals(fs.getTotalFeatureLength(), 33);
+ assertFalse(fs.delete(sf1));
+ assertEquals(fs.getTotalFeatureLength(), 33);
+ assertTrue(fs.delete(sf2));
+ assertEquals(fs.getTotalFeatureLength(), 33);
+ assertTrue(fs.delete(sf3));
+ assertEquals(fs.getTotalFeatureLength(), 32);
+ }
+
+ @Test(groups = "Functional")
+ public void testGetFeatureLength()
+ {
+ /*
+ * positional feature
+ */
+ SequenceFeature sf1 = new SequenceFeature("Cath", "desc", 10, 20, 1f, "group1");
+ assertEquals(FeatureStore.getFeatureLength(sf1), 11);
+
+ /*
+ * non-positional feature
+ */
+ SequenceFeature sf2 = new SequenceFeature("Cath", "desc", 0, 0, 1f,
+ "CathGroup");
+ assertEquals(FeatureStore.getFeatureLength(sf2), 0);
+
+ /*
+ * contact feature counts 1
+ */
+ SequenceFeature sf3 = new SequenceFeature("Disulphide Bond", "desc",
+ 14, 28, 1f, "AGroup");
+ assertEquals(FeatureStore.getFeatureLength(sf3), 1);
+ }
+
+ @Test(groups = "Functional")
+ public void testMin()
+ {
+ assertEquals(FeatureStore.min(Float.NaN, Float.NaN), Float.NaN);
+ assertEquals(FeatureStore.min(Float.NaN, 2f), 2f);
+ assertEquals(FeatureStore.min(-2f, Float.NaN), -2f);
+ assertEquals(FeatureStore.min(2f, -3f), -3f);
+ }
+
+ @Test(groups = "Functional")
+ public void testMax()
+ {
+ assertEquals(FeatureStore.max(Float.NaN, Float.NaN), Float.NaN);
+ assertEquals(FeatureStore.max(Float.NaN, 2f), 2f);
+ assertEquals(FeatureStore.max(-2f, Float.NaN), -2f);
+ assertEquals(FeatureStore.max(2f, -3f), 2f);
+ }
+
+ @Test(groups = "Functional")
+ public void testGetMinimumScore_getMaximumScore()
+ {
+ FeatureStore fs = new FeatureStore();
+ assertEquals(fs.getMinimumScore(true), Float.NaN); // positional
+ assertEquals(fs.getMaximumScore(true), Float.NaN);
+ assertEquals(fs.getMinimumScore(false), Float.NaN); // non-positional
+ assertEquals(fs.getMaximumScore(false), Float.NaN);
+
+ // add features with no score
+ SequenceFeature sf1 = new SequenceFeature("type", "desc", 0, 0,
+ Float.NaN, "group");
+ fs.addFeature(sf1);
+ SequenceFeature sf2 = new SequenceFeature("type", "desc", 10, 20,
+ Float.NaN, "group");
+ fs.addFeature(sf2);
+ assertEquals(fs.getMinimumScore(true), Float.NaN);
+ assertEquals(fs.getMaximumScore(true), Float.NaN);
+ assertEquals(fs.getMinimumScore(false), Float.NaN);
+ assertEquals(fs.getMaximumScore(false), Float.NaN);
+
+ // add positional features with score
+ SequenceFeature sf3 = new SequenceFeature("type", "desc", 10, 20, 1f,
+ "group");
+ fs.addFeature(sf3);
+ SequenceFeature sf4 = new SequenceFeature("type", "desc", 12, 16, 4f,
+ "group");
+ fs.addFeature(sf4);
+ assertEquals(fs.getMinimumScore(true), 1f);
+ assertEquals(fs.getMaximumScore(true), 4f);
+ assertEquals(fs.getMinimumScore(false), Float.NaN);
+ assertEquals(fs.getMaximumScore(false), Float.NaN);
+
+ // add non-positional features with score
+ SequenceFeature sf5 = new SequenceFeature("type", "desc", 0, 0, 11f,
+ "group");
+ fs.addFeature(sf5);
+ SequenceFeature sf6 = new SequenceFeature("type", "desc", 0, 0, -7f,
+ "group");
+ fs.addFeature(sf6);
+ assertEquals(fs.getMinimumScore(true), 1f);
+ assertEquals(fs.getMaximumScore(true), 4f);
+ assertEquals(fs.getMinimumScore(false), -7f);
+ assertEquals(fs.getMaximumScore(false), 11f);
+
+ // delete one positional and one non-positional
+ // min-max should be recomputed
+ assertTrue(fs.delete(sf6));
+ assertTrue(fs.delete(sf3));
+ assertEquals(fs.getMinimumScore(true), 4f);
+ assertEquals(fs.getMaximumScore(true), 4f);
+ assertEquals(fs.getMinimumScore(false), 11f);
+ assertEquals(fs.getMaximumScore(false), 11f);
+
+ // delete remaining features with score
+ assertTrue(fs.delete(sf4));
+ assertTrue(fs.delete(sf5));
+ assertEquals(fs.getMinimumScore(true), Float.NaN);
+ assertEquals(fs.getMaximumScore(true), Float.NaN);
+ assertEquals(fs.getMinimumScore(false), Float.NaN);
+ assertEquals(fs.getMaximumScore(false), Float.NaN);
+
+ // delete all features
+ assertTrue(fs.delete(sf1));
+ assertTrue(fs.delete(sf2));
+ assertTrue(fs.isEmpty());
+ assertEquals(fs.getMinimumScore(true), Float.NaN);
+ assertEquals(fs.getMaximumScore(true), Float.NaN);
+ assertEquals(fs.getMinimumScore(false), Float.NaN);
+ assertEquals(fs.getMaximumScore(false), Float.NaN);
+ }
+
+ @Test(groups = "Functional")
+ public void testListContains()
+ {
+ assertFalse(FeatureStore.listContains(null, null));
+ List<SequenceFeature> features = new ArrayList<SequenceFeature>();
+ assertFalse(FeatureStore.listContains(features, null));
+
+ SequenceFeature sf1 = new SequenceFeature("type1", "desc1", 20, 30, 3f,
+ "group1");
+ assertFalse(FeatureStore.listContains(null, sf1));
+ assertFalse(FeatureStore.listContains(features, sf1));
+
+ features.add(sf1);
+ SequenceFeature sf2 = new SequenceFeature("type1", "desc1", 20, 30, 3f,
+ "group1");
+ SequenceFeature sf3 = new SequenceFeature("type1", "desc1", 20, 40, 3f,
+ "group1");
+
+ // sf2.equals(sf1) so contains should return true
+ assertTrue(FeatureStore.listContains(features, sf2));
+ assertFalse(FeatureStore.listContains(features, sf3));
+ }
+
+ @Test(groups = "Functional")
+ public void testGetFeaturesForGroup()
+ {
+ FeatureStore fs = new FeatureStore();
+
+ /*
+ * with no features
+ */
+ assertTrue(fs.getFeaturesForGroup(true, null).isEmpty());
+ assertTrue(fs.getFeaturesForGroup(false, null).isEmpty());
+ assertTrue(fs.getFeaturesForGroup(true, "uniprot").isEmpty());
+ assertTrue(fs.getFeaturesForGroup(false, "uniprot").isEmpty());
+
+ /*
+ * sf1: positional feature in the null group
+ */
+ SequenceFeature sf1 = new SequenceFeature("Pfam", "desc", 4, 10, 0f,
+ null);
+ fs.addFeature(sf1);
+ assertTrue(fs.getFeaturesForGroup(true, "uniprot").isEmpty());
+ assertTrue(fs.getFeaturesForGroup(false, "uniprot").isEmpty());
+ assertTrue(fs.getFeaturesForGroup(false, null).isEmpty());
+ List<SequenceFeature> features = fs.getFeaturesForGroup(true, null);
+ assertEquals(features.size(), 1);
+ assertTrue(features.contains(sf1));
+
+ /*
+ * sf2: non-positional feature in the null group
+ * sf3: positional feature in a non-null group
+ * sf4: non-positional feature in a non-null group
+ */
+ SequenceFeature sf2 = new SequenceFeature("Pfam", "desc", 0, 0, 0f,
+ null);
+ SequenceFeature sf3 = new SequenceFeature("Pfam", "desc", 4, 10, 0f,
+ "Uniprot");
+ SequenceFeature sf4 = new SequenceFeature("Pfam", "desc", 0, 0, 0f,
+ "Rfam");
+ fs.addFeature(sf2);
+ fs.addFeature(sf3);
+ fs.addFeature(sf4);
+
+ features = fs.getFeaturesForGroup(true, null);
+ assertEquals(features.size(), 1);
+ assertTrue(features.contains(sf1));
+
+ features = fs.getFeaturesForGroup(false, null);
+ assertEquals(features.size(), 1);
+ assertTrue(features.contains(sf2));
+
+ features = fs.getFeaturesForGroup(true, "Uniprot");
+ assertEquals(features.size(), 1);
+ assertTrue(features.contains(sf3));
+
+ features = fs.getFeaturesForGroup(false, "Rfam");
+ assertEquals(features.size(), 1);
+ assertTrue(features.contains(sf4));
+ }
+
+ @Test(groups = "Functional")
+ public void testShiftFeatures()
+ {
+ FeatureStore fs = new FeatureStore();
+ assertFalse(fs.shiftFeatures(1));
+
+ SequenceFeature sf1 = new SequenceFeature("Cath", "", 2, 5, 0f, null);
+ fs.addFeature(sf1);
+ // nested feature:
+ SequenceFeature sf2 = new SequenceFeature("Cath", "", 8, 14, 0f, null);
+ fs.addFeature(sf2);
+ // contact feature:
+ SequenceFeature sf3 = new SequenceFeature("Disulfide bond", "", 23, 32,
+ 0f, null);
+ fs.addFeature(sf3);
+ // non-positional feature:
+ SequenceFeature sf4 = new SequenceFeature("Cath", "", 0, 0, 0f, null);
+ fs.addFeature(sf4);
+
+ /*
+ * shift features right by 5
+ */
+ assertTrue(fs.shiftFeatures(5));
+
+ // non-positional features untouched:
+ List<SequenceFeature> nonPos = fs.getNonPositionalFeatures();
+ assertEquals(nonPos.size(), 1);
+ assertTrue(nonPos.contains(sf4));
+
+ // positional features are replaced
+ List<SequenceFeature> pos = fs.getPositionalFeatures();
+ assertEquals(pos.size(), 3);
+ assertFalse(pos.contains(sf1));
+ assertFalse(pos.contains(sf2));
+ assertFalse(pos.contains(sf3));
+ SequenceFeatures.sortFeatures(pos, true); // ascending start pos
+ assertEquals(pos.get(0).getBegin(), 7);
+ assertEquals(pos.get(0).getEnd(), 10);
+ assertEquals(pos.get(1).getBegin(), 13);
+ assertEquals(pos.get(1).getEnd(), 19);
+ assertEquals(pos.get(2).getBegin(), 28);
+ assertEquals(pos.get(2).getEnd(), 37);
+
+ /*
+ * now shift left by 15
+ * feature at [7-10] should be removed
+ * feature at [13-19] should become [1-4]
+ */
+ assertTrue(fs.shiftFeatures(-15));
+ pos = fs.getPositionalFeatures();
+ assertEquals(pos.size(), 2);
+ SequenceFeatures.sortFeatures(pos, true);
+ assertEquals(pos.get(0).getBegin(), 1);
+ assertEquals(pos.get(0).getEnd(), 4);
+ assertEquals(pos.get(1).getBegin(), 13);
+ assertEquals(pos.get(1).getEnd(), 22);
+ }
+
+ @Test(groups = "Functional")
+ public void testDelete_readd()
+ {
+ /*
+ * add a feature and a nested feature
+ */
+ FeatureStore store = new FeatureStore();
+ SequenceFeature sf1 = addFeature(store, 10, 20);
+ // sf2 is nested in sf1 so will be stored in nestedFeatures
+ SequenceFeature sf2 = addFeature(store, 12, 14);
+ List<SequenceFeature> features = store.getPositionalFeatures();
+ assertEquals(features.size(), 2);
+ assertTrue(features.contains(sf1));
+ assertTrue(features.contains(sf2));
+ assertTrue(store.nonNestedFeatures.contains(sf1));
+ assertTrue(store.nestedFeatures.contains(sf2));
+
+ /*
+ * delete the first feature
+ */
+ assertTrue(store.delete(sf1));
+ features = store.getPositionalFeatures();
+ assertFalse(features.contains(sf1));
+ assertTrue(features.contains(sf2));
+
+ /*
+ * re-add the 'nested' feature; is it now duplicated?
+ */
+ store.addFeature(sf2);
+ features = store.getPositionalFeatures();
+ assertEquals(features.size(), 1);
+ assertTrue(features.contains(sf2));
+ }
+
+ @Test(groups = "Functional")
+ public void testContains()
+ {
+ FeatureStore fs = new FeatureStore();
+ SequenceFeature sf1 = new SequenceFeature("Cath", "", 10, 20,
+ Float.NaN, "group1");
+ SequenceFeature sf2 = new SequenceFeature("Cath", "", 10, 20,
+ Float.NaN, "group2");
+ SequenceFeature sf3 = new SequenceFeature("Cath", "", 0, 0, Float.NaN,
+ "group1");
+ SequenceFeature sf4 = new SequenceFeature("Cath", "", 0, 0, 0f,
+ "group1");
+ SequenceFeature sf5 = new SequenceFeature("Disulphide Bond", "", 5, 15,
+ Float.NaN, "group1");
+ SequenceFeature sf6 = new SequenceFeature("Disulphide Bond", "", 5, 15,
+ Float.NaN, "group2");
+
+ fs.addFeature(sf1);
+ fs.addFeature(sf3);
+ fs.addFeature(sf5);
+ assertTrue(fs.contains(sf1)); // positional feature
+ assertTrue(fs.contains(new SequenceFeature(sf1))); // identical feature
+ assertFalse(fs.contains(sf2)); // different group
+ assertTrue(fs.contains(sf3)); // non-positional
+ assertTrue(fs.contains(new SequenceFeature(sf3)));
+ assertFalse(fs.contains(sf4)); // different score
+ assertTrue(fs.contains(sf5)); // contact feature
+ assertTrue(fs.contains(new SequenceFeature(sf5)));
+ assertFalse(fs.contains(sf6)); // different group
+
+ /*
+ * add a nested feature
+ */
+ SequenceFeature sf7 = new SequenceFeature("Cath", "", 12, 16,
+ Float.NaN, "group1");
+ fs.addFeature(sf7);
+ assertTrue(fs.contains(sf7));
+ assertTrue(fs.contains(new SequenceFeature(sf7)));
+
+ /*
+ * delete the outer (enclosing, non-nested) feature
+ */
+ fs.delete(sf1);
+ assertFalse(fs.contains(sf1));
+ assertTrue(fs.contains(sf7));
+ }
+}
--- /dev/null
+package jalview.datamodel.features;
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertSame;
+import static org.testng.Assert.assertTrue;
+
+import jalview.datamodel.ContiguousI;
+import jalview.datamodel.Range;
+import jalview.datamodel.SequenceFeature;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Random;
+
+import junit.extensions.PA;
+
+import org.testng.annotations.DataProvider;
+import org.testng.annotations.Test;
+
+public class NCListTest
+{
+
+ private Random random = new Random(107);
+
+ private Comparator<ContiguousI> sorter = new RangeComparator(true);
+
+ /**
+ * A basic sanity test of the constructor
+ */
+ @Test(groups = "Functional")
+ public void testConstructor()
+ {
+ List<Range> ranges = new ArrayList<Range>();
+ ranges.add(new Range(20, 20));
+ ranges.add(new Range(10, 20));
+ ranges.add(new Range(15, 30));
+ ranges.add(new Range(10, 30));
+ ranges.add(new Range(11, 19));
+ ranges.add(new Range(10, 20));
+ ranges.add(new Range(1, 100));
+
+ NCList<Range> ncl = new NCList<Range>(ranges);
+ String expected = "[1-100 [10-30 [10-20 [10-20 [11-19]]]], 15-30 [20-20]]";
+ assertEquals(ncl.toString(), expected);
+ assertTrue(ncl.isValid());
+
+ Collections.reverse(ranges);
+ ncl = new NCList<Range>(ranges);
+ assertEquals(ncl.toString(), expected);
+ assertTrue(ncl.isValid());
+ }
+
+ @Test(groups = "Functional")
+ public void testFindOverlaps()
+ {
+ List<Range> ranges = new ArrayList<Range>();
+ ranges.add(new Range(20, 50));
+ ranges.add(new Range(30, 70));
+ ranges.add(new Range(1, 100));
+ ranges.add(new Range(70, 120));
+
+ NCList<Range> ncl = new NCList<Range>(ranges);
+
+ List<Range> overlaps = ncl.findOverlaps(121, 122);
+ assertEquals(overlaps.size(), 0);
+
+ overlaps = ncl.findOverlaps(21, 22);
+ assertEquals(overlaps.size(), 2);
+ assertEquals(((ContiguousI) overlaps.get(0)).getBegin(), 1);
+ assertEquals(((ContiguousI) overlaps.get(0)).getEnd(), 100);
+ assertEquals(((ContiguousI) overlaps.get(1)).getBegin(), 20);
+ assertEquals(((ContiguousI) overlaps.get(1)).getEnd(), 50);
+
+ overlaps = ncl.findOverlaps(110, 110);
+ assertEquals(overlaps.size(), 1);
+ assertEquals(((ContiguousI) overlaps.get(0)).getBegin(), 70);
+ assertEquals(((ContiguousI) overlaps.get(0)).getEnd(), 120);
+ }
+
+ @Test(groups = "Functional")
+ public void testAdd_onTheEnd()
+ {
+ List<Range> ranges = new ArrayList<Range>();
+ ranges.add(new Range(20, 50));
+ NCList<Range> ncl = new NCList<Range>(ranges);
+ assertEquals(ncl.toString(), "[20-50]");
+ assertTrue(ncl.isValid());
+
+ ncl.add(new Range(60, 70));
+ assertEquals(ncl.toString(), "[20-50, 60-70]");
+ assertTrue(ncl.isValid());
+ }
+
+ @Test(groups = "Functional")
+ public void testAdd_inside()
+ {
+ List<Range> ranges = new ArrayList<Range>();
+ ranges.add(new Range(20, 50));
+ NCList<Range> ncl = new NCList<Range>(ranges);
+ assertEquals(ncl.toString(), "[20-50]");
+ assertTrue(ncl.isValid());
+
+ ncl.add(new Range(30, 40));
+ assertEquals(ncl.toString(), "[20-50 [30-40]]");
+ }
+
+ @Test(groups = "Functional")
+ public void testAdd_onTheFront()
+ {
+ List<Range> ranges = new ArrayList<Range>();
+ ranges.add(new Range(20, 50));
+ NCList<Range> ncl = new NCList<Range>(ranges);
+ assertEquals(ncl.toString(), "[20-50]");
+ assertTrue(ncl.isValid());
+
+ ncl.add(new Range(5, 15));
+ assertEquals(ncl.toString(), "[5-15, 20-50]");
+ assertTrue(ncl.isValid());
+ }
+
+ @Test(groups = "Functional")
+ public void testAdd_enclosing()
+ {
+ List<Range> ranges = new ArrayList<Range>();
+ ranges.add(new Range(20, 50));
+ ranges.add(new Range(30, 60));
+ NCList<Range> ncl = new NCList<Range>(ranges);
+ assertEquals(ncl.toString(), "[20-50, 30-60]");
+ assertTrue(ncl.isValid());
+ assertEquals(ncl.getStart(), 20);
+
+ ncl.add(new Range(10, 70));
+ assertEquals(ncl.toString(), "[10-70 [20-50, 30-60]]");
+ assertTrue(ncl.isValid());
+ }
+
+ @Test(groups = "Functional")
+ public void testAdd_spanning()
+ {
+ List<Range> ranges = new ArrayList<Range>();
+ ranges.add(new Range(20, 40));
+ ranges.add(new Range(60, 70));
+ NCList<Range> ncl = new NCList<Range>(ranges);
+ assertEquals(ncl.toString(), "[20-40, 60-70]");
+ assertTrue(ncl.isValid());
+
+ ncl.add(new Range(30, 50));
+ assertEquals(ncl.toString(), "[20-40, 30-50, 60-70]");
+ assertTrue(ncl.isValid());
+
+ ncl.add(new Range(40, 65));
+ assertEquals(ncl.toString(), "[20-40, 30-50, 40-65, 60-70]");
+ assertTrue(ncl.isValid());
+ }
+
+ /**
+ * Provides the scales for pseudo-random NCLists i.e. the range of the maximal
+ * [0-scale] interval to be stored
+ *
+ * @return
+ */
+ @DataProvider(name = "scalesOfLife")
+ public Object[][] getScales()
+ {
+ return new Object[][] { new Integer[] { 10 }, new Integer[] { 100 } };
+ }
+
+ /**
+ * Do a number of pseudo-random (reproducible) builds of an NCList, to
+ * exercise as many methods of the class as possible while generating the
+ * range of possible structure topologies
+ * <ul>
+ * <li>verify that add adds an entry and increments size</li>
+ * <li>...except where the entry is already contained (by equals test)</li>
+ * <li>verify that the structure is valid at all stages of construction</li>
+ * <li>generate, run and verify a range of overlap queries</li>
+ * <li>tear down the structure by deleting entries, verifying correctness at
+ * each stage</li>
+ * </ul>
+ */
+ @Test(groups = "Functional", dataProvider = "scalesOfLife")
+ public void test_pseudoRandom(Integer scale)
+ {
+ NCList<SequenceFeature> ncl = new NCList<SequenceFeature>();
+ List<SequenceFeature> features = new ArrayList<SequenceFeature>(scale);
+
+ testAdd_pseudoRandom(scale, ncl, features);
+
+ /*
+ * sort the list of added ranges - this doesn't affect the test,
+ * just makes it easier to inspect the data in the debugger
+ */
+ Collections.sort(features, sorter);
+
+ testFindOverlaps_pseudoRandom(ncl, scale, features);
+
+ testDelete_pseudoRandom(ncl, features);
+ }
+
+ /**
+ * Pick randomly selected entries to delete in turn, checking the NCList size
+ * and validity at each stage, until it is empty
+ *
+ * @param ncl
+ * @param features
+ */
+ protected void testDelete_pseudoRandom(NCList<SequenceFeature> ncl,
+ List<SequenceFeature> features)
+ {
+ int deleted = 0;
+
+ while (!features.isEmpty())
+ {
+ assertEquals(ncl.size(), features.size());
+ int toDelete = random.nextInt(features.size());
+ SequenceFeature entry = features.get(toDelete);
+ assertTrue(ncl.contains(entry), String.format(
+ "NCList doesn't contain entry [%d] '%s'!", deleted,
+ entry.toString()));
+
+ ncl.delete(entry);
+ assertFalse(ncl.contains(entry), String.format(
+ "NCList still contains deleted entry [%d] '%s'!", deleted,
+ entry.toString()));
+ features.remove(toDelete);
+ deleted++;
+
+ assertTrue(ncl.isValid(), String.format(
+ "NCList invalid after %d deletions, last deleted was '%s'",
+ deleted, entry.toString()));
+
+ /*
+ * brute force check that deleting one entry didn't delete any others
+ */
+ for (int i = 0; i < features.size(); i++)
+ {
+ SequenceFeature sf = features.get(i);
+ assertTrue(ncl.contains(sf), String.format(
+ "NCList doesn't contain entry [%d] %s after deleting '%s'!",
+ i, sf.toString(), entry.toString()));
+ }
+ }
+ assertEquals(ncl.size(), 0); // all gone
+ }
+
+ /**
+ * Randomly generate entries and add them to the NCList, checking its validity
+ * and size at each stage. A few entries should be duplicates (by equals test)
+ * so not get added.
+ *
+ * @param scale
+ * @param ncl
+ * @param features
+ */
+ protected void testAdd_pseudoRandom(Integer scale,
+ NCList<SequenceFeature> ncl,
+ List<SequenceFeature> features)
+ {
+ int count = 0;
+ final int size = 50;
+
+ for (int i = 0; i < size; i++)
+ {
+ int r1 = random.nextInt(scale + 1);
+ int r2 = random.nextInt(scale + 1);
+ int from = Math.min(r1, r2);
+ int to = Math.max(r1, r2);
+
+ /*
+ * choice of two feature values means that occasionally an identical
+ * feature may be generated, in which case it should not be added
+ */
+ float value = (float) i % 2;
+ SequenceFeature feature = new SequenceFeature("Pfam", "", from, to,
+ value, "group");
+
+ /*
+ * add to NCList - with duplicate entries (by equals) disallowed
+ */
+ ncl.add(feature, false);
+ if (features.contains(feature))
+ {
+ System.out.println("Duplicate feature generated "
+ + feature.toString());
+ }
+ else
+ {
+ features.add(feature);
+ count++;
+ }
+
+ /*
+ * check list format is valid at each stage of its construction
+ */
+ assertTrue(ncl.isValid(),
+ String.format("Failed for scale = %d, i=%d", scale, i));
+ assertEquals(ncl.size(), count);
+ }
+ // System.out.println(ncl.prettyPrint());
+ }
+
+ /**
+ * A helper method that generates pseudo-random range queries and veries that
+ * findOverlaps returns the correct matches
+ *
+ * @param ncl
+ * the NCList to query
+ * @param scale
+ * ncl maximal range is [0, scale]
+ * @param features
+ * a list of the ranges stored in ncl
+ */
+ protected void testFindOverlaps_pseudoRandom(NCList<SequenceFeature> ncl,
+ int scale,
+ List<SequenceFeature> features)
+ {
+ int halfScale = scale / 2;
+ int minIterations = 20;
+
+ /*
+ * generates ranges in [-halfScale, scale+halfScale]
+ * - some should be internal to [0, scale] P = 1/4
+ * - some should lie before 0 P = 1/16
+ * - some should lie after scale P = 1/16
+ * - some should overlap left P = 1/4
+ * - some should overlap right P = 1/4
+ * - some should enclose P = 1/8
+ *
+ * 50 iterations give a 96% probability of including the
+ * unlikeliest case; keep going until we have done all!
+ */
+ boolean inside = false;
+ boolean enclosing = false;
+ boolean before = false;
+ boolean after = false;
+ boolean overlapLeft = false;
+ boolean overlapRight = false;
+ boolean allCasesCovered = false;
+
+ int i = 0;
+ while (i < minIterations || !allCasesCovered)
+ {
+ i++;
+ int r1 = random.nextInt((scale + 1) * 2);
+ int r2 = random.nextInt((scale + 1) * 2);
+ int from = Math.min(r1, r2) - halfScale;
+ int to = Math.max(r1, r2) - halfScale;
+
+ /*
+ * ensure all cases of interest get covered
+ */
+ inside |= from >= 0 && to <= scale;
+ enclosing |= from <= 0 && to >= scale;
+ before |= to < 0;
+ after |= from > scale;
+ overlapLeft |= from < 0 && to >= 0 && to <= scale;
+ overlapRight |= from >= 0 && from <= scale && to > scale;
+ if (!allCasesCovered)
+ {
+ allCasesCovered |= inside && enclosing && before && after
+ && overlapLeft && overlapRight;
+ if (allCasesCovered)
+ {
+ System.out
+ .println(String
+ .format("Covered all findOverlaps cases after %d iterations for scale %d",
+ i, scale));
+ }
+ }
+
+ verifyFindOverlaps(ncl, from, to, features);
+ }
+ }
+
+ /**
+ * A helper method that verifies that overlaps found by interrogating an
+ * NCList correctly match those found by brute force search
+ *
+ * @param ncl
+ * @param from
+ * @param to
+ * @param features
+ */
+ protected void verifyFindOverlaps(NCList<SequenceFeature> ncl, int from,
+ int to, List<SequenceFeature> features)
+ {
+ List<SequenceFeature> overlaps = ncl.findOverlaps(from, to);
+
+ /*
+ * check returned entries do indeed overlap from-to range
+ */
+ for (ContiguousI sf : overlaps)
+ {
+ int begin = sf.getBegin();
+ int end = sf.getEnd();
+ assertTrue(begin <= to && end >= from, String.format(
+ "[%d, %d] does not overlap query range [%d, %d]", begin, end,
+ from, to));
+ }
+
+ /*
+ * check overlapping ranges are included in the results
+ * (the test above already shows non-overlapping ranges are not)
+ */
+ for (ContiguousI sf : features)
+ {
+ int begin = sf.getBegin();
+ int end = sf.getEnd();
+ if (begin <= to && end >= from)
+ {
+ boolean found = overlaps.contains(sf);
+ assertTrue(found, String.format(
+ "[%d, %d] missing in query range [%d, %d]", begin, end,
+ from, to));
+ }
+ }
+ }
+
+ @Test(groups = "Functional")
+ public void testGetEntries()
+ {
+ List<Range> ranges = new ArrayList<Range>();
+ Range r1 = new Range(20, 20);
+ Range r2 = new Range(10, 20);
+ Range r3 = new Range(15, 30);
+ Range r4 = new Range(10, 30);
+ Range r5 = new Range(11, 19);
+ Range r6 = new Range(10, 20);
+ ranges.add(r1);
+ ranges.add(r2);
+ ranges.add(r3);
+ ranges.add(r4);
+ ranges.add(r5);
+ ranges.add(r6);
+
+ NCList<Range> ncl = new NCList<Range>(ranges);
+ Range r7 = new Range(1, 100);
+ ncl.add(r7);
+
+ List<Range> contents = ncl.getEntries();
+ assertEquals(contents.size(), 7);
+ assertTrue(contents.contains(r1));
+ assertTrue(contents.contains(r2));
+ assertTrue(contents.contains(r3));
+ assertTrue(contents.contains(r4));
+ assertTrue(contents.contains(r5));
+ assertTrue(contents.contains(r6));
+ assertTrue(contents.contains(r7));
+
+ ncl = new NCList<Range>();
+ assertTrue(ncl.getEntries().isEmpty());
+ }
+
+ @Test(groups = "Functional")
+ public void testDelete()
+ {
+ List<Range> ranges = new ArrayList<Range>();
+ Range r1 = new Range(20, 30);
+ ranges.add(r1);
+ NCList<Range> ncl = new NCList<Range>(ranges);
+ assertTrue(ncl.getEntries().contains(r1));
+
+ Range r2 = new Range(20, 30);
+ assertFalse(ncl.delete(null)); // null argument
+ assertFalse(ncl.delete(r2)); // never added
+ assertTrue(ncl.delete(r1)); // success
+ assertTrue(ncl.getEntries().isEmpty());
+
+ /*
+ * tests where object.equals() == true
+ */
+ NCList<SequenceFeature> features = new NCList<SequenceFeature>();
+ SequenceFeature sf1 = new SequenceFeature("type", "desc", 1, 10, 2f,
+ "group");
+ SequenceFeature sf2 = new SequenceFeature("type", "desc", 1, 10, 2f,
+ "group");
+ features.add(sf1);
+ assertEquals(sf1, sf2); // sf1.equals(sf2)
+ assertFalse(features.delete(sf2)); // equality is not enough for deletion
+ assertTrue(features.getEntries().contains(sf1)); // still there!
+ assertTrue(features.delete(sf1));
+ assertTrue(features.getEntries().isEmpty()); // gone now
+
+ /*
+ * test with duplicate objects in NCList
+ */
+ features.add(sf1);
+ features.add(sf1);
+ assertEquals(features.getEntries().size(), 2);
+ assertSame(features.getEntries().get(0), sf1);
+ assertSame(features.getEntries().get(1), sf1);
+ assertTrue(features.delete(sf1)); // first match only is deleted
+ assertTrue(features.contains(sf1));
+ assertEquals(features.size(), 1);
+ assertTrue(features.delete(sf1));
+ assertTrue(features.getEntries().isEmpty());
+ }
+
+ @Test(groups = "Functional")
+ public void testAdd_overlapping()
+ {
+ List<Range> ranges = new ArrayList<Range>();
+ ranges.add(new Range(40, 50));
+ ranges.add(new Range(20, 30));
+ NCList<Range> ncl = new NCList<Range>(ranges);
+ assertEquals(ncl.toString(), "[20-30, 40-50]");
+ assertTrue(ncl.isValid());
+
+ /*
+ * add range overlapping internally
+ */
+ ncl.add(new Range(25, 35));
+ assertEquals(ncl.toString(), "[20-30, 25-35, 40-50]");
+ assertTrue(ncl.isValid());
+
+ /*
+ * add range overlapping last range
+ */
+ ncl.add(new Range(45, 55));
+ assertEquals(ncl.toString(), "[20-30, 25-35, 40-50, 45-55]");
+ assertTrue(ncl.isValid());
+
+ /*
+ * add range overlapping first range
+ */
+ ncl.add(new Range(15, 25));
+ assertEquals(ncl.toString(), "[15-25, 20-30, 25-35, 40-50, 45-55]");
+ assertTrue(ncl.isValid());
+ }
+
+ /**
+ * Test the contains method (which uses object equals test)
+ */
+ @Test(groups = "Functional")
+ public void testContains()
+ {
+ NCList<SequenceFeature> ncl = new NCList<SequenceFeature>();
+ SequenceFeature sf1 = new SequenceFeature("type", "desc", 1, 10, 2f,
+ "group");
+ SequenceFeature sf2 = new SequenceFeature("type", "desc", 1, 10, 2f,
+ "group");
+ SequenceFeature sf3 = new SequenceFeature("type", "desc", 1, 10, 2f,
+ "anothergroup");
+ ncl.add(sf1);
+
+ assertTrue(ncl.contains(sf1));
+ assertTrue(ncl.contains(sf2)); // sf1.equals(sf2)
+ assertFalse(ncl.contains(sf3)); // !sf1.equals(sf3)
+
+ /*
+ * make some deeper structure in the NCList
+ */
+ SequenceFeature sf4 = new SequenceFeature("type", "desc", 2, 9, 2f,
+ "group");
+ ncl.add(sf4);
+ assertTrue(ncl.contains(sf4));
+ SequenceFeature sf5 = new SequenceFeature("type", "desc", 4, 5, 2f,
+ "group");
+ SequenceFeature sf6 = new SequenceFeature("type", "desc", 6, 8, 2f,
+ "group");
+ ncl.add(sf5);
+ ncl.add(sf6);
+ assertTrue(ncl.contains(sf5));
+ assertTrue(ncl.contains(sf6));
+ }
+
+ @Test(groups = "Functional")
+ public void testIsValid()
+ {
+ List<Range> ranges = new ArrayList<Range>();
+ Range r1 = new Range(40, 50);
+ ranges.add(r1);
+ NCList<Range> ncl = new NCList<Range>(ranges);
+ assertTrue(ncl.isValid());
+
+ Range r2 = new Range(42, 44);
+ ncl.add(r2);
+ assertTrue(ncl.isValid());
+ Range r3 = new Range(46, 48);
+ ncl.add(r3);
+ assertTrue(ncl.isValid());
+ Range r4 = new Range(43, 43);
+ ncl.add(r4);
+ assertTrue(ncl.isValid());
+
+ assertEquals(ncl.toString(), "[40-50 [42-44 [43-43], 46-48]]");
+ assertTrue(ncl.isValid());
+
+ PA.setValue(r1, "start", 43);
+ assertFalse(ncl.isValid()); // r2 not inside r1
+ PA.setValue(r1, "start", 40);
+ assertTrue(ncl.isValid());
+
+ PA.setValue(r3, "start", 41);
+ assertFalse(ncl.isValid()); // r3 should precede r2
+ PA.setValue(r3, "start", 46);
+ assertTrue(ncl.isValid());
+
+ PA.setValue(r4, "start", 41);
+ assertFalse(ncl.isValid()); // r4 not inside r2
+ PA.setValue(r4, "start", 43);
+ assertTrue(ncl.isValid());
+
+ PA.setValue(r4, "start", 44);
+ assertFalse(ncl.isValid()); // r4 has reverse range
+ }
+
+ @Test(groups = "Functional")
+ public void testPrettyPrint()
+ {
+ /*
+ * construct NCList from a list of ranges
+ * they are sorted then assembled into NCList subregions
+ * notice that 42-42 end up inside 41-46
+ */
+ List<Range> ranges = new ArrayList<Range>();
+ ranges.add(new Range(40, 50));
+ ranges.add(new Range(45, 55));
+ ranges.add(new Range(40, 45));
+ ranges.add(new Range(41, 46));
+ ranges.add(new Range(42, 42));
+ ranges.add(new Range(42, 42));
+ NCList<Range> ncl = new NCList<Range>(ranges);
+ assertTrue(ncl.isValid());
+ assertEquals(ncl.toString(),
+ "[40-50 [40-45], 41-46 [42-42 [42-42]], 45-55]");
+ String expected = "40-50\n 40-45\n41-46\n 42-42\n 42-42\n45-55\n";
+ assertEquals(ncl.prettyPrint(), expected);
+
+ /*
+ * repeat but now add ranges one at a time
+ * notice that 42-42 end up inside 40-50 so we get
+ * a different but equal valid NCList structure
+ */
+ ranges.clear();
+ ncl = new NCList<Range>(ranges);
+ ncl.add(new Range(40, 50));
+ ncl.add(new Range(45, 55));
+ ncl.add(new Range(40, 45));
+ ncl.add(new Range(41, 46));
+ ncl.add(new Range(42, 42));
+ ncl.add(new Range(42, 42));
+ assertTrue(ncl.isValid());
+ assertEquals(ncl.toString(),
+ "[40-50 [40-45 [42-42 [42-42]], 41-46], 45-55]");
+ expected = "40-50\n 40-45\n 42-42\n 42-42\n 41-46\n45-55\n";
+ assertEquals(ncl.prettyPrint(), expected);
+ }
+
+ /**
+ * A test that shows different valid trees can be constructed from the same
+ * set of ranges, depending on the order of construction
+ */
+ @Test(groups = "Functional")
+ public void testConstructor_alternativeTrees()
+ {
+ List<Range> ranges = new ArrayList<Range>();
+ ranges.add(new Range(10, 60));
+ ranges.add(new Range(20, 30));
+ ranges.add(new Range(40, 50));
+
+ /*
+ * constructor with greedy traversal of sorted ranges to build nested
+ * containment lists results in 20-30 inside 10-60, 40-50 a sibling
+ */
+ NCList<Range> ncl = new NCList<Range>(ranges);
+ assertEquals(ncl.toString(), "[10-60 [20-30], 40-50]");
+ assertTrue(ncl.isValid());
+
+ /*
+ * adding ranges one at a time results in 40-50
+ * a sibling of 20-30 inside 10-60
+ */
+ ncl = new NCList<Range>(new Range(10, 60));
+ ncl.add(new Range(20, 30));
+ ncl.add(new Range(40, 50));
+ assertEquals(ncl.toString(), "[10-60 [20-30, 40-50]]");
+ assertTrue(ncl.isValid());
+ }
+}
--- /dev/null
+package jalview.datamodel.features;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertTrue;
+
+import jalview.datamodel.Range;
+import jalview.datamodel.SequenceFeature;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import junit.extensions.PA;
+
+import org.testng.annotations.Test;
+
+public class NCNodeTest
+{
+ @Test(groups = "Functional")
+ public void testAdd()
+ {
+ Range r1 = new Range(10, 20);
+ NCNode<Range> node = new NCNode<Range>(r1);
+ assertEquals(node.getBegin(), 10);
+ Range r2 = new Range(10, 15);
+ node.add(r2);
+
+ List<Range> contents = new ArrayList<Range>();
+ node.getEntries(contents);
+ assertEquals(contents.size(), 2);
+ assertTrue(contents.contains(r1));
+ assertTrue(contents.contains(r2));
+ }
+
+ @Test(
+ groups = "Functional",
+ expectedExceptions = { IllegalArgumentException.class })
+ public void testAdd_invalidRangeStart()
+ {
+ Range r1 = new Range(10, 20);
+ NCNode<Range> node = new NCNode<Range>(r1);
+ assertEquals(node.getBegin(), 10);
+ Range r2 = new Range(9, 15);
+ node.add(r2);
+ }
+
+ @Test(
+ groups = "Functional",
+ expectedExceptions = { IllegalArgumentException.class })
+ public void testAdd_invalidRangeEnd()
+ {
+ Range r1 = new Range(10, 20);
+ NCNode<Range> node = new NCNode<Range>(r1);
+ assertEquals(node.getBegin(), 10);
+ Range r2 = new Range(12, 21);
+ node.add(r2);
+ }
+
+ @Test(groups = "Functional")
+ public void testGetEntries()
+ {
+ Range r1 = new Range(10, 20);
+ NCNode<Range> node = new NCNode<Range>(r1);
+ List<Range> entries = new ArrayList<Range>();
+
+ node.getEntries(entries);
+ assertEquals(entries.size(), 1);
+ assertTrue(entries.contains(r1));
+
+ // clearing the returned list does not affect the NCNode
+ entries.clear();
+ node.getEntries(entries);
+ assertEquals(entries.size(), 1);
+ assertTrue(entries.contains(r1));
+
+ Range r2 = new Range(15, 18);
+ node.add(r2);
+ entries.clear();
+ node.getEntries(entries);
+ assertEquals(entries.size(), 2);
+ assertTrue(entries.contains(r1));
+ assertTrue(entries.contains(r2));
+ }
+
+ /**
+ * Tests for the contains method (uses entry.equals() test)
+ */
+ @Test(groups = "Functional")
+ public void testContains()
+ {
+ SequenceFeature sf1 = new SequenceFeature("type", "desc", 1, 10, 2f,
+ "group");
+ SequenceFeature sf2 = new SequenceFeature("type", "desc", 1, 10, 2f,
+ "group");
+ SequenceFeature sf3 = new SequenceFeature("type", "desc", 1, 10, 2f,
+ "anothergroup");
+ NCNode<SequenceFeature> node = new NCNode<SequenceFeature>(sf1);
+
+ assertFalse(node.contains(null));
+ assertTrue(node.contains(sf1));
+ assertTrue(node.contains(sf2)); // sf1.equals(sf2)
+ assertFalse(node.contains(sf3)); // !sf1.equals(sf3)
+ }
+
+ /**
+ * Test method that checks for valid structure. Valid means that all
+ * subregions (if any) lie within the root range, and that all subregions have
+ * valid structure.
+ */
+ @Test(groups = "Functional")
+ public void testIsValid()
+ {
+ Range r1 = new Range(10, 20);
+ Range r2 = new Range(14, 15);
+ Range r3 = new Range(16, 17);
+ NCNode<Range> node = new NCNode<Range>(r1);
+ node.add(r2);
+ node.add(r3);
+
+ /*
+ * node has root range [10-20] and contains an
+ * NCList of [14-15, 16-17]
+ */
+ assertTrue(node.isValid());
+ PA.setValue(r1, "start", 15);
+ assertFalse(node.isValid()); // r2 not within r1
+ PA.setValue(r1, "start", 10);
+ assertTrue(node.isValid());
+ PA.setValue(r1, "end", 16);
+ assertFalse(node.isValid()); // r3 not within r1
+ PA.setValue(r1, "end", 20);
+ assertTrue(node.isValid());
+ PA.setValue(r3, "start", 12);
+ assertFalse(node.isValid()); // r3 should precede r2
+ }
+}
--- /dev/null
+package jalview.datamodel.features;
+
+import static org.testng.Assert.assertEquals;
+
+import jalview.datamodel.ContiguousI;
+import jalview.datamodel.Range;
+
+import java.util.Comparator;
+
+import org.testng.annotations.Test;
+
+public class RangeComparatorTest
+{
+
+ @Test(groups = "Functional")
+ public void testCompare()
+ {
+ RangeComparator comp = new RangeComparator(true);
+
+ // same position, same length
+ assertEquals(comp.compare(10, 10, 20, 20), 0);
+ // same position, len1 > len2
+ assertEquals(comp.compare(10, 10, 20, 19), -1);
+ // same position, len1 < len2
+ assertEquals(comp.compare(10, 10, 20, 21), 1);
+ // pos1 > pos2
+ assertEquals(comp.compare(11, 10, 20, 20), 1);
+ // pos1 < pos2
+ assertEquals(comp.compare(10, 11, 20, 10), -1);
+ }
+
+ @Test(groups = "Functional")
+ public void testCompare_byStart()
+ {
+ Comparator<ContiguousI> comp = RangeComparator.BY_START_POSITION;
+
+ // same start position, same length
+ assertEquals(comp.compare(new Range(10, 20), new Range(10, 20)), 0);
+ // same start position, len1 > len2
+ assertEquals(comp.compare(new Range(10, 20), new Range(10, 19)), -1);
+ // same start position, len1 < len2
+ assertEquals(comp.compare(new Range(10, 18), new Range(10, 20)), 1);
+ // pos1 > pos2
+ assertEquals(comp.compare(new Range(11, 20), new Range(10, 20)), 1);
+ // pos1 < pos2
+ assertEquals(comp.compare(new Range(10, 20), new Range(11, 20)), -1);
+ }
+
+ @Test(groups = "Functional")
+ public void testCompare_byEnd()
+ {
+ Comparator<ContiguousI> comp = RangeComparator.BY_END_POSITION;
+
+ // same end position, same length
+ assertEquals(comp.compare(new Range(10, 20), new Range(10, 20)), 0);
+ // same end position, len1 > len2
+ assertEquals(comp.compare(new Range(10, 20), new Range(11, 20)), -1);
+ // same end position, len1 < len2
+ assertEquals(comp.compare(new Range(11, 20), new Range(10, 20)), 1);
+ // end1 > end2
+ assertEquals(comp.compare(new Range(10, 21), new Range(10, 20)), 1);
+ // end1 < end2
+ assertEquals(comp.compare(new Range(10, 20), new Range(10, 21)), -1);
+ }
+}
--- /dev/null
+package jalview.datamodel.features;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertSame;
+import static org.testng.Assert.assertTrue;
+
+import jalview.datamodel.SequenceFeature;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import junit.extensions.PA;
+
+import org.testng.annotations.Test;
+
+public class SequenceFeaturesTest
+{
+ @Test(groups = "Functional")
+ public void testConstructor()
+ {
+ SequenceFeaturesI store = new SequenceFeatures();
+ assertFalse(store.hasFeatures());
+
+ store = new SequenceFeatures((List<SequenceFeature>) null);
+ assertFalse(store.hasFeatures());
+
+ List<SequenceFeature> features = new ArrayList<>();
+ store = new SequenceFeatures(features);
+ assertFalse(store.hasFeatures());
+
+ SequenceFeature sf1 = new SequenceFeature("Metal", "desc", 10, 20,
+ Float.NaN, null);
+ features.add(sf1);
+ SequenceFeature sf2 = new SequenceFeature("Metal", "desc", 15, 18,
+ Float.NaN, null);
+ features.add(sf2); // nested
+ SequenceFeature sf3 = new SequenceFeature("Pfam", "desc2", 0, 0,
+ Float.NaN, null); // non-positional
+ features.add(sf3);
+ store = new SequenceFeatures(features);
+ assertTrue(store.hasFeatures());
+ assertEquals(2, store.getFeatureCount(true)); // positional
+ assertEquals(1, store.getFeatureCount(false)); // non-positional
+ assertFalse(store.add(sf1)); // already contained
+ assertFalse(store.add(sf2)); // already contained
+ assertFalse(store.add(sf3)); // already contained
+ }
+
+ @Test(groups = "Functional")
+ public void testGetPositionalFeatures()
+ {
+ SequenceFeaturesI store = new SequenceFeatures();
+ SequenceFeature sf1 = new SequenceFeature("Metal", "desc", 10, 20,
+ Float.NaN, null);
+ store.add(sf1);
+ // same range, different description
+ SequenceFeature sf2 = new SequenceFeature("Metal", "desc2", 10, 20,
+ Float.NaN, null);
+ store.add(sf2);
+ // discontiguous range
+ SequenceFeature sf3 = new SequenceFeature("Metal", "desc", 30, 40,
+ Float.NaN, null);
+ store.add(sf3);
+ // overlapping range
+ SequenceFeature sf4 = new SequenceFeature("Metal", "desc", 15, 35,
+ Float.NaN, null);
+ store.add(sf4);
+ // enclosing range
+ SequenceFeature sf5 = new SequenceFeature("Metal", "desc", 5, 50,
+ Float.NaN, null);
+ store.add(sf5);
+ // non-positional feature
+ SequenceFeature sf6 = new SequenceFeature("Metal", "desc", 0, 0,
+ Float.NaN, null);
+ store.add(sf6);
+ // contact feature
+ SequenceFeature sf7 = new SequenceFeature("Disulphide bond", "desc",
+ 18, 45, Float.NaN, null);
+ store.add(sf7);
+ // different feature type
+ SequenceFeature sf8 = new SequenceFeature("Pfam", "desc", 30, 40,
+ Float.NaN, null);
+ store.add(sf8);
+ SequenceFeature sf9 = new SequenceFeature("Pfam", "desc", 15, 35,
+ Float.NaN, null);
+ store.add(sf9);
+
+ /*
+ * get all positional features
+ */
+ List<SequenceFeature> features = store.getPositionalFeatures();
+ assertEquals(features.size(), 8);
+ assertTrue(features.contains(sf1));
+ assertTrue(features.contains(sf2));
+ assertTrue(features.contains(sf3));
+ assertTrue(features.contains(sf4));
+ assertTrue(features.contains(sf5));
+ assertFalse(features.contains(sf6)); // non-positional
+ assertTrue(features.contains(sf7));
+ assertTrue(features.contains(sf8));
+ assertTrue(features.contains(sf9));
+
+ /*
+ * get features by type
+ */
+ assertTrue(store.getPositionalFeatures((String) null).isEmpty());
+ assertTrue(store.getPositionalFeatures("Cath").isEmpty());
+ assertTrue(store.getPositionalFeatures("METAL").isEmpty());
+
+ features = store.getPositionalFeatures("Metal");
+ assertEquals(features.size(), 5);
+ assertTrue(features.contains(sf1));
+ assertTrue(features.contains(sf2));
+ assertTrue(features.contains(sf3));
+ assertTrue(features.contains(sf4));
+ assertTrue(features.contains(sf5));
+ assertFalse(features.contains(sf6));
+
+ features = store.getPositionalFeatures("Disulphide bond");
+ assertEquals(features.size(), 1);
+ assertTrue(features.contains(sf7));
+
+ features = store.getPositionalFeatures("Pfam");
+ assertEquals(features.size(), 2);
+ assertTrue(features.contains(sf8));
+ assertTrue(features.contains(sf9));
+ }
+
+ @Test(groups = "Functional")
+ public void testGetContactFeatures()
+ {
+ SequenceFeaturesI store = new SequenceFeatures();
+ // non-contact
+ SequenceFeature sf1 = new SequenceFeature("Metal", "desc", 10, 20,
+ Float.NaN, null);
+ store.add(sf1);
+ // non-positional
+ SequenceFeature sf2 = new SequenceFeature("Metal", "desc", 0, 0,
+ Float.NaN, null);
+ store.add(sf2);
+ // contact feature
+ SequenceFeature sf3 = new SequenceFeature("Disulphide bond", "desc",
+ 18, 45, Float.NaN, null);
+ store.add(sf3);
+ // repeat for different feature type
+ SequenceFeature sf4 = new SequenceFeature("Pfam", "desc", 10, 20,
+ Float.NaN, null);
+ store.add(sf4);
+ SequenceFeature sf5 = new SequenceFeature("Pfam", "desc", 0, 0,
+ Float.NaN, null);
+ store.add(sf5);
+ SequenceFeature sf6 = new SequenceFeature("Disulfide bond", "desc", 18,
+ 45, Float.NaN, null);
+ store.add(sf6);
+
+ /*
+ * get all contact features
+ */
+ List<SequenceFeature> features = store.getContactFeatures();
+ assertEquals(features.size(), 2);
+ assertTrue(features.contains(sf3));
+ assertTrue(features.contains(sf6));
+
+ /*
+ * get contact features by type
+ */
+ assertTrue(store.getContactFeatures((String) null).isEmpty());
+ assertTrue(store.getContactFeatures("Cath").isEmpty());
+ assertTrue(store.getContactFeatures("Pfam").isEmpty());
+ assertTrue(store.getContactFeatures("DISULPHIDE BOND").isEmpty());
+
+ features = store.getContactFeatures("Disulphide bond");
+ assertEquals(features.size(), 1);
+ assertTrue(features.contains(sf3));
+
+ features = store.getContactFeatures("Disulfide bond");
+ assertEquals(features.size(), 1);
+ assertTrue(features.contains(sf6));
+ }
+
+ @Test(groups = "Functional")
+ public void testGetNonPositionalFeatures()
+ {
+ SequenceFeaturesI store = new SequenceFeatures();
+ // positional
+ SequenceFeature sf1 = new SequenceFeature("Metal", "desc", 10, 20,
+ Float.NaN, null);
+ store.add(sf1);
+ // non-positional
+ SequenceFeature sf2 = new SequenceFeature("Metal", "desc", 0, 0,
+ Float.NaN, null);
+ store.add(sf2);
+ // contact feature
+ SequenceFeature sf3 = new SequenceFeature("Disulphide bond", "desc",
+ 18, 45, Float.NaN, null);
+ store.add(sf3);
+ // repeat for different feature type
+ SequenceFeature sf4 = new SequenceFeature("Pfam", "desc", 10, 20,
+ Float.NaN, null);
+ store.add(sf4);
+ SequenceFeature sf5 = new SequenceFeature("Pfam", "desc", 0, 0,
+ Float.NaN, null);
+ store.add(sf5);
+ SequenceFeature sf6 = new SequenceFeature("Disulfide bond", "desc", 18,
+ 45, Float.NaN, null);
+ store.add(sf6);
+ // one more non-positional, different description
+ SequenceFeature sf7 = new SequenceFeature("Pfam", "desc2", 0, 0,
+ Float.NaN, null);
+ store.add(sf7);
+
+ /*
+ * get all non-positional features
+ */
+ List<SequenceFeature> features = store.getNonPositionalFeatures();
+ assertEquals(features.size(), 3);
+ assertTrue(features.contains(sf2));
+ assertTrue(features.contains(sf5));
+ assertTrue(features.contains(sf7));
+
+ /*
+ * get non-positional features by type
+ */
+ assertTrue(store.getNonPositionalFeatures((String) null).isEmpty());
+ assertTrue(store.getNonPositionalFeatures("Cath").isEmpty());
+ assertTrue(store.getNonPositionalFeatures("PFAM").isEmpty());
+
+ features = store.getNonPositionalFeatures("Metal");
+ assertEquals(features.size(), 1);
+ assertTrue(features.contains(sf2));
+
+ features = store.getNonPositionalFeatures("Pfam");
+ assertEquals(features.size(), 2);
+ assertTrue(features.contains(sf5));
+ assertTrue(features.contains(sf7));
+ }
+
+ /**
+ * Helper method to add a feature of no particular type
+ *
+ * @param sf
+ * @param type
+ * @param from
+ * @param to
+ * @return
+ */
+ SequenceFeature addFeature(SequenceFeaturesI sf, String type, int from,
+ int to)
+ {
+ SequenceFeature sf1 = new SequenceFeature(type, "", from, to,
+ Float.NaN,
+ null);
+ sf.add(sf1);
+ return sf1;
+ }
+
+ @Test(groups = "Functional")
+ public void testFindFeatures()
+ {
+ SequenceFeaturesI sf = new SequenceFeatures();
+ SequenceFeature sf1 = addFeature(sf, "Pfam", 10, 50);
+ SequenceFeature sf2 = addFeature(sf, "Pfam", 1, 15);
+ SequenceFeature sf3 = addFeature(sf, "Pfam", 20, 30);
+ SequenceFeature sf4 = addFeature(sf, "Pfam", 40, 100);
+ SequenceFeature sf5 = addFeature(sf, "Pfam", 60, 100);
+ SequenceFeature sf6 = addFeature(sf, "Pfam", 70, 70);
+ SequenceFeature sf7 = addFeature(sf, "Cath", 10, 50);
+ SequenceFeature sf8 = addFeature(sf, "Cath", 1, 15);
+ SequenceFeature sf9 = addFeature(sf, "Cath", 20, 30);
+ SequenceFeature sf10 = addFeature(sf, "Cath", 40, 100);
+ SequenceFeature sf11 = addFeature(sf, "Cath", 60, 100);
+ SequenceFeature sf12 = addFeature(sf, "Cath", 70, 70);
+
+ List<SequenceFeature> overlaps = sf.findFeatures(200, 200, "Pfam");
+ assertTrue(overlaps.isEmpty());
+
+ overlaps = sf.findFeatures( 1, 9, "Pfam");
+ assertEquals(overlaps.size(), 1);
+ assertTrue(overlaps.contains(sf2));
+
+ overlaps = sf.findFeatures( 5, 18, "Pfam");
+ assertEquals(overlaps.size(), 2);
+ assertTrue(overlaps.contains(sf1));
+ assertTrue(overlaps.contains(sf2));
+
+ overlaps = sf.findFeatures(30, 40, "Pfam");
+ assertEquals(overlaps.size(), 3);
+ assertTrue(overlaps.contains(sf1));
+ assertTrue(overlaps.contains(sf3));
+ assertTrue(overlaps.contains(sf4));
+
+ overlaps = sf.findFeatures( 80, 90, "Pfam");
+ assertEquals(overlaps.size(), 2);
+ assertTrue(overlaps.contains(sf4));
+ assertTrue(overlaps.contains(sf5));
+
+ overlaps = sf.findFeatures( 68, 70, "Pfam");
+ assertEquals(overlaps.size(), 3);
+ assertTrue(overlaps.contains(sf4));
+ assertTrue(overlaps.contains(sf5));
+ assertTrue(overlaps.contains(sf6));
+
+ overlaps = sf.findFeatures(16, 69, "Cath");
+ assertEquals(overlaps.size(), 4);
+ assertTrue(overlaps.contains(sf7));
+ assertFalse(overlaps.contains(sf8));
+ assertTrue(overlaps.contains(sf9));
+ assertTrue(overlaps.contains(sf10));
+ assertTrue(overlaps.contains(sf11));
+ assertFalse(overlaps.contains(sf12));
+
+ assertTrue(sf.findFeatures(0, 1000, "Metal").isEmpty());
+
+ overlaps = sf.findFeatures(7, 7, (String) null);
+ assertTrue(overlaps.isEmpty());
+ }
+
+ @Test(groups = "Functional")
+ public void testDelete()
+ {
+ SequenceFeaturesI sf = new SequenceFeatures();
+ SequenceFeature sf1 = addFeature(sf, "Pfam", 10, 50);
+ assertTrue(sf.getPositionalFeatures().contains(sf1));
+
+ assertFalse(sf.delete(null));
+ SequenceFeature sf2 = new SequenceFeature("Cath", "", 10, 15, 0f, null);
+ assertFalse(sf.delete(sf2)); // not added, can't delete it
+ assertTrue(sf.delete(sf1));
+ assertTrue(sf.getPositionalFeatures().isEmpty());
+ }
+
+ @Test(groups = "Functional")
+ public void testHasFeatures()
+ {
+ SequenceFeaturesI sf = new SequenceFeatures();
+ assertFalse(sf.hasFeatures());
+
+ SequenceFeature sf1 = addFeature(sf, "Pfam", 10, 50);
+ assertTrue(sf.hasFeatures());
+
+ sf.delete(sf1);
+ assertFalse(sf.hasFeatures());
+ }
+
+ /**
+ * Tests for the method that gets feature groups for positional or
+ * non-positional features
+ */
+ @Test(groups = "Functional")
+ public void testGetFeatureGroups()
+ {
+ SequenceFeaturesI sf = new SequenceFeatures();
+ assertTrue(sf.getFeatureGroups(true).isEmpty());
+ assertTrue(sf.getFeatureGroups(false).isEmpty());
+
+ /*
+ * add a non-positional feature (begin/end = 0/0)
+ */
+ SequenceFeature sfx = new SequenceFeature("AType", "Desc", 0, 0, 0f,
+ "AGroup");
+ sf.add(sfx);
+ Set<String> groups = sf.getFeatureGroups(true); // for positional
+ assertTrue(groups.isEmpty());
+ groups = sf.getFeatureGroups(false); // for non-positional
+ assertEquals(groups.size(), 1);
+ assertTrue(groups.contains("AGroup"));
+ groups = sf.getFeatureGroups(false, "AType");
+ assertEquals(groups.size(), 1);
+ assertTrue(groups.contains("AGroup"));
+ groups = sf.getFeatureGroups(true, "AnotherType");
+ assertTrue(groups.isEmpty());
+
+ /*
+ * add, then delete, more non-positional features of different types
+ */
+ SequenceFeature sfy = new SequenceFeature("AnotherType", "Desc", 0, 0,
+ 0f,
+ "AnotherGroup");
+ sf.add(sfy);
+ SequenceFeature sfz = new SequenceFeature("AThirdType", "Desc", 0, 0,
+ 0f,
+ null);
+ sf.add(sfz);
+ groups = sf.getFeatureGroups(false);
+ assertEquals(groups.size(), 3);
+ assertTrue(groups.contains("AGroup"));
+ assertTrue(groups.contains("AnotherGroup"));
+ assertTrue(groups.contains(null)); // null is a possible group
+ sf.delete(sfz);
+ sf.delete(sfy);
+ groups = sf.getFeatureGroups(false);
+ assertEquals(groups.size(), 1);
+ assertTrue(groups.contains("AGroup"));
+
+ /*
+ * add positional features
+ */
+ SequenceFeature sf1 = new SequenceFeature("Pfam", "Desc", 10, 50, 0f,
+ "PfamGroup");
+ sf.add(sf1);
+ groups = sf.getFeatureGroups(true);
+ assertEquals(groups.size(), 1);
+ assertTrue(groups.contains("PfamGroup"));
+ groups = sf.getFeatureGroups(false); // non-positional unchanged
+ assertEquals(groups.size(), 1);
+ assertTrue(groups.contains("AGroup"));
+
+ SequenceFeature sf2 = new SequenceFeature("Cath", "Desc", 10, 50, 0f,
+ null);
+ sf.add(sf2);
+ groups = sf.getFeatureGroups(true);
+ assertEquals(groups.size(), 2);
+ assertTrue(groups.contains("PfamGroup"));
+ assertTrue(groups.contains(null));
+
+ sf.delete(sf1);
+ sf.delete(sf2);
+ assertTrue(sf.getFeatureGroups(true).isEmpty());
+
+ SequenceFeature sf3 = new SequenceFeature("CDS", "", 10, 50, 0f,
+ "Ensembl");
+ sf.add(sf3);
+ SequenceFeature sf4 = new SequenceFeature("exon", "", 10, 50, 0f,
+ "Ensembl");
+ sf.add(sf4);
+ groups = sf.getFeatureGroups(true);
+ assertEquals(groups.size(), 1);
+ assertTrue(groups.contains("Ensembl"));
+
+ /*
+ * delete last Ensembl group feature from CDS features
+ * but still have one in exon features
+ */
+ sf.delete(sf3);
+ groups = sf.getFeatureGroups(true);
+ assertEquals(groups.size(), 1);
+ assertTrue(groups.contains("Ensembl"));
+
+ /*
+ * delete the last non-positional feature
+ */
+ sf.delete(sfx);
+ groups = sf.getFeatureGroups(false);
+ assertTrue(groups.isEmpty());
+ }
+
+ @Test(groups = "Functional")
+ public void testGetFeatureTypesForGroups()
+ {
+ SequenceFeaturesI sf = new SequenceFeatures();
+ assertTrue(sf.getFeatureTypesForGroups(true, (String) null).isEmpty());
+
+ /*
+ * add feature with group = "Uniprot", type = "helix"
+ */
+ String groupUniprot = "Uniprot";
+ SequenceFeature sf1 = new SequenceFeature("helix", "Desc", 10, 50, 0f,
+ groupUniprot);
+ sf.add(sf1);
+ Set<String> groups = sf.getFeatureTypesForGroups(true, groupUniprot);
+ assertEquals(groups.size(), 1);
+ assertTrue(groups.contains("helix"));
+ assertTrue(sf.getFeatureTypesForGroups(true, (String) null).isEmpty());
+
+ /*
+ * add feature with group = "Uniprot", type = "strand"
+ */
+ SequenceFeature sf2 = new SequenceFeature("strand", "Desc", 10, 50, 0f,
+ groupUniprot);
+ sf.add(sf2);
+ groups = sf.getFeatureTypesForGroups(true, groupUniprot);
+ assertEquals(groups.size(), 2);
+ assertTrue(groups.contains("helix"));
+ assertTrue(groups.contains("strand"));
+
+ /*
+ * delete the "strand" Uniprot feature - still have "helix"
+ */
+ sf.delete(sf2);
+ groups = sf.getFeatureTypesForGroups(true, groupUniprot);
+ assertEquals(groups.size(), 1);
+ assertTrue(groups.contains("helix"));
+
+ /*
+ * delete the "helix" Uniprot feature - none left
+ */
+ sf.delete(sf1);
+ assertTrue(sf.getFeatureTypesForGroups(true, groupUniprot).isEmpty());
+
+ /*
+ * add some null group features
+ */
+ SequenceFeature sf3 = new SequenceFeature("strand", "Desc", 10, 50, 0f,
+ null);
+ sf.add(sf3);
+ SequenceFeature sf4 = new SequenceFeature("turn", "Desc", 10, 50, 0f,
+ null);
+ sf.add(sf4);
+ groups = sf.getFeatureTypesForGroups(true, (String) null);
+ assertEquals(groups.size(), 2);
+ assertTrue(groups.contains("strand"));
+ assertTrue(groups.contains("turn"));
+
+ /*
+ * add strand/Cath and turn/Scop and query for one or both groups
+ * (find feature types for groups selected in Feature Settings)
+ */
+ SequenceFeature sf5 = new SequenceFeature("strand", "Desc", 10, 50, 0f,
+ "Cath");
+ sf.add(sf5);
+ SequenceFeature sf6 = new SequenceFeature("turn", "Desc", 10, 50, 0f,
+ "Scop");
+ sf.add(sf6);
+ groups = sf.getFeatureTypesForGroups(true, "Cath");
+ assertEquals(groups.size(), 1);
+ assertTrue(groups.contains("strand"));
+ groups = sf.getFeatureTypesForGroups(true, "Scop");
+ assertEquals(groups.size(), 1);
+ assertTrue(groups.contains("turn"));
+ groups = sf.getFeatureTypesForGroups(true, "Cath", "Scop");
+ assertEquals(groups.size(), 2);
+ assertTrue(groups.contains("turn"));
+ assertTrue(groups.contains("strand"));
+ // alternative vararg syntax
+ groups = sf.getFeatureTypesForGroups(true, new String[] { "Cath",
+ "Scop" });
+ assertEquals(groups.size(), 2);
+ assertTrue(groups.contains("turn"));
+ assertTrue(groups.contains("strand"));
+ }
+
+ @Test(groups = "Functional")
+ public void testGetFeatureTypes()
+ {
+ SequenceFeaturesI store = new SequenceFeatures();
+ Set<String> types = store.getFeatureTypes();
+ assertTrue(types.isEmpty());
+
+ SequenceFeature sf1 = new SequenceFeature("Metal", "desc", 10, 20,
+ Float.NaN, null);
+ store.add(sf1);
+ types = store.getFeatureTypes();
+ assertEquals(types.size(), 1);
+ assertTrue(types.contains("Metal"));
+
+ // null type is rejected...
+ SequenceFeature sf2 = new SequenceFeature(null, "desc", 10, 20,
+ Float.NaN, null);
+ assertFalse(store.add(sf2));
+ types = store.getFeatureTypes();
+ assertEquals(types.size(), 1);
+ assertFalse(types.contains(null));
+ assertTrue(types.contains("Metal"));
+
+ /*
+ * add non-positional feature
+ */
+ SequenceFeature sf3 = new SequenceFeature("Pfam", "desc", 0, 0,
+ Float.NaN, null);
+ store.add(sf3);
+ types = store.getFeatureTypes();
+ assertEquals(types.size(), 2);
+ assertTrue(types.contains("Pfam"));
+
+ /*
+ * add contact feature
+ */
+ SequenceFeature sf4 = new SequenceFeature("Disulphide Bond", "desc",
+ 10, 20, Float.NaN, null);
+ store.add(sf4);
+ types = store.getFeatureTypes();
+ assertEquals(types.size(), 3);
+ assertTrue(types.contains("Disulphide Bond"));
+
+ /*
+ * add another Pfam
+ */
+ SequenceFeature sf5 = new SequenceFeature("Pfam", "desc", 10, 20,
+ Float.NaN, null);
+ store.add(sf5);
+ types = store.getFeatureTypes();
+ assertEquals(types.size(), 3); // unchanged
+
+ /*
+ * delete first Pfam - still have one
+ */
+ assertTrue(store.delete(sf3));
+ types = store.getFeatureTypes();
+ assertEquals(types.size(), 3);
+ assertTrue(types.contains("Pfam"));
+
+ /*
+ * delete second Pfam - no longer have one
+ */
+ assertTrue(store.delete(sf5));
+ types = store.getFeatureTypes();
+ assertEquals(types.size(), 2);
+ assertFalse(types.contains("Pfam"));
+ }
+
+ @Test(groups = "Functional")
+ public void testGetFeatureCount()
+ {
+ SequenceFeaturesI store = new SequenceFeatures();
+ assertEquals(store.getFeatureCount(true), 0);
+ assertEquals(store.getFeatureCount(false), 0);
+
+ /*
+ * add positional
+ */
+ SequenceFeature sf1 = new SequenceFeature("Metal", "desc", 10, 20,
+ Float.NaN, null);
+ store.add(sf1);
+ assertEquals(store.getFeatureCount(true), 1);
+ assertEquals(store.getFeatureCount(false), 0);
+
+ /*
+ * null feature type is rejected
+ */
+ SequenceFeature sf2 = new SequenceFeature(null, "desc", 10, 20,
+ Float.NaN, null);
+ assertFalse(store.add(sf2));
+ assertEquals(store.getFeatureCount(true), 1);
+ assertEquals(store.getFeatureCount(false), 0);
+
+ /*
+ * add non-positional feature
+ */
+ SequenceFeature sf3 = new SequenceFeature("Pfam", "desc", 0, 0,
+ Float.NaN, null);
+ store.add(sf3);
+ assertEquals(store.getFeatureCount(true), 1);
+ assertEquals(store.getFeatureCount(false), 1);
+
+ /*
+ * add contact feature (counts as 1)
+ */
+ SequenceFeature sf4 = new SequenceFeature("Disulphide Bond", "desc",
+ 10, 20, Float.NaN, null);
+ store.add(sf4);
+ assertEquals(store.getFeatureCount(true), 2);
+ assertEquals(store.getFeatureCount(false), 1);
+
+ /*
+ * add another Pfam but this time as a positional feature
+ */
+ SequenceFeature sf5 = new SequenceFeature("Pfam", "desc", 10, 20,
+ Float.NaN, null);
+ store.add(sf5);
+ assertEquals(store.getFeatureCount(true), 3); // sf1, sf4, sf5
+ assertEquals(store.getFeatureCount(false), 1); // sf3
+ assertEquals(store.getFeatureCount(true, "Pfam"), 1); // positional
+ assertEquals(store.getFeatureCount(false, "Pfam"), 1); // non-positional
+ // search for type==null
+ assertEquals(store.getFeatureCount(true, (String) null), 0);
+ // search with no type specified
+ assertEquals(store.getFeatureCount(true, (String[]) null), 3);
+ assertEquals(store.getFeatureCount(true, "Metal", "Cath"), 1);
+ assertEquals(store.getFeatureCount(true, "Disulphide Bond"), 1);
+ assertEquals(store.getFeatureCount(true, "Metal", "Pfam", null), 2);
+
+ /*
+ * delete first Pfam (non-positional)
+ */
+ assertTrue(store.delete(sf3));
+ assertEquals(store.getFeatureCount(true), 3);
+ assertEquals(store.getFeatureCount(false), 0);
+
+ /*
+ * delete second Pfam (positional)
+ */
+ assertTrue(store.delete(sf5));
+ assertEquals(store.getFeatureCount(true), 2);
+ assertEquals(store.getFeatureCount(false), 0);
+ }
+
+ @Test(groups = "Functional")
+ public void testGetAllFeatures()
+ {
+ SequenceFeaturesI store = new SequenceFeatures();
+ List<SequenceFeature> features = store.getAllFeatures();
+ assertTrue(features.isEmpty());
+
+ SequenceFeature sf1 = new SequenceFeature("Metal", "desc", 10, 20,
+ Float.NaN, null);
+ store.add(sf1);
+ features = store.getAllFeatures();
+ assertEquals(features.size(), 1);
+ assertTrue(features.contains(sf1));
+
+ SequenceFeature sf2 = new SequenceFeature("Metallic", "desc", 10, 20,
+ Float.NaN, null);
+ store.add(sf2);
+ features = store.getAllFeatures();
+ assertEquals(features.size(), 2);
+ assertTrue(features.contains(sf2));
+
+ /*
+ * add non-positional feature
+ */
+ SequenceFeature sf3 = new SequenceFeature("Pfam", "desc", 0, 0,
+ Float.NaN, null);
+ store.add(sf3);
+ features = store.getAllFeatures();
+ assertEquals(features.size(), 3);
+ assertTrue(features.contains(sf3));
+
+ /*
+ * add contact feature
+ */
+ SequenceFeature sf4 = new SequenceFeature("Disulphide Bond", "desc",
+ 10, 20, Float.NaN, null);
+ store.add(sf4);
+ features = store.getAllFeatures();
+ assertEquals(features.size(), 4);
+ assertTrue(features.contains(sf4));
+
+ /*
+ * add another Pfam
+ */
+ SequenceFeature sf5 = new SequenceFeature("Pfam", "desc", 10, 20,
+ Float.NaN, null);
+ store.add(sf5);
+ features = store.getAllFeatures();
+ assertEquals(features.size(), 5);
+ assertTrue(features.contains(sf5));
+
+ /*
+ * select by type does not apply to non-positional features
+ */
+ features = store.getAllFeatures("Cath");
+ assertEquals(features.size(), 1);
+ assertTrue(features.contains(sf3));
+
+ features = store.getAllFeatures("Pfam", "Cath", "Metal");
+ assertEquals(features.size(), 3);
+ assertTrue(features.contains(sf1));
+ assertTrue(features.contains(sf3));
+ assertTrue(features.contains(sf5));
+
+ /*
+ * delete first Pfam
+ */
+ assertTrue(store.delete(sf3));
+ features = store.getAllFeatures();
+ assertEquals(features.size(), 4);
+ assertFalse(features.contains(sf3));
+
+ /*
+ * delete second Pfam
+ */
+ assertTrue(store.delete(sf5));
+ features = store.getAllFeatures();
+ assertEquals(features.size(), 3);
+ assertFalse(features.contains(sf3));
+ }
+
+ @Test(groups = "Functional")
+ public void testGetTotalFeatureLength()
+ {
+ SequenceFeaturesI store = new SequenceFeatures();
+ assertEquals(store.getTotalFeatureLength(), 0);
+
+ SequenceFeature sf1 = new SequenceFeature("Metal", "desc", 10, 20,
+ Float.NaN, null);
+ assertTrue(store.add(sf1));
+ assertEquals(store.getTotalFeatureLength(), 11);
+ assertEquals(store.getTotalFeatureLength("Metal"), 11);
+ assertEquals(store.getTotalFeatureLength("Plastic"), 0);
+
+ // re-add does nothing!
+ assertFalse(store.add(sf1));
+ assertEquals(store.getTotalFeatureLength(), 11);
+
+ /*
+ * add non-positional feature
+ */
+ SequenceFeature sf3 = new SequenceFeature("Pfam", "desc", 0, 0,
+ Float.NaN, null);
+ store.add(sf3);
+ assertEquals(store.getTotalFeatureLength(), 11);
+
+ /*
+ * add contact feature - counts 1 to feature length
+ */
+ SequenceFeature sf4 = new SequenceFeature("Disulphide Bond", "desc",
+ 10, 20, Float.NaN, null);
+ store.add(sf4);
+ assertEquals(store.getTotalFeatureLength(), 12);
+
+ /*
+ * add another Pfam
+ */
+ SequenceFeature sf5 = new SequenceFeature("Pfam", "desc", 10, 20,
+ Float.NaN, null);
+ store.add(sf5);
+ assertEquals(store.getTotalFeatureLength(), 23);
+
+ /*
+ * delete features
+ */
+ assertTrue(store.delete(sf3)); // non-positional
+ assertEquals(store.getTotalFeatureLength(), 23); // no change
+
+ assertTrue(store.delete(sf5));
+ assertEquals(store.getTotalFeatureLength(), 12);
+
+ assertTrue(store.delete(sf4)); // contact
+ assertEquals(store.getTotalFeatureLength(), 11);
+
+ assertTrue(store.delete(sf1));
+ assertEquals(store.getTotalFeatureLength(), 0);
+ }
+
+ @Test(groups = "Functional")
+ public void testGetMinimumScore_getMaximumScore()
+ {
+ SequenceFeatures sf = new SequenceFeatures();
+ SequenceFeature sf1 = new SequenceFeature("Metal", "desc", 0, 0,
+ Float.NaN, "group"); // non-positional, no score
+ sf.add(sf1);
+ SequenceFeature sf2 = new SequenceFeature("Cath", "desc", 10, 20,
+ Float.NaN, "group"); // positional, no score
+ sf.add(sf2);
+ SequenceFeature sf3 = new SequenceFeature("Metal", "desc", 10, 20, 1f,
+ "group");
+ sf.add(sf3);
+ SequenceFeature sf4 = new SequenceFeature("Metal", "desc", 12, 16, 4f,
+ "group");
+ sf.add(sf4);
+ SequenceFeature sf5 = new SequenceFeature("Cath", "desc", 0, 0, 11f,
+ "group");
+ sf.add(sf5);
+ SequenceFeature sf6 = new SequenceFeature("Cath", "desc", 0, 0, -7f,
+ "group");
+ sf.add(sf6);
+
+ assertEquals(sf.getMinimumScore("nosuchtype", true), Float.NaN);
+ assertEquals(sf.getMinimumScore("nosuchtype", false), Float.NaN);
+ assertEquals(sf.getMaximumScore("nosuchtype", true), Float.NaN);
+ assertEquals(sf.getMaximumScore("nosuchtype", false), Float.NaN);
+
+ // positional features min-max:
+ assertEquals(sf.getMinimumScore("Metal", true), 1f);
+ assertEquals(sf.getMaximumScore("Metal", true), 4f);
+ assertEquals(sf.getMinimumScore("Cath", true), Float.NaN);
+ assertEquals(sf.getMaximumScore("Cath", true), Float.NaN);
+
+ // non-positional features min-max:
+ assertEquals(sf.getMinimumScore("Cath", false), -7f);
+ assertEquals(sf.getMaximumScore("Cath", false), 11f);
+ assertEquals(sf.getMinimumScore("Metal", false), Float.NaN);
+ assertEquals(sf.getMaximumScore("Metal", false), Float.NaN);
+
+ // delete features; min-max should get recomputed
+ sf.delete(sf6);
+ assertEquals(sf.getMinimumScore("Cath", false), 11f);
+ assertEquals(sf.getMaximumScore("Cath", false), 11f);
+ sf.delete(sf4);
+ assertEquals(sf.getMinimumScore("Metal", true), 1f);
+ assertEquals(sf.getMaximumScore("Metal", true), 1f);
+ sf.delete(sf5);
+ assertEquals(sf.getMinimumScore("Cath", false), Float.NaN);
+ assertEquals(sf.getMaximumScore("Cath", false), Float.NaN);
+ sf.delete(sf3);
+ assertEquals(sf.getMinimumScore("Metal", true), Float.NaN);
+ assertEquals(sf.getMaximumScore("Metal", true), Float.NaN);
+ sf.delete(sf1);
+ sf.delete(sf2);
+ assertFalse(sf.hasFeatures());
+ assertEquals(sf.getMinimumScore("Cath", false), Float.NaN);
+ assertEquals(sf.getMaximumScore("Cath", false), Float.NaN);
+ assertEquals(sf.getMinimumScore("Metal", true), Float.NaN);
+ assertEquals(sf.getMaximumScore("Metal", true), Float.NaN);
+ }
+
+ @Test(groups = "Functional")
+ public void testVarargsToTypes()
+ {
+ SequenceFeatures sf = new SequenceFeatures();
+ sf.add(new SequenceFeature("Metal", "desc", 0, 0, Float.NaN, "group"));
+ sf.add(new SequenceFeature("Cath", "desc", 10, 20, Float.NaN, "group"));
+
+ /*
+ * no type specified - get all types stored
+ * they are returned in keyset (alphabetical) order
+ */
+ Map<String, FeatureStore> featureStores = (Map<String, FeatureStore>) PA
+ .getValue(sf, "featureStore");
+
+ Iterable<FeatureStore> types = sf.varargToTypes();
+ Iterator<FeatureStore> iterator = types.iterator();
+ assertTrue(iterator.hasNext());
+ assertSame(iterator.next(), featureStores.get("Cath"));
+ assertTrue(iterator.hasNext());
+ assertSame(iterator.next(), featureStores.get("Metal"));
+ assertFalse(iterator.hasNext());
+
+ /*
+ * empty array is the same as no vararg parameter supplied
+ * so treated as all stored types
+ */
+ types = sf.varargToTypes(new String[] {});
+ iterator = types.iterator();
+ assertTrue(iterator.hasNext());
+ assertSame(iterator.next(), featureStores.get("Cath"));
+ assertTrue(iterator.hasNext());
+ assertSame(iterator.next(), featureStores.get("Metal"));
+ assertFalse(iterator.hasNext());
+
+ /*
+ * null type specified; this is passed as vararg
+ * String[1] {null}
+ */
+ types = sf.varargToTypes((String) null);
+ assertFalse(types.iterator().hasNext());
+
+ /*
+ * null types array specified; this is passed as vararg null
+ */
+ types = sf.varargToTypes((String[]) null);
+ iterator = types.iterator();
+ assertTrue(iterator.hasNext());
+ assertSame(iterator.next(), featureStores.get("Cath"));
+ assertTrue(iterator.hasNext());
+ assertSame(iterator.next(), featureStores.get("Metal"));
+ assertFalse(iterator.hasNext());
+
+ /*
+ * one type specified
+ */
+ types = sf.varargToTypes("Metal");
+ iterator = types.iterator();
+ assertTrue(iterator.hasNext());
+ assertSame(iterator.next(), featureStores.get("Metal"));
+ assertFalse(iterator.hasNext());
+
+ /*
+ * two types specified - get sorted alphabetically
+ */
+ types = sf.varargToTypes("Metal", "Cath");
+ iterator = types.iterator();
+ assertTrue(iterator.hasNext());
+ assertSame(iterator.next(), featureStores.get("Cath"));
+ assertTrue(iterator.hasNext());
+ assertSame(iterator.next(), featureStores.get("Metal"));
+ assertFalse(iterator.hasNext());
+
+ /*
+ * null type included - should be ignored
+ */
+ types = sf.varargToTypes("Metal", null, "Helix");
+ iterator = types.iterator();
+ assertTrue(iterator.hasNext());
+ assertSame(iterator.next(), featureStores.get("Metal"));
+ assertFalse(iterator.hasNext());
+ }
+
+ @Test(groups = "Functional")
+ public void testGetFeatureTypes_byOntology()
+ {
+ SequenceFeaturesI store = new SequenceFeatures();
+
+ SequenceFeature sf1 = new SequenceFeature("transcript", "desc", 10, 20,
+ Float.NaN, null);
+ store.add(sf1);
+ // mRNA isA mature_transcript isA transcript
+ SequenceFeature sf2 = new SequenceFeature("mRNA", "desc", 10, 20,
+ Float.NaN, null);
+ store.add(sf2);
+ // just to prove non-positional feature types are included
+ SequenceFeature sf3 = new SequenceFeature("mRNA", "desc", 0, 0,
+ Float.NaN, null);
+ store.add(sf3);
+ SequenceFeature sf4 = new SequenceFeature("CDS", "desc", 0, 0,
+ Float.NaN, null);
+ store.add(sf4);
+
+ Set<String> types = store.getFeatureTypes("transcript");
+ assertEquals(types.size(), 2);
+ assertTrue(types.contains("transcript"));
+ assertTrue(types.contains("mRNA"));
+
+ // matches include arguments whether SO terms or not
+ types = store.getFeatureTypes("transcript", "CDS");
+ assertEquals(types.size(), 3);
+ assertTrue(types.contains("transcript"));
+ assertTrue(types.contains("mRNA"));
+ assertTrue(types.contains("CDS"));
+
+ types = store.getFeatureTypes("exon");
+ assertTrue(types.isEmpty());
+ }
+
+ @Test(groups = "Functional")
+ public void testGetFeaturesByOntology()
+ {
+ SequenceFeaturesI store = new SequenceFeatures();
+ List<SequenceFeature> features = store.getFeaturesByOntology();
+ assertTrue(features.isEmpty());
+ assertTrue(store.getFeaturesByOntology(new String[] {}).isEmpty());
+ assertTrue(store.getFeaturesByOntology((String[]) null).isEmpty());
+
+ SequenceFeature sf1 = new SequenceFeature("transcript", "desc", 10, 20,
+ Float.NaN, null);
+ store.add(sf1);
+
+ // mRNA isA transcript; added here 'as if' non-positional
+ // just to show that non-positional features are included in results
+ SequenceFeature sf2 = new SequenceFeature("mRNA", "desc", 0, 0,
+ Float.NaN, null);
+ store.add(sf2);
+
+ SequenceFeature sf3 = new SequenceFeature("Pfam", "desc", 30, 40,
+ Float.NaN, null);
+ store.add(sf3);
+
+ features = store.getFeaturesByOntology("transcript");
+ assertEquals(features.size(), 2);
+ assertTrue(features.contains(sf1));
+ assertTrue(features.contains(sf2));
+
+ features = store.getFeaturesByOntology("mRNA");
+ assertEquals(features.size(), 1);
+ assertTrue(features.contains(sf2));
+
+ features = store.getFeaturesByOntology("mRNA", "Pfam");
+ assertEquals(features.size(), 2);
+ assertTrue(features.contains(sf2));
+ assertTrue(features.contains(sf3));
+ }
+
+ @Test(groups = "Functional")
+ public void testSortFeatures()
+ {
+ List<SequenceFeature> sfs = new ArrayList<SequenceFeature>();
+ SequenceFeature sf1 = new SequenceFeature("Pfam", "desc", 30, 80,
+ Float.NaN, null);
+ sfs.add(sf1);
+ SequenceFeature sf2 = new SequenceFeature("Rfam", "desc", 40, 50,
+ Float.NaN, null);
+ sfs.add(sf2);
+ SequenceFeature sf3 = new SequenceFeature("Rfam", "desc", 50, 60,
+ Float.NaN, null);
+ sfs.add(sf3);
+
+ // sort by end position descending
+ SequenceFeatures.sortFeatures(sfs, false);
+ assertSame(sfs.get(0), sf1);
+ assertSame(sfs.get(1), sf3);
+ assertSame(sfs.get(2), sf2);
+
+ // sort by start position ascending
+ SequenceFeatures.sortFeatures(sfs, true);
+ assertSame(sfs.get(0), sf1);
+ assertSame(sfs.get(1), sf2);
+ assertSame(sfs.get(2), sf3);
+ }
+
+ @Test(groups = "Functional")
+ public void testGetFeaturesForGroup()
+ {
+ SequenceFeaturesI store = new SequenceFeatures();
+
+ List<SequenceFeature> features = store.getFeaturesForGroup(true, null);
+ assertTrue(features.isEmpty());
+ assertTrue(store.getFeaturesForGroup(false, null).isEmpty());
+ assertTrue(store.getFeaturesForGroup(true, "Uniprot").isEmpty());
+ assertTrue(store.getFeaturesForGroup(false, "Uniprot").isEmpty());
+
+ SequenceFeature sf1 = new SequenceFeature("Pfam", "desc", 4, 10, 0f,
+ null);
+ SequenceFeature sf2 = new SequenceFeature("Pfam", "desc", 0, 0, 0f,
+ null);
+ SequenceFeature sf3 = new SequenceFeature("Pfam", "desc", 4, 10, 0f,
+ "Uniprot");
+ SequenceFeature sf4 = new SequenceFeature("Metal", "desc", 0, 0, 0f,
+ "Rfam");
+ SequenceFeature sf5 = new SequenceFeature("Cath", "desc", 5, 15, 0f,
+ null);
+ store.add(sf1);
+ store.add(sf2);
+ store.add(sf3);
+ store.add(sf4);
+ store.add(sf5);
+
+ // positional features for null group, any type
+ features = store.getFeaturesForGroup(true, null);
+ assertEquals(features.size(), 2);
+ assertTrue(features.contains(sf1));
+ assertTrue(features.contains(sf5));
+
+ // positional features for null group, specified type
+ features = store.getFeaturesForGroup(true, null, new String[] { "Pfam",
+ "Xfam" });
+ assertEquals(features.size(), 1);
+ assertTrue(features.contains(sf1));
+ features = store.getFeaturesForGroup(true, null, new String[] { "Pfam",
+ "Xfam", "Cath" });
+ assertEquals(features.size(), 2);
+ assertTrue(features.contains(sf1));
+ assertTrue(features.contains(sf5));
+
+ // positional features for non-null group, any type
+ features = store.getFeaturesForGroup(true, "Uniprot");
+ assertEquals(features.size(), 1);
+ assertTrue(features.contains(sf3));
+ assertTrue(store.getFeaturesForGroup(true, "Rfam").isEmpty());
+
+ // positional features for non-null group, specified type
+ features = store.getFeaturesForGroup(true, "Uniprot", "Pfam", "Xfam",
+ "Rfam");
+ assertEquals(features.size(), 1);
+ assertTrue(features.contains(sf3));
+ assertTrue(store.getFeaturesForGroup(true, "Uniprot", "Cath").isEmpty());
+
+ // non-positional features for null group, any type
+ features = store.getFeaturesForGroup(false, null);
+ assertEquals(features.size(), 1);
+ assertTrue(features.contains(sf2));
+
+ // non-positional features for null group, specified type
+ features = store.getFeaturesForGroup(false, null, "Pfam", "Xfam");
+ assertEquals(features.size(), 1);
+ assertTrue(features.contains(sf2));
+ assertTrue(store.getFeaturesForGroup(false, null, "Cath").isEmpty());
+
+ // non-positional features for non-null group, any type
+ features = store.getFeaturesForGroup(false, "Rfam");
+ assertEquals(features.size(), 1);
+ assertTrue(features.contains(sf4));
+ assertTrue(store.getFeaturesForGroup(false, "Uniprot").isEmpty());
+
+ // non-positional features for non-null group, specified type
+ features = store.getFeaturesForGroup(false, "Rfam", "Pfam", "Metal");
+ assertEquals(features.size(), 1);
+ assertTrue(features.contains(sf4));
+ assertTrue(store.getFeaturesForGroup(false, "Rfam", "Cath", "Pfam")
+ .isEmpty());
+ }
+
+ @Test(groups = "Functional")
+ public void testShiftFeatures()
+ {
+ SequenceFeatures store = new SequenceFeatures();
+ assertFalse(store.shiftFeatures(1));
+
+ SequenceFeature sf1 = new SequenceFeature("Cath", "", 2, 5, 0f, null);
+ store.add(sf1);
+ // nested feature:
+ SequenceFeature sf2 = new SequenceFeature("Metal", "", 8, 14, 0f, null);
+ store.add(sf2);
+ // contact feature:
+ SequenceFeature sf3 = new SequenceFeature("Disulfide bond", "", 23, 32,
+ 0f, null);
+ store.add(sf3);
+ // non-positional feature:
+ SequenceFeature sf4 = new SequenceFeature("Pfam", "", 0, 0, 0f, null);
+ store.add(sf4);
+
+ /*
+ * shift features right by 5
+ */
+ assertTrue(store.shiftFeatures(5));
+
+ // non-positional features untouched:
+ List<SequenceFeature> nonPos = store.getNonPositionalFeatures();
+ assertEquals(nonPos.size(), 1);
+ assertTrue(nonPos.contains(sf4));
+
+ // positional features are replaced
+ List<SequenceFeature> pos = store.getPositionalFeatures();
+ assertEquals(pos.size(), 3);
+ assertFalse(pos.contains(sf1));
+ assertFalse(pos.contains(sf2));
+ assertFalse(pos.contains(sf3));
+ SequenceFeatures.sortFeatures(pos, true); // ascending start pos
+ assertEquals(pos.get(0).getBegin(), 7);
+ assertEquals(pos.get(0).getEnd(), 10);
+ assertEquals(pos.get(0).getType(), "Cath");
+ assertEquals(pos.get(1).getBegin(), 13);
+ assertEquals(pos.get(1).getEnd(), 19);
+ assertEquals(pos.get(1).getType(), "Metal");
+ assertEquals(pos.get(2).getBegin(), 28);
+ assertEquals(pos.get(2).getEnd(), 37);
+ assertEquals(pos.get(2).getType(), "Disulfide bond");
+
+ /*
+ * now shift left by 15
+ * feature at [7-10] should be removed
+ * feature at [13-19] should become [1-4]
+ */
+ assertTrue(store.shiftFeatures(-15));
+ pos = store.getPositionalFeatures();
+ assertEquals(pos.size(), 2);
+ SequenceFeatures.sortFeatures(pos, true);
+ assertEquals(pos.get(0).getBegin(), 1);
+ assertEquals(pos.get(0).getEnd(), 4);
+ assertEquals(pos.get(0).getType(), "Metal");
+ assertEquals(pos.get(1).getBegin(), 13);
+ assertEquals(pos.get(1).getEnd(), 22);
+ assertEquals(pos.get(1).getType(), "Disulfide bond");
+ }
+
+ @Test(groups = "Functional")
+ public void testIsOntologyTerm()
+ {
+ SequenceFeatures store = new SequenceFeatures();
+ assertTrue(store.isOntologyTerm("gobbledygook"));
+ assertTrue(store.isOntologyTerm("transcript", "transcript"));
+ assertTrue(store.isOntologyTerm("mRNA", "transcript"));
+ assertFalse(store.isOntologyTerm("transcript", "mRNA"));
+ assertTrue(store.isOntologyTerm("junk", "transcript", "junk"));
+ assertTrue(store.isOntologyTerm("junk", new String[] {}));
+ assertTrue(store.isOntologyTerm("junk", (String[]) null));
+ }
+}
20500, 0f, null);
assertFalse(testee.retainFeature(sf, accId));
- sf.setType("aberrant_processed_transcript");
+ sf = new SequenceFeature("aberrant_processed_transcript", "", 20000,
+ 20500, 0f, null);
assertFalse(testee.retainFeature(sf, accId));
- sf.setType("NMD_transcript_variant");
+ sf = new SequenceFeature("NMD_transcript_variant", "", 20000, 20500,
+ 0f, null);
assertFalse(testee.retainFeature(sf, accId));
// other feature with no parent is retained
- sf.setType("sequence_variant");
+ sf = new SequenceFeature("sequence_variant", "", 20000, 20500, 0f, null);
assertTrue(testee.retainFeature(sf, accId));
// other feature with desired parent is retained
assertTrue(testee.identifiesSequence(sf, accId));
// exon sub-type with right parent is valid
- sf.setType("coding_exon");
+ sf = new SequenceFeature("coding_exon", "", 1, 2, 0f, null);
+ sf.setValue("Parent", "transcript:" + accId);
assertTrue(testee.identifiesSequence(sf, accId));
// transcript not valid:
- sf.setType("transcript");
+ sf = new SequenceFeature("transcript", "", 1, 2, 0f, null);
+ sf.setValue("Parent", "transcript:" + accId);
assertFalse(testee.identifiesSequence(sf, accId));
// CDS not valid:
- sf.setType("CDS");
+ sf = new SequenceFeature("CDS", "", 1, 2, 0f, null);
+ sf.setValue("Parent", "transcript:" + accId);
assertFalse(testee.identifiesSequence(sf, accId));
}
null);
assertFalse(testee.retainFeature(sf, accId));
- sf.setType("CDS_predicted");
+ sf = new SequenceFeature("CDS_predicted", "", 20000, 20500, 0f, null);
assertFalse(testee.retainFeature(sf, accId));
// other feature with no parent is retained
- sf.setType("sequence_variant");
+ sf = new SequenceFeature("CDS_psequence_variantredicted", "", 20000,
+ 20500, 0f, null);
assertTrue(testee.retainFeature(sf, accId));
// other feature with desired parent is retained
assertTrue(testee.identifiesSequence(sf, accId));
// cds sub-type with right parent is valid
- sf.setType("CDS_predicted");
+ sf = new SequenceFeature("CDS_predicted", "", 1, 2, 0f, null);
+ sf.setValue("Parent", "transcript:" + accId);
assertTrue(testee.identifiesSequence(sf, accId));
// transcript not valid:
- sf.setType("transcript");
+ sf = new SequenceFeature("transcript", "", 1, 2, 0f, null);
+ sf.setValue("Parent", "transcript:" + accId);
assertFalse(testee.identifiesSequence(sf, accId));
// exon not valid:
- sf.setType("exon");
+ sf = new SequenceFeature("exon", "", 1, 2, 0f, null);
+ sf.setValue("Parent", "transcript:" + accId);
assertFalse(testee.identifiesSequence(sf, accId));
}
import static org.testng.AssertJUnit.assertEquals;
import static org.testng.AssertJUnit.assertFalse;
-import static org.testng.AssertJUnit.assertSame;
import static org.testng.AssertJUnit.assertTrue;
import jalview.api.FeatureSettingsModelI;
genomic.setEnd(50000);
String geneId = "ABC123";
- // gene at (start+10000) length 501
+ // gene at (start+20000) length 501
+ // should be ignored - the first 'gene' found defines the whole range
+ // (note features are found in position order, not addition order)
SequenceFeature sf = new SequenceFeature("gene", "", 20000, 20500, 0f,
null);
sf.setValue("ID", "gene:" + geneId);
genomic.addSequenceFeature(sf);
// gene at (start + 10500) length 101
- // should be ignored - the first 'gene' found defines the whole range
sf = new SequenceFeature("gene", "", 10500, 10600, 0f, null);
sf.setValue("ID", "gene:" + geneId);
sf.setStrand("+");
23);
List<int[]> fromRanges = ranges.getFromRanges();
assertEquals(1, fromRanges.size());
- assertEquals(20000, fromRanges.get(0)[0]);
- assertEquals(20500, fromRanges.get(0)[1]);
+ assertEquals(10500, fromRanges.get(0)[0]);
+ assertEquals(10600, fromRanges.get(0)[1]);
// to range should start from given start numbering
List<int[]> toRanges = ranges.getToRanges();
assertEquals(1, toRanges.size());
assertEquals(23, toRanges.get(0)[0]);
- assertEquals(523, toRanges.get(0)[1]);
+ assertEquals(123, toRanges.get(0)[1]);
}
/**
genomic.setEnd(50000);
String geneId = "ABC123";
- // gene at (start+10000) length 501
+ // gene at (start+20000) length 501
+ // should be ignored - the first 'gene' found defines the whole range
+ // (real data would only have one such feature)
SequenceFeature sf = new SequenceFeature("ncRNA_gene", "", 20000,
20500, 0f, null);
sf.setValue("ID", "gene:" + geneId);
genomic.addSequenceFeature(sf);
// gene at (start + 10500) length 101
- // should be ignored - the first 'gene' found defines the whole range
- // (real data would only have one such feature)
sf = new SequenceFeature("gene", "", 10500, 10600, 0f, null);
sf.setValue("ID", "gene:" + geneId);
sf.setStrand("+");
List<int[]> fromRanges = ranges.getFromRanges();
assertEquals(1, fromRanges.size());
// from range on reverse strand:
- assertEquals(20500, fromRanges.get(0)[0]);
- assertEquals(20000, fromRanges.get(0)[1]);
+ assertEquals(10500, fromRanges.get(0)[0]);
+ assertEquals(10600, fromRanges.get(0)[1]);
// to range should start from given start numbering
List<int[]> toRanges = ranges.getToRanges();
assertEquals(1, toRanges.size());
assertEquals(23, toRanges.get(0)[0]);
- assertEquals(523, toRanges.get(0)[1]);
+ assertEquals(123, toRanges.get(0)[1]);
}
/**
genomic.addSequenceFeature(sf1);
// transcript sub-type feature
- SequenceFeature sf2 = new SequenceFeature("snRNA", "", 20000, 20500,
+ SequenceFeature sf2 = new SequenceFeature("snRNA", "", 21000, 21500,
0f, null);
sf2.setValue("Parent", "gene:" + geneId);
sf2.setValue("transcript_id", "transcript2");
// NMD_transcript_variant treated like transcript in Ensembl
SequenceFeature sf3 = new SequenceFeature("NMD_transcript_variant", "",
- 20000, 20500, 0f, null);
+ 22000, 22500, 0f, null);
sf3.setValue("Parent", "gene:" + geneId);
sf3.setValue("transcript_id", "transcript3");
genomic.addSequenceFeature(sf3);
// transcript for a different gene - ignored
- SequenceFeature sf4 = new SequenceFeature("snRNA", "", 20000, 20500,
+ SequenceFeature sf4 = new SequenceFeature("snRNA", "", 23000, 23500,
0f, null);
sf4.setValue("Parent", "gene:XYZ");
sf4.setValue("transcript_id", "transcript4");
List<SequenceFeature> features = testee.getTranscriptFeatures(geneId,
genomic);
assertEquals(3, features.size());
- assertSame(sf1, features.get(0));
- assertSame(sf2, features.get(1));
- assertSame(sf3, features.get(2));
+ assertTrue(features.contains(sf1));
+ assertTrue(features.contains(sf2));
+ assertTrue(features.contains(sf3));
}
/**
sf.setValue("ID", "gene:" + geneId);
assertFalse(testee.retainFeature(sf, geneId));
- sf.setType("transcript");
+ sf = new SequenceFeature("transcript", "", 20000, 20500, 0f, null);
sf.setValue("Parent", "gene:" + geneId);
assertTrue(testee.retainFeature(sf, geneId));
- sf.setType("mature_transcript");
+ sf = new SequenceFeature("mature_transcript", "", 20000, 20500, 0f,
+ null);
sf.setValue("Parent", "gene:" + geneId);
assertTrue(testee.retainFeature(sf, geneId));
- sf.setType("NMD_transcript_variant");
+ sf = new SequenceFeature("NMD_transcript_variant", "", 20000, 20500,
+ 0f, null);
sf.setValue("Parent", "gene:" + geneId);
assertTrue(testee.retainFeature(sf, geneId));
sf.setValue("Parent", "gene:XYZ");
assertFalse(testee.retainFeature(sf, geneId));
- sf.setType("anything");
+ sf = new SequenceFeature("anything", "", 20000, 20500, 0f, null);
assertTrue(testee.retainFeature(sf, geneId));
}
assertTrue(testee.identifiesSequence(sf, accId));
// gene sub-type with right ID is valid
- sf.setType("snRNA_gene");
+ sf = new SequenceFeature("snRNA_gene", "", 1, 2, 0f, null);
+ sf.setValue("ID", "gene:" + accId);
assertTrue(testee.identifiesSequence(sf, accId));
// transcript not valid:
- sf.setType("transcript");
+ sf = new SequenceFeature("transcript", "", 1, 2, 0f, null);
+ sf.setValue("ID", "gene:" + accId);
assertFalse(testee.identifiesSequence(sf, accId));
// exon not valid:
- sf.setType("exon");
+ sf = new SequenceFeature("exon", "", 1, 2, 0f, null);
+ sf.setValue("ID", "gene:" + accId);
assertFalse(testee.identifiesSequence(sf, accId));
}
20500, 0f, null);
assertFalse(testee.retainFeature(sf, accId));
- sf.setType("mature_transcript");
+ sf = new SequenceFeature("mature_transcript", "", 20000, 20500, 0f,
+ null);
assertFalse(testee.retainFeature(sf, accId));
- sf.setType("NMD_transcript_variant");
+ sf = new SequenceFeature("NMD_transcript_variant", "", 20000, 20500,
+ 0f, null);
assertFalse(testee.retainFeature(sf, accId));
// other feature with no parent is kept
- sf.setType("anything");
+ sf = new SequenceFeature("anything", "", 20000, 20500, 0f, null);
assertTrue(testee.retainFeature(sf, accId));
// other feature with correct parent is kept
assertTrue(testee.identifiesSequence(sf, accId));
// transcript sub-type with right ID is valid
- sf.setType("ncRNA");
+ sf = new SequenceFeature("ncRNA", "", 1, 2, 0f, null);
+ sf.setValue("ID", "transcript:" + accId);
assertTrue(testee.identifiesSequence(sf, accId));
// Ensembl treats NMD_transcript_variant as if a transcript
- sf.setType("NMD_transcript_variant");
+ sf = new SequenceFeature("NMD_transcript_variant", "", 1, 2, 0f, null);
+ sf.setValue("ID", "transcript:" + accId);
assertTrue(testee.identifiesSequence(sf, accId));
// gene not valid:
- sf.setType("gene");
+ sf = new SequenceFeature("gene", "", 1, 2, 0f, null);
+ sf.setValue("ID", "transcript:" + accId);
assertFalse(testee.identifiesSequence(sf, accId));
// exon not valid:
- sf.setType("exon");
+ sf = new SequenceFeature("exon", "", 1, 2, 0f, null);
+ sf.setValue("ID", "transcript:" + accId);
assertFalse(testee.identifiesSequence(sf, accId));
}
import static org.testng.AssertJUnit.assertEquals;
import static org.testng.AssertJUnit.assertFalse;
+import static org.testng.AssertJUnit.assertSame;
import static org.testng.AssertJUnit.assertTrue;
-import static org.testng.internal.junit.ArrayAsserts.assertArrayEquals;
import jalview.datamodel.Alignment;
import jalview.datamodel.SequenceFeature;
import jalview.datamodel.SequenceI;
+import jalview.datamodel.features.SequenceFeatures;
import jalview.gui.JvOptionPane;
import jalview.io.DataSourceType;
import jalview.io.FastaFile;
import java.lang.reflect.Method;
import java.util.Arrays;
+import java.util.List;
import org.testng.Assert;
import org.testng.annotations.AfterClass;
SequenceFeature sf2 = new SequenceFeature("", "", 8, 12, 0f, null);
SequenceFeature sf3 = new SequenceFeature("", "", 8, 13, 0f, null);
SequenceFeature sf4 = new SequenceFeature("", "", 11, 11, 0f, null);
- SequenceFeature[] sfs = new SequenceFeature[] { sf1, sf2, sf3, sf4 };
+ List<SequenceFeature> sfs = Arrays.asList(new SequenceFeature[] { sf1,
+ sf2, sf3, sf4 });
// sort by start position ascending (forward strand)
// sf2 and sf3 tie and should not be reordered by sorting
- EnsemblSeqProxy.sortFeatures(sfs, true);
- assertArrayEquals(new SequenceFeature[] { sf2, sf3, sf1, sf4 }, sfs);
+ SequenceFeatures.sortFeatures(sfs, true);
+ assertSame(sfs.get(0), sf2);
+ assertSame(sfs.get(1), sf3);
+ assertSame(sfs.get(2), sf1);
+ assertSame(sfs.get(3), sf4);
// sort by end position descending (reverse strand)
- EnsemblSeqProxy.sortFeatures(sfs, false);
- assertArrayEquals(new SequenceFeature[] { sf1, sf3, sf2, sf4 }, sfs);
+ SequenceFeatures.sortFeatures(sfs, false);
+ assertSame(sfs.get(0), sf1);
+ assertSame(sfs.get(1), sf3);
+ assertSame(sfs.get(2), sf2);
+ assertSame(sfs.get(3), sf4);
}
}
/*
* the ID is also the group for features derived from structure data
*/
- assertNotNull(structureData.getSeqs().get(0).getSequenceFeatures()[0].featureGroup);
- assertEquals(
- structureData.getSeqs().get(0).getSequenceFeatures()[0].featureGroup,
- "localstruct");
-
+ String featureGroup = structureData.getSeqs().get(0)
+ .getSequenceFeatures().get(0).featureGroup;
+ assertNotNull(featureGroup);
+ assertEquals(featureGroup, "localstruct");
}
}
{
{
SequenceI struseq = null;
- String sq_ = new String(sq.getSequence()).toLowerCase();
+ String sq_ = sq.getSequenceAsString().toLowerCase();
for (SequenceI _struseq : pdbf.getSeqsAsArray())
{
- final String lowerCase = new String(_struseq.getSequence())
+ final String lowerCase = _struseq.getSequenceAsString()
.toLowerCase();
if (lowerCase.equals(sq_))
{
import jalview.gui.Preferences;
import jalview.gui.StructureViewer;
import jalview.gui.StructureViewer.ViewerType;
+import jalview.io.DataSourceType;
import jalview.io.FileLoader;
import jalview.structure.StructureMapping;
import jalview.structure.StructureSelectionManager;
import java.io.IOException;
import java.util.List;
import java.util.Vector;
-import jalview.io.DataSourceType;
import org.testng.annotations.AfterClass;
import org.testng.annotations.AfterMethod;
binding.copyStructureAttributesToFeatures("phi", af.getViewport()
.getAlignPanel());
fr.setVisible("phi");
- List<SequenceFeature> fs = fr.findFeaturesAtRes(fer2Arath, 54);
+ List<SequenceFeature> fs = fer2Arath.getFeatures().findFeatures(54, 54);
assertEquals(fs.size(), 3);
- assertEquals(fs.get(0).getType(), "RESNUM");
- assertEquals(fs.get(1).getType(), "phi");
- assertEquals(fs.get(2).getType(), "phi");
- assertEquals(fs.get(1).getDescription(), "A"); // chain
- assertEquals(fs.get(2).getDescription(), "B");
- assertEquals(fs.get(1).getScore(), -131.0713f, 0.001f);
- assertEquals(fs.get(2).getScore(), -127.39512, 0.001f);
+ /*
+ * order of returned features is not guaranteed
+ */
+ assertTrue("RESNUM".equals(fs.get(0).getType())
+ || "RESNUM".equals(fs.get(1).getType())
+ || "RESNUM".equals(fs.get(2).getType()));
+ assertTrue(fs.contains(new SequenceFeature("phi", "A", 54, 54,
+ -131.0713f, "Chimera")));
+ assertTrue(fs.contains(new SequenceFeature("phi", "B", 54, 54,
+ -127.39512f, "Chimera")));
/*
* tear down - also in AfterMethod
int res, String featureType)
{
String where = "at position " + res;
- List<SequenceFeature> fs = fr.findFeaturesAtRes(seq, res);
+ List<SequenceFeature> fs = seq.getFeatures().findFeatures(res, res);
+
assertEquals(fs.size(), 2, where);
assertEquals(fs.get(0).getType(), "RESNUM", where);
SequenceFeature sf = fs.get(1);
{
Jalview.main(new String[] { "-nonews", "-props",
"test/jalview/testProps.jvprops" });
+
+ /*
+ * remove any sequence mappings left lying around by other tests
+ */
+ StructureSelectionManager ssm = StructureSelectionManager
+ .getStructureSelectionManager(Desktop.instance);
+ ssm.resetAll();
}
@BeforeMethod(alwaysRun = true)
*/
StructureSelectionManager ssm = StructureSelectionManager
.getStructureSelectionManager(Desktop.instance);
- assertEquals(2, ssm.getSequenceMappings().size());
- assertTrue(ssm.getSequenceMappings().contains(acf1));
- assertTrue(ssm.getSequenceMappings().contains(acf2));
+ List<AlignedCodonFrame> sequenceMappings = ssm.getSequenceMappings();
+ assertEquals(2, sequenceMappings.size());
+ assertTrue(sequenceMappings.contains(acf1));
+ assertTrue(sequenceMappings.contains(acf2));
/*
* Close the second view. Verify that mappings are not removed as the first
* view still holds a reference to them.
*/
af1.closeMenuItem_actionPerformed(false);
- assertEquals(2, ssm.getSequenceMappings().size());
- assertTrue(ssm.getSequenceMappings().contains(acf1));
- assertTrue(ssm.getSequenceMappings().contains(acf2));
+ assertEquals(2, sequenceMappings.size());
+ assertTrue(sequenceMappings.contains(acf1));
+ assertTrue(sequenceMappings.contains(acf2));
}
/**
import jalview.datamodel.PDBEntry;
import jalview.datamodel.SequenceFeature;
import jalview.datamodel.SequenceI;
+import jalview.datamodel.features.SequenceFeatures;
import jalview.gui.AlignFrame;
import jalview.gui.JvOptionPane;
import jalview.structure.StructureImportSettings;
import jalview.structure.StructureImportSettings.StructureParser;
import java.io.File;
+import java.util.List;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
/*
* 1GAQ/A
*/
- SequenceFeature[] sf = al.getSequenceAt(0).getSequenceFeatures();
- assertEquals(296, sf.length);
- assertEquals("RESNUM", sf[0].getType());
- assertEquals("GLU: 19 1gaqA", sf[0].getDescription());
- assertEquals("RESNUM", sf[295].getType());
- assertEquals("TYR: 314 1gaqA", sf[295].getDescription());
+ List<SequenceFeature> sf = al.getSequenceAt(0).getSequenceFeatures();
+ SequenceFeatures.sortFeatures(sf, true);
+ assertEquals(296, sf.size());
+ assertEquals("RESNUM", sf.get(0).getType());
+ assertEquals("GLU: 19 1gaqA", sf.get(0).getDescription());
+ assertEquals("RESNUM", sf.get(295).getType());
+ assertEquals("TYR: 314 1gaqA", sf.get(295).getDescription());
/*
* 1GAQ/B
*/
sf = al.getSequenceAt(1).getSequenceFeatures();
- assertEquals(98, sf.length);
- assertEquals("RESNUM", sf[0].getType());
- assertEquals("ALA: 1 1gaqB", sf[0].getDescription());
- assertEquals("RESNUM", sf[97].getType());
- assertEquals("ALA: 98 1gaqB", sf[97].getDescription());
+ SequenceFeatures.sortFeatures(sf, true);
+ assertEquals(98, sf.size());
+ assertEquals("RESNUM", sf.get(0).getType());
+ assertEquals("ALA: 1 1gaqB", sf.get(0).getDescription());
+ assertEquals("RESNUM", sf.get(97).getType());
+ assertEquals("ALA: 98 1gaqB", sf.get(97).getDescription());
/*
* 1GAQ/C
*/
sf = al.getSequenceAt(2).getSequenceFeatures();
- assertEquals(296, sf.length);
- assertEquals("RESNUM", sf[0].getType());
- assertEquals("GLU: 19 1gaqC", sf[0].getDescription());
- assertEquals("RESNUM", sf[295].getType());
- assertEquals("TYR: 314 1gaqC", sf[295].getDescription());
+ SequenceFeatures.sortFeatures(sf, true);
+ assertEquals(296, sf.size());
+ assertEquals("RESNUM", sf.get(0).getType());
+ assertEquals("GLU: 19 1gaqC", sf.get(0).getDescription());
+ assertEquals("RESNUM", sf.get(295).getType());
+ assertEquals("TYR: 314 1gaqC", sf.get(295).getDescription());
}
@Test(groups = { "Functional" })
import static org.testng.AssertJUnit.assertEquals;
import static org.testng.AssertJUnit.assertFalse;
import static org.testng.AssertJUnit.assertNotNull;
-import static org.testng.AssertJUnit.assertNull;
import static org.testng.AssertJUnit.assertTrue;
import jalview.api.FeatureColourI;
import jalview.datamodel.SequenceDummy;
import jalview.datamodel.SequenceFeature;
import jalview.datamodel.SequenceI;
+import jalview.datamodel.features.SequenceFeatures;
import jalview.gui.AlignFrame;
+import jalview.gui.Desktop;
import jalview.gui.JvOptionPane;
+import jalview.structure.StructureSelectionManager;
import java.awt.Color;
import java.io.File;
import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
import java.util.Map;
+import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
public class FeaturesFileTest
{
+ private static String simpleGffFile = "examples/testdata/simpleGff3.gff";
+
+ @AfterClass(alwaysRun = true)
+ public void tearDownAfterClass()
+ {
+ /*
+ * remove any sequence mappings created so they don't pollute other tests
+ */
+ StructureSelectionManager ssm = StructureSelectionManager
+ .getStructureSelectionManager(Desktop.instance);
+ ssm.resetAll();
+ }
@BeforeClass(alwaysRun = true)
public void setUpJvOptionPane()
JvOptionPane.setMockResponse(JvOptionPane.CANCEL_OPTION);
}
- private static String simpleGffFile = "examples/testdata/simpleGff3.gff";
-
@Test(groups = { "Functional" })
public void testParse() throws Exception
{
/*
* verify (some) features on sequences
*/
- SequenceFeature[] sfs = al.getSequenceAt(0).getDatasetSequence()
+ List<SequenceFeature> sfs = al.getSequenceAt(0).getDatasetSequence()
.getSequenceFeatures(); // FER_CAPAA
- assertEquals(8, sfs.length);
- SequenceFeature sf = sfs[0];
+ SequenceFeatures.sortFeatures(sfs, true);
+ assertEquals(8, sfs.size());
+
+ /*
+ * verify (in ascending start position order)
+ */
+ SequenceFeature sf = sfs.get(0);
assertEquals("Pfam family%LINK%", sf.description);
assertEquals(0, sf.begin);
assertEquals(0, sf.end);
assertEquals("Pfam family|http://pfam.xfam.org/family/PF00111",
sf.links.get(0));
- sf = sfs[1];
+ sf = sfs.get(1);
+ assertEquals("Ferredoxin_fold Status: True Positive ", sf.description);
+ assertEquals(3, sf.begin);
+ assertEquals(93, sf.end);
+ assertEquals("uniprot", sf.featureGroup);
+ assertEquals("Cath", sf.type);
+
+ sf = sfs.get(2);
+ assertEquals("Fer2 Status: True Positive Pfam 8_8%LINK%",
+ sf.description);
+ assertEquals("Pfam 8_8|http://pfam.xfam.org/family/PF00111",
+ sf.links.get(0));
+ assertEquals(8, sf.begin);
+ assertEquals(83, sf.end);
+ assertEquals("uniprot", sf.featureGroup);
+ assertEquals("Pfam", sf.type);
+
+ sf = sfs.get(3);
assertEquals("Iron-sulfur (2Fe-2S)", sf.description);
assertEquals(39, sf.begin);
assertEquals(39, sf.end);
assertEquals("uniprot", sf.featureGroup);
assertEquals("METAL", sf.type);
- sf = sfs[2];
+
+ sf = sfs.get(4);
assertEquals("Iron-sulfur (2Fe-2S)", sf.description);
assertEquals(44, sf.begin);
assertEquals(44, sf.end);
assertEquals("uniprot", sf.featureGroup);
assertEquals("METAL", sf.type);
- sf = sfs[3];
+
+ sf = sfs.get(5);
assertEquals("Iron-sulfur (2Fe-2S)", sf.description);
assertEquals(47, sf.begin);
assertEquals(47, sf.end);
assertEquals("uniprot", sf.featureGroup);
assertEquals("METAL", sf.type);
- sf = sfs[4];
+
+ sf = sfs.get(6);
assertEquals("Iron-sulfur (2Fe-2S)", sf.description);
assertEquals(77, sf.begin);
assertEquals(77, sf.end);
assertEquals("uniprot", sf.featureGroup);
assertEquals("METAL", sf.type);
- sf = sfs[5];
- assertEquals("Fer2 Status: True Positive Pfam 8_8%LINK%",
- sf.description);
- assertEquals("Pfam 8_8|http://pfam.xfam.org/family/PF00111",
- sf.links.get(0));
- assertEquals(8, sf.begin);
- assertEquals(83, sf.end);
- assertEquals("uniprot", sf.featureGroup);
- assertEquals("Pfam", sf.type);
- sf = sfs[6];
- assertEquals("Ferredoxin_fold Status: True Positive ", sf.description);
- assertEquals(3, sf.begin);
- assertEquals(93, sf.end);
- assertEquals("uniprot", sf.featureGroup);
- assertEquals("Cath", sf.type);
- sf = sfs[7];
+
+ sf = sfs.get(7);
assertEquals(
"High confidence server. Only hits with scores over 0.8 are reported. PHOSPHORYLATION (T) 89_8%LINK%",
sf.description);
assertEquals(colours.get("METAL").getColour(), new Color(0xcc9900));
// verify feature on FER_CAPAA
- SequenceFeature[] sfs = al.getSequenceAt(0).getDatasetSequence()
+ List<SequenceFeature> sfs = al.getSequenceAt(0).getDatasetSequence()
.getSequenceFeatures();
- assertEquals(1, sfs.length);
- SequenceFeature sf = sfs[0];
+ assertEquals(1, sfs.size());
+ SequenceFeature sf = sfs.get(0);
assertEquals("Iron-sulfur,2Fe-2S", sf.description);
assertEquals(44, sf.begin);
assertEquals(45, sf.end);
// verify feature on FER1_SOLLC
sfs = al.getSequenceAt(2).getDatasetSequence().getSequenceFeatures();
- assertEquals(1, sfs.length);
- sf = sfs[0];
+ assertEquals(1, sfs.size());
+ sf = sfs.get(0);
assertEquals("uniprot", sf.description);
assertEquals(55, sf.begin);
assertEquals(130, sf.end);
featuresFile.parse(al.getDataset(), colours, true));
// verify feature on FER_CAPAA
- SequenceFeature[] sfs = al.getSequenceAt(0).getDatasetSequence()
+ List<SequenceFeature> sfs = al.getSequenceAt(0).getDatasetSequence()
.getSequenceFeatures();
- assertEquals(1, sfs.length);
- SequenceFeature sf = sfs[0];
+ assertEquals(1, sfs.size());
+ SequenceFeature sf = sfs.get(0);
// description parsed from Note attribute
assertEquals("Iron-sulfur (2Fe-2S),another note", sf.description);
assertEquals(39, sf.begin);
// verify feature on FER1_SOLLC1
sfs = al.getSequenceAt(2).getDatasetSequence().getSequenceFeatures();
- assertEquals(1, sfs.length);
- sf = sfs[0];
+ assertEquals(1, sfs.size());
+ sf = sfs.get(0);
// ID used for description if available
assertEquals("$23", sf.description);
assertEquals(55, sf.begin);
featuresFile.parse(al.getDataset(), colours, true));
// verify FER_CAPAA feature
- SequenceFeature[] sfs = al.getSequenceAt(0).getDatasetSequence()
+ List<SequenceFeature> sfs = al.getSequenceAt(0).getDatasetSequence()
.getSequenceFeatures();
- assertEquals(1, sfs.length);
- SequenceFeature sf = sfs[0];
+ assertEquals(1, sfs.size());
+ SequenceFeature sf = sfs.get(0);
assertEquals("Iron-sulfur (2Fe-2S)", sf.description);
assertEquals(39, sf.begin);
assertEquals(39, sf.end);
// verify FER1_SOLLC feature
sfs = al.getSequenceAt(2).getDatasetSequence().getSequenceFeatures();
- assertEquals(1, sfs.length);
- sf = sfs[0];
+ assertEquals(1, sfs.size());
+ sf = sfs.get(0);
assertEquals("Iron-phosphorus (2Fe-P)", sf.description);
assertEquals(86, sf.begin);
assertEquals(87, sf.end);
assertFalse("dummy replacement buggy for seq2",
placeholderseq.equals(seq2.getSequenceAsString()));
assertNotNull("No features added to seq1", seq1.getSequenceFeatures());
- assertEquals("Wrong number of features", 3,
- seq1.getSequenceFeatures().length);
- assertNull(seq2.getSequenceFeatures());
+ assertEquals("Wrong number of features", 3, seq1.getSequenceFeatures()
+ .size());
+ assertTrue(seq2.getSequenceFeatures().isEmpty());
assertEquals(
"Wrong number of features",
0,
seq2.getSequenceFeatures() == null ? 0 : seq2
- .getSequenceFeatures().length);
+ .getSequenceFeatures().size());
assertTrue(
"Expected at least one CDNA/Protein mapping for seq1",
dataset.getCodonFrame(seq1) != null
+ "GAMMA-TURN\tred|0,255,255|20.0|95.0|below|66.0\n"
+ "Pfam\tred\n"
+ "STARTGROUP\tuniprot\n"
+ + "Cath\tFER_CAPAA\t-1\t0\t0\tDomain\n" // non-positional feature
+ "Iron\tFER_CAPAA\t-1\t39\t39\tMETAL\n"
+ "Turn\tFER_CAPAA\t-1\t36\t38\tGAMMA-TURN\n"
+ "<html>Pfam domain<a href=\"http://pfam.xfam.org/family/PF00111\">Pfam_3_4</a></html>\tFER_CAPAA\t-1\t20\t20\tPfam\n"
featuresFile.parse(al.getDataset(), colours, false);
/*
- * first with no features displayed
+ * add positional and non-positional features with null and
+ * empty feature group to check handled correctly
+ */
+ SequenceI seq = al.getSequenceAt(1); // FER_CAPAN
+ seq.addSequenceFeature(new SequenceFeature("Pfam", "desc1", 0, 0, 1.3f,
+ null));
+ seq.addSequenceFeature(new SequenceFeature("Pfam", "desc2", 4, 9,
+ Float.NaN, null));
+ seq = al.getSequenceAt(2); // FER1_SOLLC
+ seq.addSequenceFeature(new SequenceFeature("Pfam", "desc3", 0, 0,
+ Float.NaN, ""));
+ seq.addSequenceFeature(new SequenceFeature("Pfam", "desc4", 5, 8,
+ -2.6f, ""));
+
+ /*
+ * first with no features displayed, exclude non-positional features
*/
FeatureRenderer fr = af.alignPanel.getFeatureRenderer();
Map<String, FeatureColourI> visible = fr.getDisplayedFeatureCols();
+ List<String> visibleGroups = new ArrayList<String>(
+ Arrays.asList(new String[] {}));
String exported = featuresFile.printJalviewFormat(
- al.getSequencesArray(), visible);
+ al.getSequencesArray(), visible, visibleGroups, false);
String expected = "No Features Visible";
assertEquals(expected, exported);
/*
+ * include non-positional features
+ */
+ visibleGroups.add("uniprot");
+ exported = featuresFile.printJalviewFormat(al.getSequencesArray(),
+ visible, visibleGroups, true);
+ expected = "Cath\tFER_CAPAA\t-1\t0\t0\tDomain\t0.0\n"
+ + "desc1\tFER_CAPAN\t-1\t0\t0\tPfam\t1.3\n"
+ + "desc3\tFER1_SOLLC\t-1\t0\t0\tPfam\n" // NaN is not output
+ + "\nSTARTGROUP\tuniprot\nENDGROUP\tuniprot\n";
+ assertEquals(expected, exported);
+
+ /*
* set METAL (in uniprot group) and GAMMA-TURN visible, but not Pfam
*/
fr.setVisible("METAL");
fr.setVisible("GAMMA-TURN");
visible = fr.getDisplayedFeatureCols();
exported = featuresFile.printJalviewFormat(al.getSequencesArray(),
- visible);
+ visible, visibleGroups, false);
expected = "METAL\tcc9900\n"
+ "GAMMA-TURN\tff0000|00ffff|20.0|95.0|below|66.0\n"
+ "\nSTARTGROUP\tuniprot\n"
- + "Iron\tFER_CAPAA\t-1\t39\t39\tMETAL\t0.0\n"
+ "Turn\tFER_CAPAA\t-1\t36\t38\tGAMMA-TURN\t0.0\n"
+ + "Iron\tFER_CAPAA\t-1\t39\t39\tMETAL\t0.0\n"
+ "ENDGROUP\tuniprot\n";
assertEquals(expected, exported);
fr.setVisible("Pfam");
visible = fr.getDisplayedFeatureCols();
exported = featuresFile.printJalviewFormat(al.getSequencesArray(),
- visible);
+ visible, visibleGroups, false);
/*
- * note the order of feature types is uncontrolled - derives from
- * FeaturesDisplayed.featuresDisplayed which is a HashSet
+ * features are output within group, ordered by sequence and by type
*/
expected = "METAL\tcc9900\n"
+ "Pfam\tff0000\n"
+ "GAMMA-TURN\tff0000|00ffff|20.0|95.0|below|66.0\n"
+ "\nSTARTGROUP\tuniprot\n"
- + "Iron\tFER_CAPAA\t-1\t39\t39\tMETAL\t0.0\n"
+ "Turn\tFER_CAPAA\t-1\t36\t38\tGAMMA-TURN\t0.0\n"
+ + "Iron\tFER_CAPAA\t-1\t39\t39\tMETAL\t0.0\n"
+ "<html>Pfam domain<a href=\"http://pfam.xfam.org/family/PF00111\">Pfam_3_4</a></html>\tFER_CAPAA\t-1\t20\t20\tPfam\t0.0\n"
- + "ENDGROUP\tuniprot\n";
+ + "ENDGROUP\tuniprot\n"
+ // null / empty group features output after features in named
+ // groups:
+ + "desc2\tFER_CAPAN\t-1\t4\t9\tPfam\n"
+ + "desc4\tFER1_SOLLC\t-1\t5\t8\tPfam\t-2.6\n";
+ assertEquals(expected, exported);
+ }
+
+ @Test(groups = { "Functional" })
+ public void testPrintGffFormat() throws Exception
+ {
+ File f = new File("examples/uniref50.fa");
+ AlignmentI al = readAlignmentFile(f);
+ AlignFrame af = new AlignFrame(al, 500, 500);
+
+ /*
+ * no features
+ */
+ FeaturesFile featuresFile = new FeaturesFile();
+ FeatureRenderer fr = af.alignPanel.getFeatureRenderer();
+ Map<String, FeatureColourI> visible = new HashMap<String, FeatureColourI>();
+ List<String> visibleGroups = new ArrayList<String>(
+ Arrays.asList(new String[] {}));
+ String exported = featuresFile.printGffFormat(al.getSequencesArray(),
+ visible, visibleGroups, false);
+ String gffHeader = "##gff-version 2\n";
+ assertEquals(gffHeader, exported);
+ exported = featuresFile.printGffFormat(al.getSequencesArray(), visible,
+ visibleGroups, true);
+ assertEquals(gffHeader, exported);
+
+ /*
+ * add some features
+ */
+ al.getSequenceAt(0).addSequenceFeature(
+ new SequenceFeature("Domain", "Cath", 0, 0, 0f, "Uniprot"));
+ al.getSequenceAt(0).addSequenceFeature(
+ new SequenceFeature("METAL", "Cath", 39, 39, 1.2f, null));
+ al.getSequenceAt(1)
+ .addSequenceFeature(
+ new SequenceFeature("GAMMA-TURN", "Turn", 36, 38, 2.1f,
+ "s3dm"));
+ SequenceFeature sf = new SequenceFeature("Pfam", "", 20, 20, 0f,
+ "Uniprot");
+ sf.setAttributes("x=y;black=white");
+ sf.setStrand("+");
+ sf.setPhase("2");
+ al.getSequenceAt(1).addSequenceFeature(sf);
+
+ /*
+ * with no features displayed, exclude non-positional features
+ */
+ exported = featuresFile.printGffFormat(al.getSequencesArray(), visible,
+ visibleGroups, false);
+ assertEquals(gffHeader, exported);
+
+ /*
+ * include non-positional features
+ */
+ visibleGroups.add("Uniprot");
+ exported = featuresFile.printGffFormat(al.getSequencesArray(), visible,
+ visibleGroups, true);
+ String expected = gffHeader
+ + "FER_CAPAA\tUniprot\tDomain\t0\t0\t0.0\t.\t.\n";
+ assertEquals(expected, exported);
+
+ /*
+ * set METAL (in uniprot group) and GAMMA-TURN visible, but not Pfam
+ * only Uniprot group visible here...
+ */
+ fr.setVisible("METAL");
+ fr.setVisible("GAMMA-TURN");
+ visible = fr.getDisplayedFeatureCols();
+ exported = featuresFile.printGffFormat(al.getSequencesArray(), visible,
+ visibleGroups, false);
+ // METAL feature has null group: description used for column 2
+ expected = gffHeader + "FER_CAPAA\tCath\tMETAL\t39\t39\t1.2\t.\t.\n";
+ assertEquals(expected, exported);
+
+ /*
+ * set s3dm group visible
+ */
+ visibleGroups.add("s3dm");
+ exported = featuresFile.printGffFormat(al.getSequencesArray(), visible,
+ visibleGroups, false);
+ // METAL feature has null group: description used for column 2
+ expected = gffHeader + "FER_CAPAA\tCath\tMETAL\t39\t39\t1.2\t.\t.\n"
+ + "FER_CAPAN\ts3dm\tGAMMA-TURN\t36\t38\t2.1\t.\t.\n";
+ assertEquals(expected, exported);
+
+ /*
+ * now set Pfam visible
+ */
+ fr.setVisible("Pfam");
+ visible = fr.getDisplayedFeatureCols();
+ exported = featuresFile.printGffFormat(al.getSequencesArray(), visible,
+ visibleGroups, false);
+ // Pfam feature columns include strand(+), phase(2), attributes
+ expected = gffHeader
+ + "FER_CAPAA\tCath\tMETAL\t39\t39\t1.2\t.\t.\n"
+ + "FER_CAPAN\ts3dm\tGAMMA-TURN\t36\t38\t2.1\t.\t.\n"
+ + "FER_CAPAN\tUniprot\tPfam\t20\t20\t0.0\t+\t2\tx=y;black=white\n";
assertEquals(expected, exported);
}
}
import jalview.datamodel.SequenceFeature;
import jalview.datamodel.SequenceGroup;
import jalview.datamodel.SequenceI;
+import jalview.datamodel.features.SequenceFeatures;
import jalview.gui.AlignFrame;
import jalview.gui.JvOptionPane;
import jalview.json.binding.biojson.v1.ColourSchemeMapper;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import org.testng.Assert;
import org.testng.AssertJUnit;
@BeforeTest(alwaysRun = true)
public void setup() throws Exception
{
+ /*
+ * construct expected values
+ * nb this have to match the data in examples/example.json
+ */
// create and add sequences
Sequence[] seqs = new Sequence[5];
seqs[0] = new Sequence("FER_CAPAN",
// create and add sequence features
SequenceFeature seqFeature2 = new SequenceFeature("feature_x",
- "desciption", "status", 6, 15, "Jalview");
+ "theDesc", 6, 15, "Jalview");
SequenceFeature seqFeature3 = new SequenceFeature("feature_x",
- "desciption", "status", 9, 18, "Jalview");
+ "theDesc", 9, 18, "Jalview");
SequenceFeature seqFeature4 = new SequenceFeature("feature_x",
- "desciption", "status", 9, 18, "Jalview");
+ "theDesc", 9, 18, "Jalview");
+ // non-positional feature:
+ SequenceFeature seqFeature5 = new SequenceFeature("Domain",
+ "My description", 0, 0, "Pfam");
seqs[2].addSequenceFeature(seqFeature2);
seqs[3].addSequenceFeature(seqFeature3);
seqs[4].addSequenceFeature(seqFeature4);
+ seqs[2].addSequenceFeature(seqFeature5);
for (Sequence seq : seqs)
{
return true;
}
- public boolean isSeqMatched(SequenceI expectedSeq, SequenceI actualSeq)
+ boolean isSeqMatched(SequenceI expectedSeq, SequenceI actualSeq)
{
System.out.println("Testing >>> " + actualSeq.getName());
+ actualGrp.getStartRes());
System.out.println(expectedGrp.getEndRes() + " | "
+ actualGrp.getEndRes());
- System.out.println(expectedGrp.cs + " | " + actualGrp.cs);
+ System.out.println(expectedGrp.cs.getColourScheme() + " | "
+ + actualGrp.cs.getColourScheme());
+ boolean colourSchemeMatches = (expectedGrp.cs.getColourScheme() == null && actualGrp.cs
+ .getColourScheme() == null)
+ || expectedGrp.cs.getColourScheme().getClass()
+ .equals(actualGrp.cs.getColourScheme().getClass());
if (expectedGrp.getName().equals(actualGrp.getName())
&& expectedGrp.getColourText() == actualGrp.getColourText()
&& expectedGrp.getDisplayBoxes() == actualGrp.getDisplayBoxes()
&& expectedGrp.getIgnoreGapsConsensus() == actualGrp
.getIgnoreGapsConsensus()
- && (expectedGrp.cs.getClass().equals(actualGrp.cs.getClass()))
+ && colourSchemeMatches
&& expectedGrp.getSequences().size() == actualGrp
.getSequences().size()
&& expectedGrp.getStartRes() == actualGrp.getStartRes()
private boolean featuresMatched(SequenceI seq1, SequenceI seq2)
{
- boolean matched = false;
try
{
if (seq1 == null && seq2 == null)
return true;
}
- SequenceFeature[] inFeature = seq1.getSequenceFeatures();
- SequenceFeature[] outFeature = seq2.getSequenceFeatures();
+ List<SequenceFeature> inFeature = seq1.getFeatures().getAllFeatures();
+ List<SequenceFeature> outFeature = seq2.getFeatures()
+ .getAllFeatures();
- if (inFeature == null && outFeature == null)
- {
- return true;
- }
- else if ((inFeature == null && outFeature != null)
- || (inFeature != null && outFeature == null))
+ if (inFeature.size() != outFeature.size())
{
+ System.err.println("Feature count in: " + inFeature.size()
+ + ", out: " + outFeature.size());
return false;
}
- int testSize = inFeature.length;
- int matchedCount = 0;
+ SequenceFeatures.sortFeatures(inFeature, true);
+ SequenceFeatures.sortFeatures(outFeature, true);
+ int i = 0;
for (SequenceFeature in : inFeature)
{
- for (SequenceFeature out : outFeature)
+ SequenceFeature out = outFeature.get(i);
+ /*
+ System.out.println(out.getType() + " | " + in.getType());
+ System.out.println(out.getBegin() + " | " + in.getBegin());
+ System.out.println(out.getEnd() + " | " + in.getEnd());
+ */
+ if (!in.equals(out))
{
- System.out.println(out.getType() + " | " + in.getType());
- System.out.println(out.getBegin() + " | " + in.getBegin());
- System.out.println(out.getEnd() + " | " + in.getEnd());
-
- if (inFeature.length == outFeature.length
- && in.getBegin() == out.getBegin()
- && in.getEnd() == out.getEnd()
- && in.getScore() == out.getScore()
- && in.getFeatureGroup().equals(out.getFeatureGroup())
- && in.getType().equals(out.getType()))
- {
-
- ++matchedCount;
- }
+ System.err.println("Mismatch of " + in.toString() + " "
+ + out.toString());
+ return false;
}
- }
- System.out.println("matched count >>>>>> " + matchedCount);
- if (testSize == matchedCount)
- {
- matched = true;
+ /*
+ if (in.getBegin() == out.getBegin() && in.getEnd() == out.getEnd()
+ && in.getScore() == out.getScore()
+ && in.getFeatureGroup().equals(out.getFeatureGroup())
+ && in.getType().equals(out.getType())
+ && mapsMatch(in.otherDetails, out.otherDetails))
+ {
+ }
+ else
+ {
+ System.err.println("Feature[" + i + "] mismatch, in: "
+ + in.toString() + ", out: "
+ + outFeature.get(i).toString());
+ return false;
+ }
+ */
+ i++;
}
} catch (Exception e)
{
e.printStackTrace();
}
// System.out.println(">>>>>>>>>>>>>> features matched : " + matched);
- return matched;
+ return true;
+ }
+
+ boolean mapsMatch(Map<String, Object> m1, Map<String, Object> m2)
+ {
+ if (m1 == null || m2 == null)
+ {
+ if (m1 != null || m2 != null)
+ {
+ System.err
+ .println("only one SequenceFeature.otherDetails is not null");
+ return false;
+ }
+ else
+ {
+ return true;
+ }
+ }
+ if (m1.size() != m2.size())
+ {
+ System.err.println("otherDetails map different sizes");
+ return false;
+ }
+ for (String key : m1.keySet())
+ {
+ if (!m2.containsKey(key))
+ {
+ System.err.println(key + " in only one otherDetails");
+ return false;
+ }
+ if (m1.get(key) == null && m2.get(key) != null || m1.get(key) != null
+ && m2.get(key) == null || !m1.get(key).equals(m2.get(key)))
+ {
+ System.err.println(key + " values in otherDetails don't match");
+ return false;
+ }
+ }
+ return true;
}
/**
Assert.assertNotNull(newAlignment.getGroups());
for (SequenceGroup seqGrp : newAlignment.getGroups())
{
- SequenceGroup expectedGrp = expectedGrps.get(seqGrp.getName());
+ SequenceGroup expectedGrp = copySg;
AssertJUnit.assertTrue(
"Failed SequenceGroup Test for >>> " + seqGrp.getName(),
isGroupMatched(expectedGrp, seqGrp));
package jalview.io;
import static org.testng.AssertJUnit.assertEquals;
+import static org.testng.AssertJUnit.assertTrue;
+import jalview.datamodel.DBRefEntry;
+import jalview.datamodel.Sequence;
import jalview.datamodel.SequenceFeature;
+import jalview.datamodel.SequenceI;
import jalview.gui.JvOptionPane;
+import jalview.io.gff.GffConstants;
+import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
+import junit.extensions.PA;
+
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
// if no <html> tag, html-encodes > and < (only):
assertEquals("METAL 1 3; <br>&kHD>6", sb.toString());
}
+
+ @Test(groups = "Functional")
+ public void testCreateSequenceAnnotationReport()
+ {
+ SequenceAnnotationReport sar = new SequenceAnnotationReport(null);
+ StringBuilder sb = new StringBuilder();
+
+ SequenceI seq = new Sequence("s1", "MAKLKRFQSSTLL");
+ seq.setDescription("SeqDesc");
+
+ sar.createSequenceAnnotationReport(sb, seq, true, true, null);
+
+ /*
+ * positional features are ignored
+ */
+ seq.addSequenceFeature(new SequenceFeature("Domain", "Ferredoxin", 5,
+ 10, 1f, null));
+ assertEquals("<i><br>SeqDesc</i>", sb.toString());
+
+ /*
+ * non-positional feature
+ */
+ seq.addSequenceFeature(new SequenceFeature("Type1", "Nonpos", 0, 0, 1f,
+ null));
+ sb.setLength(0);
+ sar.createSequenceAnnotationReport(sb, seq, true, true, null);
+ String expected = "<i><br>SeqDesc<br>Type1 ; Nonpos</i>";
+ assertEquals(expected, sb.toString());
+
+ /*
+ * non-positional features not wanted
+ */
+ sb.setLength(0);
+ sar.createSequenceAnnotationReport(sb, seq, true, false, null);
+ assertEquals("<i><br>SeqDesc</i>", sb.toString());
+
+ /*
+ * add non-pos feature with score inside min-max range for feature type
+ * minmax holds { [positionalMin, positionalMax], [nonPosMin, nonPosMax] }
+ * score is only appended for positional features so ignored here!
+ * minMax are not recorded for non-positional features
+ */
+ seq.addSequenceFeature(new SequenceFeature("Metal", "Desc", 0, 0, 5f,
+ null));
+ Map<String, float[][]> minmax = new HashMap<String, float[][]>();
+ minmax.put("Metal", new float[][] { null, new float[] { 2f, 5f } });
+ sb.setLength(0);
+ sar.createSequenceAnnotationReport(sb, seq, true, true, minmax);
+ expected = "<i><br>SeqDesc<br>Metal ; Desc<br>Type1 ; Nonpos</i>";
+ assertEquals(expected, sb.toString());
+
+ /*
+ * 'linkonly' features are ignored; this is obsolete, as linkonly
+ * is only set by DasSequenceFetcher, and DAS is history
+ */
+ SequenceFeature sf = new SequenceFeature("Metal", "Desc", 0, 0, 5f,
+ null);
+ sf.setValue("linkonly", Boolean.TRUE);
+ seq.addSequenceFeature(sf);
+ sb.setLength(0);
+ sar.createSequenceAnnotationReport(sb, seq, true, true, minmax);
+ assertEquals(expected, sb.toString()); // unchanged!
+
+ /*
+ * 'clinical_significance' currently being specially included
+ */
+ SequenceFeature sf2 = new SequenceFeature("Variant", "Havana", 0, 0,
+ 5f, null);
+ sf2.setValue(GffConstants.CLINICAL_SIGNIFICANCE, "benign");
+ seq.addSequenceFeature(sf2);
+ sb.setLength(0);
+ sar.createSequenceAnnotationReport(sb, seq, true, true, minmax);
+ expected = "<i><br>SeqDesc<br>Metal ; Desc<br>Type1 ; Nonpos<br>Variant ; Havana; benign</i>";
+ assertEquals(expected, sb.toString());
+
+ /*
+ * add dbrefs
+ */
+ seq.addDBRef(new DBRefEntry("PDB", "0", "3iu1"));
+ seq.addDBRef(new DBRefEntry("Uniprot", "1", "P30419"));
+ // with showDbRefs = false
+ sb.setLength(0);
+ sar.createSequenceAnnotationReport(sb, seq, false, true, minmax);
+ assertEquals(expected, sb.toString()); // unchanged
+ // with showDbRefs = true
+ sb.setLength(0);
+ sar.createSequenceAnnotationReport(sb, seq, true, true, minmax);
+ expected = "<i><br>SeqDesc<br>UNIPROT P30419<br>PDB 3iu1<br>Metal ; Desc<br>Type1 ; Nonpos<br>Variant ; Havana; benign</i>";
+ assertEquals(expected, sb.toString());
+ // with showNonPositionalFeatures = false
+ sb.setLength(0);
+ sar.createSequenceAnnotationReport(sb, seq, true, false, minmax);
+ expected = "<i><br>SeqDesc<br>UNIPROT P30419<br>PDB 3iu1</i>";
+ assertEquals(expected, sb.toString());
+
+ // see other tests for treatment of status and html
+ }
+
+ /**
+ * Test that exercises an abbreviated sequence details report, with ellipsis
+ * where there are more than 40 different sources, or more than 4 dbrefs for a
+ * single source
+ */
+ @Test(groups = "Functional")
+ public void testCreateSequenceAnnotationReport_withEllipsis()
+ {
+ SequenceAnnotationReport sar = new SequenceAnnotationReport(null);
+ StringBuilder sb = new StringBuilder();
+
+ SequenceI seq = new Sequence("s1", "ABC");
+
+ int maxSources = (int) PA.getValue(sar, "MAX_SOURCES");
+ for (int i = 0; i <= maxSources; i++)
+ {
+ seq.addDBRef(new DBRefEntry("PDB" + i, "0", "3iu1"));
+ }
+
+ int maxRefs = (int) PA.getValue(sar, "MAX_REFS_PER_SOURCE");
+ for (int i = 0; i <= maxRefs; i++)
+ {
+ seq.addDBRef(new DBRefEntry("Uniprot", "0", "P3041" + i));
+ }
+
+ sar.createSequenceAnnotationReport(sb, seq, true, true, null, true);
+ String report = sb.toString();
+ assertTrue(report
+ .startsWith("<i><br>UNIPROT P30410, P30411, P30412, P30413,...<br>PDB0 3iu1"));
+ assertTrue(report
+ .endsWith("<br>PDB7 3iu1<br>PDB8,...<br>(Output Sequence Details to list all database references)</i>"));
+ }
}
seq_original = al.getSequencesArray();
SequenceI[] seq_new = new SequenceI[al_input.getSequencesArray().length];
seq_new = al_input.getSequencesArray();
- SequenceFeature[] sequenceFeatures_original, sequenceFeatures_new;
+ List<SequenceFeature> sequenceFeatures_original;
+ List<SequenceFeature> sequenceFeatures_new;
AlignmentAnnotation annot_original, annot_new;
//
for (int i = 0; i < al.getSequencesArray().length; i++)
&& seq_new[in].getSequenceFeatures() != null)
{
System.out.println("There are feature!!!");
- sequenceFeatures_original = new SequenceFeature[seq_original[i]
- .getSequenceFeatures().length];
sequenceFeatures_original = seq_original[i]
.getSequenceFeatures();
- sequenceFeatures_new = new SequenceFeature[seq_new[in]
- .getSequenceFeatures().length];
sequenceFeatures_new = seq_new[in].getSequenceFeatures();
- assertEquals("different number of features",
- seq_original[i].getSequenceFeatures().length,
- seq_new[in].getSequenceFeatures().length);
+ assertEquals("different number of features", seq_original[i]
+ .getSequenceFeatures().size(), seq_new[in]
+ .getSequenceFeatures().size());
- for (int feat = 0; feat < seq_original[i].getSequenceFeatures().length; feat++)
+ for (int feat = 0; feat < seq_original[i].getSequenceFeatures()
+ .size(); feat++)
{
assertEquals("Different features",
- sequenceFeatures_original[feat],
- sequenceFeatures_new[feat]);
+ sequenceFeatures_original.get(feat),
+ sequenceFeatures_new.get(feat));
}
}
// compare alignment annotation
import java.io.IOException;
import java.util.ArrayList;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
.getToRanges().get(0));
}
+ @Test(groups = "Functional")
+ public void testGetDescription()
+ {
+ Gff3Helper testee = new Gff3Helper();
+ SequenceFeature sf = new SequenceFeature("type", "desc", 10, 20, 3f,
+ "group");
+ Map<String, List<String>> attributes = new HashMap<String, List<String>>();
+ assertNull(testee.getDescription(sf, attributes));
+
+ // ID if any is a fall-back for description
+ sf.setValue("ID", "Patrick");
+ assertEquals("Patrick", testee.getDescription(sf, attributes));
+
+ // Target is set by Exonerate
+ sf.setValue("Target", "Destination Moon");
+ assertEquals("Destination", testee.getDescription(sf, attributes));
+
+ // Ensembl variant feature - extract "alleles" value
+ // may be sequence_variant or a sub-type in the sequence ontology
+ sf = new SequenceFeature("feature_variant", "desc", 10, 20, 3f, "group");
+ List<String> atts = new ArrayList<String>();
+ atts.add("A");
+ atts.add("C");
+ atts.add("T");
+ attributes.put("alleles", atts);
+ assertEquals("A,C,T", testee.getDescription(sf, attributes));
+
+ // Ensembl transcript or exon feature - extract Name
+ List<String> atts2 = new ArrayList<String>();
+ atts2.add("ENSE00001871077");
+ attributes.put("Name", atts2);
+ sf = new SequenceFeature("transcript", "desc", 10, 20, 3f, "group");
+ assertEquals("ENSE00001871077", testee.getDescription(sf, attributes));
+ // transcript sub-type in SO
+ sf = new SequenceFeature("mRNA", "desc", 10, 20, 3f, "group");
+ assertEquals("ENSE00001871077", testee.getDescription(sf, attributes));
+ // special usage of feature by Ensembl
+ sf = new SequenceFeature("NMD_transcript_variant", "desc", 10, 20, 3f,
+ "group");
+ assertEquals("ENSE00001871077", testee.getDescription(sf, attributes));
+ // exon feature
+ sf = new SequenceFeature("exon", "desc", 10, 20, 3f, "group");
+ assertEquals("ENSE00001871077", testee.getDescription(sf, attributes));
+ }
}
package jalview.io.gff;
import static org.testng.AssertJUnit.assertEquals;
+import static org.testng.AssertJUnit.assertNotNull;
import static org.testng.AssertJUnit.assertSame;
import static org.testng.AssertJUnit.assertTrue;
import static org.testng.internal.junit.ArrayAsserts.assertArrayEquals;
import jalview.datamodel.AlignmentI;
import jalview.datamodel.Sequence;
import jalview.datamodel.SequenceDummy;
+import jalview.datamodel.SequenceFeature;
import jalview.datamodel.SequenceI;
import jalview.gui.JvOptionPane;
assertEquals(1, newseqs.size());
assertTrue(newseqs.get(0) instanceof SequenceDummy);
assertEquals("match$17_5_30", newseqs.get(0).getName());
+
+ assertNotNull(newseqs.get(0).getSequenceFeatures());
+ assertEquals(1, newseqs.get(0).getSequenceFeatures().size());
+ SequenceFeature sf = newseqs.get(0).getSequenceFeatures().get(0);
+ assertEquals(1, sf.getBegin());
+ assertEquals(26, sf.getEnd());
+ assertEquals("Pfam", sf.getType());
+ assertEquals("4Fe-4S dicluster domain", sf.getDescription());
+ assertEquals("InterProScan", sf.getFeatureGroup());
+
assertEquals(1, align.getCodonFrames().size());
AlignedCodonFrame mapping = align.getCodonFrames().iterator().next();
import jalview.schemes.FeatureColour;
import java.awt.Color;
+import java.util.List;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.BeforeTest;
@BeforeMethod(alwaysRun = true)
public void setUpBeforeTest()
{
- SequenceFeature[] sfs = seq.getSequenceFeatures();
- if (sfs != null)
+ List<SequenceFeature> sfs = seq.getSequenceFeatures();
+ for (SequenceFeature sf : sfs)
{
- for (SequenceFeature sf : sfs)
- {
- seq.deleteFeature(sf);
- }
+ seq.deleteFeature(sf);
}
fr.findAllFeatures(true);
@Test(groups = "Functional")
public void testFindFeatureColour_graduatedWithThreshold()
{
- seq.addSequenceFeature(new SequenceFeature("kd", "hydrophobicity", 2,
+ String kdFeature = "kd";
+ String metalFeature = "Metal";
+ seq.addSequenceFeature(new SequenceFeature(kdFeature, "hydrophobicity", 2,
2, 0f, "KdGroup"));
- seq.addSequenceFeature(new SequenceFeature("kd", "hydrophobicity", 4,
+ seq.addSequenceFeature(new SequenceFeature(kdFeature, "hydrophobicity", 4,
4, 5f, "KdGroup"));
- seq.addSequenceFeature(new SequenceFeature("kd", "hydrophobicity", 7,
+ seq.addSequenceFeature(new SequenceFeature(metalFeature, "Fe", 4, 4,
+ 5f, "MetalGroup"));
+ seq.addSequenceFeature(new SequenceFeature(kdFeature, "hydrophobicity", 7,
7, 10f, "KdGroup"));
/*
- * graduated colour from 0 to 10
+ * kd feature has graduated colour from 0 to 10
* above threshold value of 5
*/
Color min = new Color(100, 50, 150);
FeatureColourI fc = new FeatureColour(min, max, 0, 10);
fc.setAboveThreshold(true);
fc.setThreshold(5f);
- fr.setColour("kd", fc);
+ fr.setColour(kdFeature, fc);
+ FeatureColour green = new FeatureColour(Color.green);
+ fr.setColour(metalFeature, green);
fr.featuresAdded();
+
+ /*
+ * render order is kd above Metal
+ */
+ Object[][] data = new Object[2][];
+ data[0] = new Object[] { kdFeature, fc, true };
+ data[1] = new Object[] { metalFeature, green, true };
+ fr.setFeaturePriority(data);
+
av.setShowSequenceFeatures(true);
/*
assertEquals(c, Color.blue);
/*
- * position 4, column 3, score 5 - at threshold - default colour
+ * position 4, column 3, score 5 - at threshold
+ * should return Green (colour of Metal feature)
*/
c = finder.findFeatureColour(Color.blue, seq, 3);
- assertEquals(c, Color.blue);
+ assertEquals(c, Color.green);
/*
* position 7, column 9, score 10 - maximum colour in range
assertEquals(c, min);
/*
- * position 4, column 3, score 5 - at threshold - default colour
+ * position 4, column 3, score 5 - at threshold
+ * should return Green (colour of Metal feature)
*/
c = finder.findFeatureColour(Color.blue, seq, 3);
- assertEquals(c, Color.blue);
+ assertEquals(c, Color.green);
/*
* position 7, column 9, score 10 - above threshold - default colour
--- /dev/null
+package jalview.renderer.seqfeatures;
+
+import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
+import static org.testng.Assert.assertTrue;
+
+import jalview.api.AlignViewportI;
+import jalview.api.FeatureColourI;
+import jalview.datamodel.SequenceFeature;
+import jalview.datamodel.SequenceI;
+import jalview.gui.AlignFrame;
+import jalview.io.DataSourceType;
+import jalview.io.FileLoader;
+import jalview.schemes.FeatureColour;
+
+import java.awt.Color;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+import org.testng.annotations.Test;
+
+public class FeatureRendererTest
+{
+
+ @Test(groups = "Functional")
+ public void testFindAllFeatures()
+ {
+ String seqData = ">s1\nabcdef\n>s2\nabcdef\n>s3\nabcdef\n>s4\nabcdef\n";
+ AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(seqData,
+ DataSourceType.PASTE);
+ AlignViewportI av = af.getViewport();
+ FeatureRenderer fr = new FeatureRenderer(av);
+
+ /*
+ * with no features
+ */
+ fr.findAllFeatures(true);
+ assertTrue(fr.getRenderOrder().isEmpty());
+ assertTrue(fr.getFeatureGroups().isEmpty());
+
+ List<SequenceI> seqs = av.getAlignment().getSequences();
+
+ // add a non-positional feature - should be ignored by FeatureRenderer
+ SequenceFeature sf1 = new SequenceFeature("Type", "Desc", 0, 0, 1f,
+ "Group");
+ seqs.get(0).addSequenceFeature(sf1);
+ fr.findAllFeatures(true);
+ // ? bug - types and groups added for non-positional features
+ List<String> types = fr.getRenderOrder();
+ List<String> groups = fr.getFeatureGroups();
+ assertEquals(types.size(), 0);
+ assertFalse(types.contains("Type"));
+ assertEquals(groups.size(), 0);
+ assertFalse(groups.contains("Group"));
+
+ // add some positional features
+ seqs.get(1).addSequenceFeature(
+ new SequenceFeature("Pfam", "Desc", 5, 9, 1f, "PfamGroup"));
+ seqs.get(2).addSequenceFeature(
+ new SequenceFeature("Pfam", "Desc", 14, 22, 2f, "RfamGroup"));
+ // bug in findAllFeatures - group not checked for a known feature type
+ seqs.get(2).addSequenceFeature(
+ new SequenceFeature("Rfam", "Desc", 5, 9, Float.NaN,
+ "RfamGroup"));
+ // existing feature type with null group
+ seqs.get(3).addSequenceFeature(
+ new SequenceFeature("Rfam", "Desc", 5, 9, Float.NaN, null));
+ // new feature type with null group
+ seqs.get(3).addSequenceFeature(
+ new SequenceFeature("Scop", "Desc", 5, 9, Float.NaN, null));
+ // null value for type produces NullPointerException
+ fr.findAllFeatures(true);
+ types = fr.getRenderOrder();
+ groups = fr.getFeatureGroups();
+ assertEquals(types.size(), 3);
+ assertFalse(types.contains("Type"));
+ assertTrue(types.contains("Pfam"));
+ assertTrue(types.contains("Rfam"));
+ assertTrue(types.contains("Scop"));
+ assertEquals(groups.size(), 2);
+ assertFalse(groups.contains("Group"));
+ assertTrue(groups.contains("PfamGroup"));
+ assertTrue(groups.contains("RfamGroup"));
+ assertFalse(groups.contains(null)); // null group is ignored
+
+ /*
+ * check min-max values
+ */
+ Map<String, float[][]> minMax = fr.getMinMax();
+ assertEquals(minMax.size(), 1); // non-positional and NaN not stored
+ assertEquals(minMax.get("Pfam")[0][0], 1f); // positional min
+ assertEquals(minMax.get("Pfam")[0][1], 2f); // positional max
+
+ // increase max for Pfam, add scores for Rfam
+ seqs.get(0).addSequenceFeature(
+ new SequenceFeature("Pfam", "Desc", 14, 22, 8f, "RfamGroup"));
+ seqs.get(1).addSequenceFeature(
+ new SequenceFeature("Rfam", "Desc", 5, 9, 6f, "RfamGroup"));
+ fr.findAllFeatures(true);
+ // note minMax is not a defensive copy, shouldn't expose this
+ assertEquals(minMax.size(), 2);
+ assertEquals(minMax.get("Pfam")[0][0], 1f);
+ assertEquals(minMax.get("Pfam")[0][1], 8f);
+ assertEquals(minMax.get("Rfam")[0][0], 6f);
+ assertEquals(minMax.get("Rfam")[0][1], 6f);
+
+ /*
+ * check render order (last is on top)
+ */
+ List<String> renderOrder = fr.getRenderOrder();
+ assertEquals(renderOrder, Arrays.asList("Scop", "Rfam", "Pfam"));
+
+ /*
+ * change render order (todo: an easier way)
+ * nb here last comes first in the data array
+ */
+ Object[][] data = new Object[3][];
+ FeatureColourI colour = new FeatureColour(Color.RED);
+ data[0] = new Object[] { "Rfam", colour, true };
+ data[1] = new Object[] { "Pfam", colour, false };
+ data[2] = new Object[] { "Scop", colour, false };
+ fr.setFeaturePriority(data);
+ assertEquals(fr.getRenderOrder(), Arrays.asList("Scop", "Pfam", "Rfam"));
+ assertEquals(fr.getDisplayedFeatureTypes(), Arrays.asList("Rfam"));
+
+ /*
+ * add a new feature type: should go on top of render order as visible,
+ * other feature ordering and visibility should be unchanged
+ */
+ seqs.get(2).addSequenceFeature(
+ new SequenceFeature("Metal", "Desc", 14, 22, 8f, "MetalGroup"));
+ fr.findAllFeatures(true);
+ assertEquals(fr.getRenderOrder(),
+ Arrays.asList("Scop", "Pfam", "Rfam", "Metal"));
+ assertEquals(fr.getDisplayedFeatureTypes(),
+ Arrays.asList("Rfam", "Metal"));
+ }
+
+ @Test(groups = "Functional")
+ public void testFindFeaturesAtColumn()
+ {
+ String seqData = ">s1/4-29\n-ab--cdefghijklmnopqrstuvwxyz\n";
+ AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(seqData,
+ DataSourceType.PASTE);
+ AlignViewportI av = af.getViewport();
+ FeatureRenderer fr = new FeatureRenderer(av);
+ SequenceI seq = av.getAlignment().getSequenceAt(0);
+
+ /*
+ * with no features
+ */
+ List<SequenceFeature> features = fr.findFeaturesAtColumn(seq, 3);
+ assertTrue(features.isEmpty());
+
+ /*
+ * add features
+ */
+ SequenceFeature sf1 = new SequenceFeature("Type1", "Desc", 0, 0, 1f,
+ "Group"); // non-positional
+ seq.addSequenceFeature(sf1);
+ SequenceFeature sf2 = new SequenceFeature("Type2", "Desc", 8, 18, 1f,
+ "Group1");
+ seq.addSequenceFeature(sf2);
+ SequenceFeature sf3 = new SequenceFeature("Type3", "Desc", 8, 18, 1f,
+ "Group2");
+ seq.addSequenceFeature(sf3);
+ SequenceFeature sf4 = new SequenceFeature("Type3", "Desc", 8, 18, 1f,
+ null); // null group is always treated as visible
+ seq.addSequenceFeature(sf4);
+
+ /*
+ * add contact features
+ */
+ SequenceFeature sf5 = new SequenceFeature("Disulphide Bond", "Desc", 7,
+ 15, 1f, "Group1");
+ seq.addSequenceFeature(sf5);
+ SequenceFeature sf6 = new SequenceFeature("Disulphide Bond", "Desc", 7,
+ 15, 1f, "Group2");
+ seq.addSequenceFeature(sf6);
+ SequenceFeature sf7 = new SequenceFeature("Disulphide Bond", "Desc", 7,
+ 15, 1f, null);
+ seq.addSequenceFeature(sf7);
+
+ // feature spanning B--C
+ SequenceFeature sf8 = new SequenceFeature("Type1", "Desc", 5, 6, 1f,
+ "Group");
+ seq.addSequenceFeature(sf8);
+ // contact feature B/C
+ SequenceFeature sf9 = new SequenceFeature("Disulphide Bond", "Desc", 5,
+ 6, 1f, "Group");
+ seq.addSequenceFeature(sf9);
+
+ /*
+ * let feature renderer discover features (and make visible)
+ */
+ fr.findAllFeatures(true);
+ features = fr.findFeaturesAtColumn(seq, 15); // all positional
+ assertEquals(features.size(), 6);
+ assertTrue(features.contains(sf2));
+ assertTrue(features.contains(sf3));
+ assertTrue(features.contains(sf4));
+ assertTrue(features.contains(sf5));
+ assertTrue(features.contains(sf6));
+ assertTrue(features.contains(sf7));
+
+ /*
+ * at a non-contact position
+ */
+ features = fr.findFeaturesAtColumn(seq, 14);
+ assertEquals(features.size(), 3);
+ assertTrue(features.contains(sf2));
+ assertTrue(features.contains(sf3));
+ assertTrue(features.contains(sf4));
+
+ /*
+ * make "Type2" not displayed
+ */
+ Object[][] data = new Object[4][];
+ FeatureColourI colour = new FeatureColour(Color.RED);
+ data[0] = new Object[] { "Type1", colour, true };
+ data[1] = new Object[] { "Type2", colour, false };
+ data[2] = new Object[] { "Type3", colour, true };
+ data[3] = new Object[] { "Disulphide Bond", colour, true };
+ fr.setFeaturePriority(data);
+
+ features = fr.findFeaturesAtColumn(seq, 15);
+ assertEquals(features.size(), 5); // no sf2
+ assertTrue(features.contains(sf3));
+ assertTrue(features.contains(sf4));
+ assertTrue(features.contains(sf5));
+ assertTrue(features.contains(sf6));
+ assertTrue(features.contains(sf7));
+
+ /*
+ * make "Group2" not displayed
+ */
+ fr.setGroupVisibility("Group2", false);
+
+ features = fr.findFeaturesAtColumn(seq, 15);
+ assertEquals(features.size(), 3); // no sf2, sf3, sf6
+ assertTrue(features.contains(sf4));
+ assertTrue(features.contains(sf5));
+ assertTrue(features.contains(sf7));
+
+ // features 'at' a gap between b and c
+ // - returns enclosing feature BC but not contact feature B/C
+ features = fr.findFeaturesAtColumn(seq, 4);
+ assertEquals(features.size(), 1);
+ assertTrue(features.contains(sf8));
+ features = fr.findFeaturesAtColumn(seq, 5);
+ assertEquals(features.size(), 1);
+ assertTrue(features.contains(sf8));
+ }
+
+ @Test(groups = "Functional")
+ public void testFilterFeaturesForDisplay()
+ {
+ String seqData = ">s1\nabcdef\n";
+ AlignFrame af = new FileLoader().LoadFileWaitTillLoaded(seqData,
+ DataSourceType.PASTE);
+ AlignViewportI av = af.getViewport();
+ FeatureRenderer fr = new FeatureRenderer(av);
+
+ List<SequenceFeature> features = new ArrayList<>();
+ fr.filterFeaturesForDisplay(features, null); // empty list, does nothing
+
+ SequenceI seq = av.getAlignment().getSequenceAt(0);
+ SequenceFeature sf1 = new SequenceFeature("Cath", "", 6, 8, Float.NaN,
+ "group1");
+ seq.addSequenceFeature(sf1);
+ SequenceFeature sf2 = new SequenceFeature("Cath", "", 5, 11, 2f,
+ "group2");
+ seq.addSequenceFeature(sf2);
+ SequenceFeature sf3 = new SequenceFeature("Cath", "", 5, 11, 3f,
+ "group3");
+ seq.addSequenceFeature(sf3);
+ SequenceFeature sf4 = new SequenceFeature("Cath", "", 6, 8, 4f,
+ "group4");
+ seq.addSequenceFeature(sf4);
+ SequenceFeature sf5 = new SequenceFeature("Cath", "", 6, 9, 5f,
+ "group4");
+ seq.addSequenceFeature(sf5);
+
+ fr.findAllFeatures(true);
+
+ features = seq.getSequenceFeatures();
+ assertEquals(features.size(), 5);
+ assertTrue(features.contains(sf1));
+ assertTrue(features.contains(sf2));
+ assertTrue(features.contains(sf3));
+ assertTrue(features.contains(sf4));
+ assertTrue(features.contains(sf5));
+
+ /*
+ * filter out duplicate (co-located) features
+ * note: which gets removed is not guaranteed
+ */
+ fr.filterFeaturesForDisplay(features, new FeatureColour(Color.blue));
+ assertEquals(features.size(), 3);
+ assertTrue(features.contains(sf1) || features.contains(sf4));
+ assertFalse(features.contains(sf1) && features.contains(sf4));
+ assertTrue(features.contains(sf2) || features.contains(sf3));
+ assertFalse(features.contains(sf2) && features.contains(sf3));
+ assertTrue(features.contains(sf5));
+
+ /*
+ * hide group 3 - sf3 is removed, sf2 is retained
+ */
+ fr.setGroupVisibility("group3", false);
+ features = seq.getSequenceFeatures();
+ fr.filterFeaturesForDisplay(features, new FeatureColour(Color.blue));
+ assertEquals(features.size(), 3);
+ assertTrue(features.contains(sf1) || features.contains(sf4));
+ assertFalse(features.contains(sf1) && features.contains(sf4));
+ assertTrue(features.contains(sf2));
+ assertFalse(features.contains(sf3));
+ assertTrue(features.contains(sf5));
+
+ /*
+ * hide group 2, show group 3 - sf2 is removed, sf3 is retained
+ */
+ fr.setGroupVisibility("group2", false);
+ fr.setGroupVisibility("group3", true);
+ features = seq.getSequenceFeatures();
+ fr.filterFeaturesForDisplay(features, null);
+ assertEquals(features.size(), 3);
+ assertTrue(features.contains(sf1) || features.contains(sf4));
+ assertFalse(features.contains(sf1) && features.contains(sf4));
+ assertFalse(features.contains(sf2));
+ assertTrue(features.contains(sf3));
+ assertTrue(features.contains(sf5));
+
+ /*
+ * no filtering of co-located features with graduated colour scheme
+ * filterFeaturesForDisplay does _not_ check colour threshold
+ * sf2 is removed as its group is hidden
+ */
+ features = seq.getSequenceFeatures();
+ fr.filterFeaturesForDisplay(features, new FeatureColour(Color.black,
+ Color.white, 0f, 1f));
+ assertEquals(features.size(), 4);
+ assertTrue(features.contains(sf1));
+ assertTrue(features.contains(sf3));
+ assertTrue(features.contains(sf4));
+ assertTrue(features.contains(sf5));
+
+ /*
+ * co-located features with colour by label
+ * should not get filtered
+ */
+ features = seq.getSequenceFeatures();
+ FeatureColour fc = new FeatureColour(Color.black);
+ fc.setColourByLabel(true);
+ fr.filterFeaturesForDisplay(features, fc);
+ assertEquals(features.size(), 4);
+ assertTrue(features.contains(sf1));
+ assertTrue(features.contains(sf3));
+ assertTrue(features.contains(sf4));
+ assertTrue(features.contains(sf5));
+ }
+}
anns[col] = new Annotation("a", "a", 'a', col, colour);
}
- seq = new Sequence("", "");
+ seq = new Sequence("Seq", "");
al = new Alignment(new SequenceI[]{ seq});
/*
import static org.testng.AssertJUnit.assertEquals;
import static org.testng.AssertJUnit.assertFalse;
+import static org.testng.AssertJUnit.assertNull;
import static org.testng.AssertJUnit.assertTrue;
import static org.testng.AssertJUnit.fail;
}
@Test(groups = { "Functional" })
- public void testIsColored_simpleColour()
- {
- FeatureColour fc = new FeatureColour(Color.RED);
- assertTrue(fc.isColored(new SequenceFeature()));
- }
-
- @Test(groups = { "Functional" })
- public void testIsColored_colourByLabel()
- {
- FeatureColour fc = new FeatureColour();
- fc.setColourByLabel(true);
- assertTrue(fc.isColored(new SequenceFeature()));
- }
-
- @Test(groups = { "Functional" })
- public void testIsColored_aboveThreshold()
- {
- // graduated colour range from score 20 to 100
- FeatureColour fc = new FeatureColour(Color.WHITE, Color.BLACK, 20f,
- 100f);
-
- // score 0 is adjusted to bottom of range
- SequenceFeature sf = new SequenceFeature("type", "desc", 0, 20, 0f,
- null);
- assertTrue(fc.isColored(sf));
- assertEquals(Color.WHITE, fc.getColor(sf));
-
- // score 120 is adjusted to top of range
- sf.setScore(120f);
- assertEquals(Color.BLACK, fc.getColor(sf));
-
- // value below threshold is still rendered
- // setting threshold has no effect yet...
- fc.setThreshold(60f);
- sf.setScore(36f);
- assertTrue(fc.isColored(sf));
- assertEquals(new Color(204, 204, 204), fc.getColor(sf));
-
- // now apply threshold:
- fc.setAboveThreshold(true);
- assertFalse(fc.isColored(sf));
- // colour is still returned though ?!?
- assertEquals(new Color(204, 204, 204), fc.getColor(sf));
-
- sf.setScore(84); // above threshold now
- assertTrue(fc.isColored(sf));
- assertEquals(new Color(51, 51, 51), fc.getColor(sf));
- }
-
- @Test(groups = { "Functional" })
public void testGetColor_simpleColour()
{
FeatureColour fc = new FeatureColour(Color.RED);
- assertEquals(Color.RED, fc.getColor(new SequenceFeature()));
+ assertEquals(Color.RED,
+ fc.getColor(new SequenceFeature("Cath", "", 1, 2, 0f, null)));
}
@Test(groups = { "Functional" })
}
@Test(groups = { "Functional" })
- public void testGetColor_belowThreshold()
+ public void testGetColor_aboveBelowThreshold()
{
// gradient from [50, 150] from WHITE(255, 255, 255) to BLACK(0, 0, 0)
FeatureColour fc = new FeatureColour(Color.WHITE, Color.BLACK, 50f,
150f);
SequenceFeature sf = new SequenceFeature("type", "desc", 0, 20, 70f,
null);
+
+ /*
+ * feature with score of Float.NaN is always assigned minimum colour
+ */
+ SequenceFeature sf2 = new SequenceFeature("type", "desc", 0, 20,
+ Float.NaN, null);
+
fc.setThreshold(100f); // ignore for now
- assertTrue(fc.isColored(sf));
assertEquals(new Color(204, 204, 204), fc.getColor(sf));
+ assertEquals(Color.white, fc.getColor(sf2));
fc.setAboveThreshold(true); // feature lies below threshold
- assertFalse(fc.isColored(sf));
- assertEquals(new Color(204, 204, 204), fc.getColor(sf));
+ assertNull(fc.getColor(sf));
+ assertEquals(Color.white, fc.getColor(sf2));
+
+ fc.setBelowThreshold(true);
+ fc.setThreshold(70f);
+ assertNull(fc.getColor(sf)); // feature score == threshold - hidden
+ assertEquals(Color.white, fc.getColor(sf2));
+ fc.setThreshold(69f);
+ assertNull(fc.getColor(sf)); // feature score > threshold - hidden
+ assertEquals(Color.white, fc.getColor(sf2));
}
/**
/*
* Verify a RESNUM sequence feature in the PDBfile sequence
*/
- SequenceFeature sf = pmap.getSeqs().get(0).getSequenceFeatures()[0];
+ SequenceFeature sf = pmap.getSeqs().get(0).getSequenceFeatures().get(0);
assertEquals("RESNUM", sf.getType());
assertEquals("1gaq", sf.getFeatureGroup());
assertEquals("GLU: 19 1gaqA", sf.getDescription());
* sequence
*/
StructureMapping map = sm.getMapping("examples/1gaq.txt")[0];
- sf = map.sequence.getSequenceFeatures()[0];
+ sf = map.sequence.getSequenceFeatures().get(0);
assertEquals("RESNUM", sf.getType());
assertEquals("1gaq", sf.getFeatureGroup());
assertEquals("ALA: 1 1gaqB", sf.getDescription());
package jalview.viewmodel;
import static org.testng.Assert.assertEquals;
+import static org.testng.Assert.assertFalse;
import static org.testng.Assert.assertTrue;
import jalview.analysis.AlignmentGenerator;
import java.beans.PropertyChangeEvent;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Collections;
import java.util.List;
import org.testng.annotations.BeforeClass;
@Test(groups = { "Functional" })
public void testScrollToWrappedVisible()
{
- ViewportRanges vr = new ViewportRanges(al);
+ AlignmentI al2 = gen.generate(60, 30, 1, 5, 5);
+
+ ViewportRanges vr = new ViewportRanges(al2);
+
+ // start with viewport on 5-14
vr.setViewportStartAndWidth(5, 10);
+ assertEquals(vr.getStartRes(), 5);
+ assertEquals(vr.getEndRes(), 14);
- vr.scrollToWrappedVisible(0);
+ // scroll to 12 - no change
+ assertFalse(vr.scrollToWrappedVisible(12));
+ assertEquals(vr.getStartRes(), 5);
+
+ // scroll to 2 - back to 0-9
+ assertTrue(vr.scrollToWrappedVisible(2));
assertEquals(vr.getStartRes(), 0);
+ assertEquals(vr.getEndRes(), 9);
- vr.scrollToWrappedVisible(10);
- assertEquals(vr.getStartRes(), 10);
+ // scroll to 9 - no change
+ assertFalse(vr.scrollToWrappedVisible(9));
+ assertEquals(vr.getStartRes(), 0);
- vr.scrollToWrappedVisible(15);
+ // scroll to 12 - moves to 10-19
+ assertTrue(vr.scrollToWrappedVisible(12));
assertEquals(vr.getStartRes(), 10);
+ assertEquals(vr.getEndRes(), 19);
+
+ vr.setStartRes(13);
+ assertEquals(vr.getStartRes(), 13);
+ assertEquals(vr.getEndRes(), 22);
+
+ // scroll to 45 - jumps to 43-52
+ assertTrue(vr.scrollToWrappedVisible(45));
+ assertEquals(vr.getStartRes(), 43);
+ assertEquals(vr.getEndRes(), 52);
}
// leave until JAL-2388 is merged and we can do without viewport
Arrays.asList("startseq", "startseq", "startseq", "startseq")));
l.reset();
- vr.scrollToWrappedVisible(5);
- assertTrue(l.verify(1, Arrays.asList("startres")));
+ /*
+ * scrollToWrappedVisible does nothing if the target position is
+ * within the current startRes-endRes range
+ */
+ assertFalse(vr.scrollToWrappedVisible(5));
+ assertTrue(l.verify(0, Collections.<String> emptyList()));
l.reset();
+
+ vr.scrollToWrappedVisible(25);
+ assertTrue(l.verify(1, Arrays.asList("startres")));
}
@Test(groups = { "Functional" })
import static org.testng.AssertJUnit.assertNull;
import jalview.datamodel.PDBEntry;
-import jalview.datamodel.SequenceFeature;
import jalview.datamodel.SequenceI;
-import jalview.datamodel.UniprotEntry;
+import jalview.datamodel.xdb.uniprot.UniprotEntry;
+import jalview.datamodel.xdb.uniprot.UniprotFeature;
import jalview.gui.JvOptionPane;
import java.io.Reader;
/*
* Check sequence features
*/
- Vector<SequenceFeature> features = entry.getFeature();
+ Vector<UniprotFeature> features = entry.getFeature();
assertEquals(3, features.size());
- SequenceFeature sf = features.get(0);
+ UniprotFeature sf = features.get(0);
assertEquals("signal peptide", sf.getType());
assertNull(sf.getDescription());
assertNull(sf.getStatus());
- assertEquals(1, sf.getPosition());
assertEquals(1, sf.getBegin());
assertEquals(18, sf.getEnd());
sf = features.get(1);
xref = xrefs.get(2);
assertEquals("AE007869", xref.getId());
assertEquals("EMBL", xref.getType());
- assertEquals("AAK85932.1",
- xref.getProperty("protein sequence ID"));
- assertEquals("Genomic_DNA",
- xref.getProperty("molecule type"));
+ assertEquals("AAK85932.1", xref.getProperty("protein sequence ID"));
+ assertEquals("Genomic_DNA", xref.getProperty("molecule type"));
}
@Test(groups = { "Functional" })
package jalview.ws.seqfetcher;
import static org.testng.AssertJUnit.assertEquals;
+import static org.testng.AssertJUnit.assertFalse;
import static org.testng.AssertJUnit.assertNotNull;
import static org.testng.AssertJUnit.assertTrue;
SequenceI seq = alsq.getSequenceAt(0);
assertEquals("Wrong sequence name", embl.getDbSource() + "|"
+ retrievalId, seq.getName());
- SequenceFeature[] sfs = seq.getSequenceFeatures();
- assertNotNull("Sequence features missing", sfs);
+ List<SequenceFeature> sfs = seq.getSequenceFeatures();
+ assertFalse("Sequence features missing", sfs.isEmpty());
assertTrue(
"Feature not CDS",
FeatureProperties.isCodingFeature(embl.getDbSource(),
- sfs[0].getType()));
- assertEquals(embl.getDbSource(), sfs[0].getFeatureGroup());
+ sfs.get(0).getType()));
+ assertEquals(embl.getDbSource(), sfs.get(0).getFeatureGroup());
DBRefEntry[] dr = DBRefUtils.selectRefs(seq.getDBRefs(),
new String[] { DBRefSource.UNIPROT });
assertNotNull(dr);