package org.genedb.db.loading;
import static org.junit.Assert.*;
import org.gmod.schema.feature.AbstractExon;
import org.gmod.schema.feature.AbstractGene;
import org.gmod.schema.feature.Polypeptide;
import org.gmod.schema.feature.ProductiveTranscript;
import org.gmod.schema.feature.ProteinMatch;
import org.gmod.schema.feature.Region;
import org.gmod.schema.feature.TopLevelFeature;
import org.gmod.schema.feature.Transcript;
import org.gmod.schema.feature.TranscriptRegion;
import org.gmod.schema.mapped.Analysis;
import org.gmod.schema.mapped.AnalysisFeature;
import org.gmod.schema.mapped.CvTerm;
import org.gmod.schema.mapped.DbXRef;
import org.gmod.schema.mapped.Feature;
import org.gmod.schema.mapped.FeatureCvTerm;
import org.gmod.schema.mapped.FeatureDbXRef;
import org.gmod.schema.mapped.FeatureLoc;
import org.gmod.schema.mapped.FeatureProp;
import org.gmod.schema.mapped.Pub;
import org.gmod.schema.mapped.PubDbXRef;
import org.gmod.schema.mapped.Synonym;
import org.apache.log4j.Logger;
import org.hibernate.Session;
import org.hibernate.criterion.Projections;
import org.hibernate.criterion.Restrictions;
import org.springframework.util.Assert;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.SortedSet;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class FeatureTester {
private static final Logger logger = Logger.getLogger(FeatureTester.class);
private Session session;
public FeatureTester(Session session) {
this.session = session;
}
private <T> void assertSetEquals(Collection<T> set, T... elements) {
if (elements.length != set.size()) {
fail(String.format("The set %s is not equal to %s", set, Arrays.toString(elements)));
}
for (T element: elements) {
assertTrue(String.format("Set %s does not contain element '%s'", set, element), set.contains(element));
}
}
private void assertSynonymNamesEqual(Collection<Synonym> set, String... expectedNames) {
Set<String> synonymNames = new HashSet<String>();
for (Synonym synonym: set) {
synonymNames.add(synonym.getName());
}
assertSetEquals(synonymNames, expectedNames);
}
private void assertFeatureUniqueNamesEqual(Collection<? extends Feature> set, String... expectedNames) {
Set<String> featureUniqueNames = new HashSet<String>();
for (Feature feature: set) {
featureUniqueNames.add(feature.getUniqueName());
}
assertSetEquals(featureUniqueNames, expectedNames);
}
private void assertLoc(FeatureLoc loc, int strand, int fmin, int fmax) {
if (loc == null) {
fail("FeatureLoc is null");
}
assertEquals(strand, loc.getStrand().shortValue());
assertEquals(fmin, loc.getFmin().intValue());
assertEquals(fmax, loc.getFmax().intValue());
}
public FeatureTester uniqueNames(Class<? extends Feature> featureClass, String... expectedUniqueNames) {
@SuppressWarnings("unchecked")
List<String> actualUniqueNames = session.createCriteria(featureClass)
.setProjection(Projections.property("uniqueName")).list();
assertSetEquals(actualUniqueNames, expectedUniqueNames);
return this;
}
public FeatureTester names(Class<? extends Feature> featureClass, String... expectedNames) {
@SuppressWarnings("unchecked")
List<String> actualNames = session.createCriteria(featureClass)
.setProjection(Projections.property("name")).list();
assertSetEquals(actualNames, expectedNames);
return this;
}
public GeneTester geneTester(String uniqueName) {
return new GeneTester(uniqueName);
}
public TLFTester tlfTester(Class<? extends TopLevelFeature> tlfClass, String uniqueName) {
return new TLFTester(tlfClass, uniqueName);
}
public GenericTester featureTester(String uniqueName) {
return new GenericTester(uniqueName);
}
/*
* The purpose of this rather complicated type trickery is to ensure
* that the return type of these methods is the type of the subclass,
* for method chaining.
*
* Although the definition may be a little mind-bending, it's easy to
* use. Just define <code>class FooTester extends AbstractTester<FooTester></code>,
* and pass <code>FooTester.class</code> to the super constructor.
*/
abstract class AbstractTester<T extends AbstractTester<T>> {
private Class<T> ourClass;
protected Feature feature;
/*
* Because generics are implemented using type erasure, we have
* to use a Class object to explicitly pass the type information
* from compile-time to run-time. We're using a completely constrained
* Class object, so the compiler will only accept precisely the
* correct Class object here.
*/
protected AbstractTester(Class<T> ourClass, Feature feature) {
assert ourClass.isInstance(this);
assertNotNull("Did not find feature", feature);
this.ourClass = ourClass;
this.feature = feature;
}
public T loc(int strand, int fmin, int fmax) {
assertLoc(feature.getRankZeroFeatureLoc(), strand, fmin, fmax);
return ourClass.cast(this);
}
public T loc(int locgroup, int rank, int strand, int fmin, int fmax) {
assertLoc(feature.getFeatureLoc(locgroup, rank), strand, fmin, fmax);
return ourClass.cast(this);
}
public T loc(String sourceFeatureUniqueName, int locgroup, int rank, int strand, int fmin, int fmax) {
FeatureLoc featureLoc = feature.getFeatureLoc(locgroup, rank);
assertLoc(featureLoc, strand, fmin, fmax);
assertNotNull(featureLoc.getSourceFeature());
assertEquals(sourceFeatureUniqueName, featureLoc.getSourceFeature().getUniqueName());
return ourClass.cast(this);
}
public T noLoc(int locgroup, int rank) {
assertNull(feature.getFeatureLoc(locgroup, rank));
return ourClass.cast(this);
}
public T source(String sourceUniqueName) {
assertEquals(sourceUniqueName, feature.getRankZeroFeatureLoc().getSourceFeature().getUniqueName());
return ourClass.cast(this);
}
public T phaseIsNull() {
for (FeatureLoc featureLoc: feature.getFeatureLocs()) {
assertNull(String.format("Phase of featureloc ID=%d is not null", featureLoc.getFeatureLocId()),
featureLoc.getPhase());
}
return ourClass.cast(this);
}
public T name(String name) {
assertEquals(name, feature.getName());
return ourClass.cast(this);
}
private String getPropertyValue(String cv, String term) {
boolean found = false;
String value = null;
for (FeatureProp featureProp: feature.getFeatureProps()) {
CvTerm propType = featureProp.getType();
String propValue = featureProp.getValue();
logger.trace(String.format("Found property (%s=%s) on '%s'", propType, propValue, feature.getUniqueName()));
if (propType.getCv().getName().equals(cv) && propType.getName().equals(term)) {
if (found) {
fail(String.format("Property '%s' found more than once on feature '%s'", propType, feature.getUniqueName()));
}
value = propValue;
found = true;
}
}
assertTrue (String.format("Property '%s:%s' not found on feature '%s'", cv, term, feature.getUniqueName()), found);
return value;
}
public T property(String cv, String term, String value) {
assertEquals(value, getPropertyValue(cv, term));
return ourClass.cast(this);
}
public T properties(String cv, String term, String... values) {
Set<String> expectedValues = new HashSet<String>();
Set<String> foundValues = new HashSet<String>();
Collections.addAll(expectedValues, values);
for (FeatureProp featureProp: feature.getFeatureProps()) {
CvTerm propType = featureProp.getType();
String propValue = featureProp.getValue();
if (propType.getCv().getName().equals(cv) && propType.getName().equals(term)) {
foundValues.add(propValue);
}
}
assertEquals(
String.format("Feature '%s' does not have the expected %s:%s properties;",
feature.getUniqueName(), cv, term),
expectedValues, foundValues);
return ourClass.cast(this);
}
public T propertyMatches(String cv, String term, String regex) {
Pattern pattern = Pattern.compile(regex);
String value = getPropertyValue(cv, term);
Matcher matcher = pattern.matcher(value);
assertTrue(String.format("Property '%s:%s' has value '%s', which does not match /%s/", cv, term, value, regex),
matcher.matches());
return ourClass.cast(this);
}
public T cvterms(String cvName, String... terms) {
return cvtermsCheckingDb(cvName, null, terms);
}
public T cvtermsCheckingDb(String cvName, String dbName, String... terms) {
Set<String> expectedTerms = new HashSet<String>();
Collections.addAll(expectedTerms, terms);
assertEquals(expectedTerms, getTermNames(cvName, dbName));
return ourClass.cast(this);
}
private Set<String> getTermNames(String cvName, String dbName) {
Set<String> termNames = new HashSet<String>();
for (CvTerm cvTerm: getTerms(cvName, dbName)) {
termNames.add(cvTerm.getName());
}
return termNames;
}
public Set<CvTerm> getTerms(String cvName) {
return getTerms(cvName, null);
}
public Set<CvTerm> getTerms(String cvName, String dbName) {
Set<CvTerm> foundTerms = new HashSet<CvTerm>();
for (FeatureCvTerm featureCvTerm: feature.getFeatureCvTerms()) {
CvTerm cvTerm = featureCvTerm.getCvTerm();
if (cvTerm.getCv().getName().equals(cvName)) {
if (dbName != null) {
logger.trace(String.format("Term '%s' has dbxref '%s'",
cvTerm, cvTerm.getDbXRef()));
assertEquals(dbName, cvTerm.getDbXRef().getDb().getName());
}
foundTerms.add(cvTerm);
}
}
return foundTerms;
}
public T pubs(String... pubUniqueNames) {
Set<String> expectedPubUniqueNames = new HashSet<String>();
Collections.addAll(expectedPubUniqueNames, pubUniqueNames);
assertEquals(expectedPubUniqueNames, getPubUniqueNames());
return ourClass.cast(this);
}
private Set<String> getPubUniqueNames() {
Set<String> pubUniqueNames = new HashSet<String>();
for (Pub pub: feature.getPubs()) {
String pubUniqueName = pub.getUniqueName();
if (pubUniqueName.startsWith("PMID:")) {
String accession = pubUniqueName.substring(5);
Collection<PubDbXRef> pubDbXRefs = pub.getPubDbXRefs();
assertEquals(1, pubDbXRefs.size());
PubDbXRef pubDbXRef = pubDbXRefs.iterator().next();
DbXRef dbXRef = pubDbXRef.getDbXRef();
assertNotNull(dbXRef);
assertEquals("PMID", dbXRef.getDb().getName());
assertEquals(accession, dbXRef.getAccession());
}
pubUniqueNames.add(pubUniqueName);
}
return pubUniqueNames;
}
public T dbXRefs(String... dbXRefStrings) {
Set<String> actualDbXRefStrings = new HashSet<String>();
for (FeatureDbXRef featureDbXRef: feature.getFeatureDbXRefs()) {
actualDbXRefStrings.add(featureDbXRef.getDbXRef().toString());
}
Set<String> expectedDbXRefStrings = new HashSet<String>();
Collections.addAll(expectedDbXRefStrings, dbXRefStrings);
assertEquals(expectedDbXRefStrings, actualDbXRefStrings);
return ourClass.cast(this);
}
public T assertObsolete() {
assertTrue(String.format("Expected feature '%s' to be obsolete", feature.getUniqueName()), feature.isObsolete());
return ourClass.cast(this);
}
}
class GeneTester extends AbstractTester<GeneTester> {
private AbstractGene gene;
private GeneTester(String uniqueName) {
super(GeneTester.class, (Feature) session.createCriteria(AbstractGene.class)
.add(Restrictions.eq("uniqueName", uniqueName))
.uniqueResult());
this.gene = (AbstractGene) feature;
assertNotNull(gene);
boundaries();
}
private GeneTester boundaries() {
int strand = 0, fmin = Integer.MAX_VALUE, fmax = Integer.MIN_VALUE;
Collection<Transcript> transcripts = gene.getTranscripts();
Assert.notEmpty(transcripts, String.format("Gene '%s' has no transcripts", gene));
for (Transcript transcript: gene.getTranscripts()) {
assertEquals(gene.getPrimarySourceFeature(), transcript.getPrimarySourceFeature());
int tStrand = transcript.getStrand();
int tFmin = transcript.getFmin();
int tFmax = transcript.getFmax();
if (tFmin < fmin) {
fmin = tFmin;
}
if (tFmax > fmax) {
fmax = tFmax;
}
if (tStrand == 0) {
fail(String.format("Transcript '%s' of gene '%s' has no strand direction",
transcript.getUniqueName(), gene.getUniqueName()));
}
else if (strand == 0) {
strand = tStrand;
}
else if (strand != tStrand) {
fail(String.format("Gene '%s' has inconsistent strand directions on its transcripts"));
}
}
assertEquals(gene.getFmin(), fmin);
assertEquals(gene.getFmax(), fmax);
assertEquals(gene.getStrand(), strand);
return this;
}
public GeneTester transcripts(String... uniqueNames) {
assertFeatureUniqueNamesEqual(gene.getTranscripts(), uniqueNames);
return this;
}
public TranscriptTester transcript(String uniqueName) {
StringBuilder transcriptNames = new StringBuilder("{");
boolean first = true;
for (Transcript transcript: gene.getTranscripts()) {
if (transcript.getUniqueName().equals(uniqueName)) {
return new TranscriptTester(transcript);
}
if (!first) {
transcriptNames.append(", ");
}
transcriptNames.append(transcript.getUniqueName());
first = false;
}
transcriptNames.append("}");
fail(String.format("Transcript '%s' not found on gene '%s'; transcripts are %s",
uniqueName, gene.getUniqueName(), transcriptNames));
return null; // Not reached; silly compiler
}
}
class TranscriptTester extends AbstractTester<TranscriptTester> {
private Transcript transcript;
private TranscriptTester(Transcript transcript) {
super(TranscriptTester.class, transcript);
this.transcript = (Transcript) feature;
}
public TranscriptTester synonyms(String synonymType, String... expectedUniqueNames) {
assertSynonymNamesEqual(transcript.getSynonyms(synonymType), expectedUniqueNames);
return this;
}
public TranscriptTester hasPolypeptide(String uniqueName) {
assertTrue (transcript instanceof ProductiveTranscript);
ProductiveTranscript productiveTranscript = (ProductiveTranscript) transcript;
Polypeptide polypeptide = productiveTranscript.getProtein();
assertEquals(uniqueName, polypeptide.getUniqueName());
SortedSet<AbstractExon> exons = productiveTranscript.getComponents(AbstractExon.class);
assertLoc(polypeptide.getRankZeroFeatureLoc(), transcript.getStrand(),
exons.first().getFmin(), exons.last().getFmax());
return this;
}
public PolypeptideTester polypeptide(String uniqueName) {
assertTrue (transcript instanceof ProductiveTranscript);
ProductiveTranscript productiveTranscript = (ProductiveTranscript) transcript;
Polypeptide polypeptide = productiveTranscript.getProtein();
assertEquals(uniqueName, polypeptide.getUniqueName());
return new PolypeptideTester(polypeptide);
}
public TranscriptTester singleExon(int strand, int fmin, int fmax) {
Collection<TranscriptRegion> components = transcript.getComponents();
assertEquals(1, components.size());
TranscriptRegion region = components.iterator().next();
assertTrue(region instanceof AbstractExon);
AbstractExon exon = (AbstractExon) region;
assertLoc(exon.getRankZeroFeatureLoc(), strand, fmin, fmax);
return this;
}
public TranscriptTester components(String... expectedUniqueNames) {
assertFeatureUniqueNamesEqual(transcript.getComponents(), expectedUniqueNames);
return this;
}
public ExonTester exon(String uniqueName) {
for (AbstractExon exon: transcript.getExons()) {
if (uniqueName.equals(exon.getUniqueName())) {
return new ExonTester(exon);
}
}
fail(String.format("Exon '%s' not found on transcript '%s'", uniqueName, transcript.getUniqueName()));
return null; // Not reached, but makes compiler happy
}
}
class PolypeptideTester extends AbstractTester<PolypeptideTester> {
private Polypeptide polypeptide;
private PolypeptideTester(Polypeptide polypeptide) {
super(PolypeptideTester.class, polypeptide);
this.polypeptide = (Polypeptide) feature;
}
public SimilarityTester similarity(String db, String accession) {
Set<String> primaryDbXRefsOfFoundMatches = new HashSet<String>();
for (ProteinMatch proteinMatch: polypeptide.getSimilarityMatches()) {
DbXRef dbXRef = proteinMatch.getSubject().getDbXRef();
if (dbXRef.getDb().getName().equals(db) && dbXRef.getAccession().equals(accession)) {
return new SimilarityTester(proteinMatch);
}
primaryDbXRefsOfFoundMatches.add(dbXRef.toString());
}
fail(String.format("Could not find similarity '%s:%s' on polypeptide '%s': found %s",
db, accession, polypeptide.getUniqueName(), primaryDbXRefsOfFoundMatches));
return null; // Not reached
}
}
class SimilarityTester extends AbstractTester<SimilarityTester> {
private ProteinMatch proteinMatch;
private Region subject;
private SimilarityTester(ProteinMatch proteinMatch) {
super(SimilarityTester.class, proteinMatch);
this.proteinMatch = proteinMatch;
this.subject = proteinMatch.getSubject();
}
private AnalysisFeature analysisFeature;
private AnalysisFeature analysisFeature() {
if (analysisFeature != null) {
return analysisFeature;
}
Collection<AnalysisFeature> analysisFeatures = proteinMatch.getAnalysisFeatures();
assertFalse(String.format("Protein match '%s' (ID=%d) has no analysis features",
proteinMatch.getUniqueName(), proteinMatch.getFeatureId()), analysisFeatures.isEmpty());
assertEquals(String.format("Protein match '%s' (ID=%d) has %d analysis features",
proteinMatch.getUniqueName(), proteinMatch.getFeatureId(), analysisFeatures.size()),
1, analysisFeatures.size());
analysisFeature = analysisFeatures.iterator().next();
return analysisFeature;
}
public SimilarityTester organism(String organismName) {
assertEquals(organismName, subject.getFeatureProp("feature_property", "organism"));
return this;
}
public SimilarityTester product(String product) {
assertEquals(product, subject.getFeatureProp("genedb_misc", "product"));
return this;
}
public SimilarityTester gene(String gene) {
assertEquals(gene, subject.getFeatureProp("sequence", "gene"));
return this;
}
public SimilarityTester id(Double id) {
assertEquals(id, analysisFeature().getIdentity());
return this;
}
public SimilarityTester score(Double score) {
assertEquals(score, analysisFeature().getRawScore());
return this;
}
public SimilarityTester eValue(Double eValue) {
assertEquals(eValue, analysisFeature().getSignificance());
return this;
}
public SimilarityTester ungappedId(Double ungappedId) {
String actualUngappedId = proteinMatch.getFeatureProp("genedb_misc", "ungapped id");
if (actualUngappedId == null) {
if (ungappedId != null) {
fail(String.format("Protein match '%s' (ID=%d) has no ungapped id",
proteinMatch.getUniqueName(), proteinMatch.getFeatureId()));
}
return this; // Expected it to be null
}
assertEquals(ungappedId.doubleValue(), Double.parseDouble(actualUngappedId), 0.0001);
return this;
}
public SimilarityTester overlap(Integer overlap) {
String actualOverlap = proteinMatch.getFeatureProp("genedb_misc", "overlap");
if (actualOverlap == null) {
if (overlap != null) {
fail(String.format("Protein match '%s' (ID=%d) has no overlap property",
proteinMatch.getUniqueName(), proteinMatch.getFeatureId()));
}
return this; // Expected it to be null
}
Matcher matcher = Pattern.compile("(\\d+) aa overlap").matcher(actualOverlap);
if (! matcher.matches()) {
fail(String.format("Overlap property has value '%s', which I can't parse", actualOverlap));
}
assertEquals(overlap.intValue(), Integer.parseInt(matcher.group(1)));
return this;
}
public SimilarityTester analysisProgram(String analysisProgram) {
assertEquals(analysisProgram, analysisFeature().getAnalysis().getProgram());
return this;
}
public SimilarityTester analysisProgram(String analysisProgram, String analysisProgramVersion) {
Analysis analysis = analysisFeature().getAnalysis();
assertEquals(analysisProgram, analysis.getProgram());
assertEquals(analysisProgramVersion, analysis.getProgramVersion());
return this;
}
public SimilarityTester analysisAlgorithm(String analysisAlgorithm) {
assertEquals(analysisAlgorithm, analysisFeature().getAnalysis().getAlgorithm());
return this;
}
public SimilarityTester secondaryDbXRefs(String... dbxRefStrings) {
Set<String> actualDbXRefStrings = new HashSet<String>();
for (FeatureDbXRef featureDbXRef: subject.getFeatureDbXRefs()) {
actualDbXRefStrings.add(featureDbXRef.getDbXRef().toString());
}
Set<String> expectedDbXRefStrings = new HashSet<String>();
Collections.addAll(expectedDbXRefStrings, dbxRefStrings);
assertEquals(expectedDbXRefStrings, actualDbXRefStrings);
return this;
}
public int getAnalysisId() {
return analysisFeature.getAnalysis().getAnalysisId();
}
}
class ExonTester extends AbstractTester<ExonTester> {
private AbstractExon exon;
private ExonTester(AbstractExon exon) {
super(ExonTester.class, exon);
this.exon = (AbstractExon) feature;
}
public ExonTester fmin(int fmin) {
assertEquals(fmin, exon.getFmin());
return this;
}
public ExonTester fmax(int fmax) {
assertEquals(fmax, exon.getFmax());
return this;
}
public ExonTester strand(int strand) {
assertEquals(strand, exon.getStrand());
return this;
}
public ExonTester phase(Integer phase) {
for (FeatureLoc featureLoc: exon.getFeatureLocs()) {
assertEquals(phase, featureLoc.getPhase());
}
return this;
}
}
class TLFTester extends AbstractTester<TLFTester> {
private TopLevelFeature tlf;
private TLFTester(Class<? extends TopLevelFeature> tlfClass, String uniqueName) {
super(TLFTester.class, (Feature) session.createCriteria(tlfClass)
.add(Restrictions.eq("uniqueName", uniqueName))
.uniqueResult());
tlf = tlfClass.cast(feature);
assertNotNull(tlf);
}
public TLFTester seqLen(int len) {
assertEquals(len, tlf.getSeqLen());
return this;
}
public TLFTester residues(String residues) {
assertEquals(residues, tlf.getResidues());
return this;
}
}
class GenericTester extends AbstractTester<GenericTester> {
private GenericTester(String uniqueName) {
super(GenericTester.class, (Feature) session.createCriteria(Feature.class)
.add(Restrictions.eq("uniqueName", uniqueName))
.uniqueResult());
}
}
}