package org.gmod.schema.feature; import org.genedb.db.analyzers.AllNamesAnalyzer; import org.gmod.schema.cfg.FeatureType; import org.gmod.schema.mapped.Feature; import org.gmod.schema.mapped.FeatureLoc; import org.gmod.schema.mapped.FeatureRelationship; import org.gmod.schema.mapped.Organism; import org.apache.log4j.Logger; import org.hibernate.search.annotations.Analyzer; import org.hibernate.search.annotations.Field; import org.hibernate.search.annotations.Index; import org.hibernate.search.annotations.Indexed; import org.hibernate.search.annotations.Store; import com.google.common.collect.Lists; import java.lang.reflect.InvocationTargetException; import java.sql.Timestamp; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.SortedSet; import java.util.TreeSet; import javax.persistence.Entity; import javax.persistence.Transient; @Entity @FeatureType(cv = "sequence", term = "transcript") @Indexed public class Transcript extends Region { private static Logger logger = Logger.getLogger(Transcript.class); @Transient protected AbstractGene gene; Transcript() { // empty } public Transcript(Organism organism, String uniqueName, boolean analysis, boolean obsolete, Timestamp dateAccessioned) { super(organism, uniqueName, analysis, obsolete, dateAccessioned); } static <T extends Transcript> T construct(Class<T> transcriptClass, Organism organism, String uniqueName, String name) { try { return transcriptClass.getDeclaredConstructor(Organism.class, String.class, String.class) .newInstance(organism, uniqueName, name); } catch (IllegalArgumentException e) { throw new RuntimeException("Internal error: failed to construct transcript", e); } catch (SecurityException e) { throw new RuntimeException("Internal error: failed to construct transcript", e); } catch (InstantiationException e) { throw new RuntimeException("Internal error: failed to construct transcript", e); } catch (IllegalAccessException e) { throw new RuntimeException("Internal error: failed to construct transcript", e); } catch (InvocationTargetException e) { throw new RuntimeException("Internal error: failed to construct transcript", e); } catch (NoSuchMethodException e) { throw new RuntimeException("Internal error: failed to construct transcript", e); } } public Integer getColourId() { return null; } public AbstractGene getGene() { if (gene != null) { return gene; } for (FeatureRelationship relation : getFeatureRelationshipsForSubjectId()) { Feature geneFeature = relation.getObjectFeature(); if (geneFeature instanceof AbstractGene) { gene = (AbstractGene) geneFeature; break; } } if (gene == null) { logger.error(String.format("The transcript '%s' has no associated gene", getUniqueName())); return null; } return gene; } @Transient protected Polypeptide polypeptide; @Transient public Polypeptide getPolypeptide() { if (polypeptide != null) { return polypeptide; } for (FeatureRelationship relation : getFeatureRelationshipsForObjectId()) { Feature polypeptideFeature = relation.getSubjectFeature(); logger.error(String.format("?? %s %s", polypeptideFeature.getUniqueName(), polypeptideFeature.getClass())); if (polypeptideFeature instanceof Polypeptide) { polypeptide = (Polypeptide) polypeptideFeature; break; } } if (polypeptide == null) { logger.error(String.format("The transcript '%s' has no associated polypeptide", getUniqueName())); return null; } return polypeptide; } /** * Overrides to add the gene name to the names list. */ @Override protected List<String> generateNamesList() { List<String> names = super.generateNamesList(); AbstractGene gene = getGene(); if (gene != null) { names.add(gene.getUniqueName()); } return names; } /** * Get the uniqueName of the gene associated to this transcript. * Equivalent to <code>getGene().getUniqueName()</code>. * * Indexed as <code>gene</code> in the Lucene index. * * @return */ @Transient @Field(name = "gene", index = Index.UN_TOKENIZED, store = Store.YES) public String getGeneUniqueName() { AbstractGene gene = getGene(); if (gene != null) { return gene.getUniqueName(); } return null; } @Transient @Field(name = "alternateTranscriptNumber", index = Index.UN_TOKENIZED, store = Store.YES) public int alternateTranscriptNumber() { AbstractGene gene = getGene(); if (gene != null) { return gene.getNonObsoleteTranscripts().size(); } return 0; } @Transient @Field(name = "alternateTranscripts", index = Index.UN_TOKENIZED, store = Store.YES) public String alternateTranscripts() { AbstractGene gene = getGene(); if (gene != null) { return gene.alternateTranscripts(); } return null; } @Transient @Analyzer(impl = AllNamesAnalyzer.class) @Field(name = "product", index = Index.TOKENIZED, store = Store.YES) public String getProductsAsSpaceSeparatedString() { if (getPolypeptide() != null) { return getPolypeptide().getProductsAsSpaceSeparatedString(); } return null; } /** * Change the gene associated with this transcript. * Assumes that there already is an associated gene. * * @param gene */ public void setGene(AbstractGene gene) { boolean foundGene = false; for (FeatureRelationship relation : getFeatureRelationshipsForSubjectId()) { Feature geneFeature = relation.getObjectFeature(); if (geneFeature instanceof Gene) { foundGene = true; relation.setObjectFeature(gene); this.gene = gene; break; } } if (!foundGene) { logger.error(String.format("The transcript '%s' has no associated gene", getUniqueName())); } } @Transient public Collection<TranscriptRegion> getComponents() { return getComponents(TranscriptRegion.class); } @Transient public <T extends TranscriptRegion> SortedSet<T> getComponents(Class<T> regionClass) { SortedSet<T> components = new TreeSet<T>(); for (FeatureRelationship relation : getFeatureRelationshipsForObjectId()) { Feature feature = relation.getSubjectFeature(); if (regionClass.isInstance(feature)) { components.add(regionClass.cast(feature)); } } return components; } @Transient public SortedSet<AbstractExon> getExons() { return getComponents(AbstractExon.class); } /** * Get the component locations, as a comma-separated string. * Includes UTRs as well as exons. This is stored in the Lucene index, * for use by the chromosome browser. * * @return */ @Transient @Field(name = "locs", store = Store.YES) public String getExonLocs() { StringBuilder locs = new StringBuilder(); boolean first = true; for (TranscriptRegion component : getComponents(TranscriptRegion.class)) { if (first) { first = false; } else { locs.append(','); } if (!(component instanceof AbstractExon)) { locs.append(component.getClass().getSimpleName()); locs.append(':'); } locs.append(component.getLocAsString()); } return locs.toString(); } /** * Get the exon locations in a form suitable for displaying * to the end-user, in traditional coordinates and separated * by a comma and a space. * * @return */ @Transient public String getExonLocsTraditional() { StringBuilder locs = new StringBuilder(); boolean first = true; for (AbstractExon exon : getExons()) { if (first) { first = false; } else { locs.append(", "); } locs.append(exon.getTraditionalLocAsString()); } return locs.toString(); } @Transient protected Class<? extends AbstractExon> getExonClass() { // This method is over-ridden in PseudogenicTranscript return Exon.class; } /** * Create a new exon, and add it to this transcript. * * @param exonUniqueName * @param fmin * @param fmax * @return the newly-created exon */ public AbstractExon createExon(String exonUniqueName, int fmin, int fmax, Integer phase) { /* * NB This method is overridden in ProductiveTranscript. */ return createRegion(getExonClass(), exonUniqueName, fmin, fmax, phase); } public <T extends UTR> T createUTR(Class<T> utrClass, String utrUniqueName, int fmin, int fmax) { return createRegion(utrClass, utrUniqueName, fmin, fmax, null /*phase*/); } /** * Add a TranscriptRegion to this transcript. If the new region is not contained in the boundaries * of this transcript, the transcript and gene boundaries are extended appropriately. * * @param <T> * @param componentClass the class of region to add * @param componentUniqueName the uniquename of the new region * @param fmin the <code>fmin</code>, relative to the primary source feature, of the new region. * @param fmax the <code>fmax</code>, relative to the primary source feature, of the new region * @param phase the <code>phase</code> of translation: 0, 1, 2, or <code>null</code> * @return */ private <T extends TranscriptRegion> T createRegion(Class<T> componentClass, String componentUniqueName, int fmin, int fmax, Integer phase) { FeatureLoc ourLoc = getRankZeroFeatureLoc(); if (fmin < ourLoc.getFmin()) { logger.debug(String.format("[%s] The %s start (%d) is before the transcript start (%d). Resetting transcript start", getUniqueName(), componentClass.getSimpleName(), fmin, ourLoc.getFmin())); lowerFminTo(fmin); } if (fmax > ourLoc.getFmax()) { logger.debug(String.format("[%s] The %s end (%d) is after the transcript end (%d). Resetting transcript end", getUniqueName(), componentClass.getSimpleName(), fmax, ourLoc.getFmax())); raiseFmaxTo(fmax); } int relativeFmin = fmin - getFmin(); int relativeFmax = fmax - getFmin(); T region = TranscriptRegion.construct(componentClass, this.getOrganism(), componentUniqueName); for (FeatureLoc featureLoc: getFeatureLocs()) { Feature sourceFeature = featureLoc.getSourceFeature(); if (sourceFeature == null) { logger.error(String.format("Feature '%s' has a FeatureLoc (ID %d) with no source feature", getUniqueName(), featureLoc.getFeatureLocId())); } else { sourceFeature.addLocatedChild(region, featureLoc.getFmin() + relativeFmin, featureLoc.getFmin() + relativeFmax, featureLoc.getStrand(), phase, featureLoc.getLocGroup(), featureLoc.getRank()); } } addRegion(region); return region; } /** * Move the lower bound of this feature leftwards. The new location is * specified relative to the primary source feature, but all <code>FeatureLoc</code>s * will be updated by the same relative amount. * * On a transcript, we also update the <code>fmin</code> of the associated gene, * if necessary. * * @param fmin the new <code>fmin</code> relative to the primary location */ @Override public void lowerFminTo(int fmin) { super.lowerFminTo(fmin); getGene().lowerFminTo(fmin); } /** * Move the upper bound of this feature rightwards. The new location is * specified relative to the primary source feature, but all <code>FeatureLoc</code>s * will be updated by the same relative amount. * * On a transcript, we also update the <code>fmax</code> of the associated gene, * if necessary. * * @param fmax the new <code>fmax</code>, relative to the primary location */ @Override public void raiseFmaxTo(int fmax) { super.raiseFmaxTo(fmax); getGene().raiseFmaxTo(fmax); } /** * Add an (already-created) region to this transcript. * * @param exon */ void addRegion(TranscriptRegion region) { this.addFeatureRelationship(region, "relationship", "part_of"); } }