package org.flymine.bj.bio;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import org.apache.log4j.Logger;
import org.biojava.bio.BioException;
import org.biojava.bio.seq.AbstractFeatureHolder;
import org.biojava.bio.seq.Feature;
import org.biojava.bio.seq.FeatureFilter;
import org.biojava.bio.seq.FeatureHolder;
import org.biojava.bio.seq.FilterUtils;
import org.biojava.bio.seq.StrandedFeature;
import org.biojava.utils.walker.Walker;
import org.biojava.utils.walker.WalkerFactory;
import org.flymine.bj.exceptions.ModelExceptionFM;
import org.flymine.bj.query.QueryFM;
import org.flymine.bj.query.VisitorSeq;
import org.flymine.model.genomic.BioEntity;
import org.intermine.objectstore.query.Query;
/**
* Implementation of a FlyMine (FM) specific BioJava FeatureHolder
*
* @author Markus Brosch
*/
public class FeatureHolderFM extends AbstractFeatureHolder implements FeatureHolder {
// =======================================================================
// Attributes
// =======================================================================
/**
* logger
*/
private static Logger _log = Logger.getLogger(FeatureHolderFM.class);
/**
* the source/root SequenceFM
*/
private final SequenceFM _sourceSeq; //required
/**
* if this FeatureHolder holds _all_ features of a chromosome, this flag ist true;
* if this FeatureHolder is a result of a filter and a subset of features of a chromosome, this
* flag is false
*/
private final boolean _isAllFeatures;
/**
* chromosome ID which represents the _sourceSeq; can be extracted from sourceSeq, but it is
* called many times, therefore a attribute is
*/
private final Integer _chromosomeID;
/**
* ID's of Features (BioEntities) represented by this FeatureHolder
*/
private Collection _ids;
/**
* Set of FeatureFM Objects which are held by this FeatureHolder
*/
private Collection _features = null;
// =======================================================================
// Constructors
// =======================================================================
/**
* Constructor
*
* @param pSourceSeq
* represents the source / root Sequence (FlyMine Chromosome). All operations are based
* on this underlying sequence. Therefore, all Features of this sequence are taken into
* account. If you want to build a subset of features, use the extended constructor and
* pass in the ids of these features
*/
public FeatureHolderFM(final SequenceFM pSourceSeq) {
this(pSourceSeq, null);
}
/**
* Constructor
*
* @param pSourceSeq
* represents the source / root Sequence (FlyMine Chromosome).
* @param pIds
* the IDs of Features (FlyMine BioEntities) this FeatureHolder holds;
* If IDs are provided, all operations and methods only work on this subset of features
* according to the ID set.
*/
public FeatureHolderFM(final SequenceFM pSourceSeq, final Set pIds) {
if (pSourceSeq == null) { throw new NullPointerException("SequenceFM must not be null"); }
_sourceSeq = pSourceSeq;
_chromosomeID = pSourceSeq.getBioEntityID();
_ids = pIds;
if (pIds == null) {
_isAllFeatures = true;
} else {
_isAllFeatures = false;
}
if (_log.isDebugEnabled()) {
_log.debug("constructor():");
_log.debug("\t sourceSequence was set; name: " + _sourceSeq.getName());
_log.debug("\t chromosomeID was set: " + _chromosomeID);
if (_isAllFeatures) {
_log.debug("constructor(): ids=null; => FH ~> whole Chromosome ");
} else {
_log.debug("\t : ids were set; => FH for Features with these IDs");
}
}
}
// =======================================================================
// Implements FeatureHolder interface
// =======================================================================
/**
* Count how many features (BioEntities) are contained on this Sequence (Chromosome)
*
* @return a positive integer or zero, equal to the number of features contained
*/
public final int countFeatures() {
if (_ids != null) {
_log.info("countFeatures(): ids.size();");
return _ids.size();
}
if (_features != null) {
_log.info("countFeatures(): features.size();");
return _features.size();
}
final QueryFM q = new QueryFM(_chromosomeID);
try {
_log.info("countFeatures(): q.countResult(q.byClass(BioEntity.class));");
return q.countResult(q.byClass(BioEntity.class));
} catch (ModelExceptionFM e) {
throw new RuntimeException("Not a BioEntity", e);
}
}
/**
* @see FeatureHolder#features()
*/
public final Iterator features() {
return getFeatures().iterator();
}
/**
* @param pFilter
* Your FeatureFilter. Be aware, that this implementation does not provide any hierarchy,
* therefore the Filters: FeatureFilter.ByChild(), FeatureFilter.ByParent(),
* FeatureFilter.ByAncestor() are returning an empty FeatureHolder.
* @param pRecurse
* you can set it true or false, in both cases it will not change the result as we don't
* provide a hierarchy in this implementation.
* @return FeatureFM Objects in a FeatureHolder which fulfil the filter ff
* @deprecated
* @see FeatureHolder#filter(org.biojava.bio.seq.FeatureFilter, boolean)
*/
public final FeatureHolder filter(final FeatureFilter pFilter, final boolean pRecurse) {
return filter(pFilter, false);
}
/**
* @param pFilter
* Your FeatureFilter. Be aware, that this implementation does not provide any hierarchy,
* therefore the Filters: FeatureFilter.ByChild(), FeatureFilter.ByParent(),
* FeatureFilter.ByAncestor() are returning an empty FeatureHolder.
* @return FeatureFM Objects in a FeatureHolder which fulfil the filter ff
* @see FeatureHolder#filter(org.biojava.bio.seq.FeatureFilter)
*/
public final FeatureHolder filter(final FeatureFilter pFilter) {
_log.info("filter(filter):");
//IF in memory processing
if (_sourceSeq.isMemoryMode()) {
_log.info("\t isMemoryMode ~> super.filter(filter, false);");
//note: features() is called in super.filter()
return super.filter(pFilter, false);
}
//IF on the fly processing / non memory mode: query dynamically
final QueryFM q = new QueryFM(_chromosomeID);
//check if features of featureFilter tree are disjoint
final FeatureFilter childF = new FeatureFilter.Not(FeatureFilter.top_level);
if (FilterUtils.areDisjoint(pFilter, childF)) { return FeatureHolder.EMPTY_FEATURE_HOLDER; }
//get a visitor & walk the filter tree, get the generated Query & return FH with resulting IDs
final VisitorSeq visitor = new VisitorSeq(q);
final WalkerFactory wf = WalkerFactory.getInstance();
_log.info("\t !isMemoryMode ~> walker / visitor used");
try {
final Walker walker = wf.getWalker(visitor);
walker.walk(pFilter, visitor);
Query query = (Query) walker.getValue();
_log.info("\t walker.getValue() -> query -> \n" + query.toString());
assert (query != null) : "something went wrong by walking the feature filter tree";
if (_ids != null && !_isAllFeatures) {
query = q.constrainQueryByIDs(query, _ids); //constrain by given IDs
_log.info("\t constrained query: \n" + query.toString());
}
final Set resultIDs = q.getIds(query);
_log.info("\t return new FeatureHolderFM(_sourceSeq, resulIds of query)");
return new FeatureHolderFM(_sourceSeq, resultIDs);
} catch (BioException e) {
_log.fatal("Problem getting the walker from WalkerFactory" + e.getMessage());
throw new RuntimeException("Problem getting the walker from WalkerFactory", e);
}
}
/**
* @see FeatureHolder#containsFeature(org.biojava.bio.seq.Feature)
*/
public final boolean containsFeature(final Feature pFeature) {
if (pFeature instanceof FeatureFM) { return getFeatures().contains(pFeature); }
return false; //FeatureHolderFM can only hold instances of FeatureFM
}
/**
* @see FeatureHolder#getSchema()
*/
public final FeatureFilter getSchema() {
return FeatureFilter.all;
}
// -----------------------------------------------------------------------
// private helpers for implementation of FeatureHolder
// -----------------------------------------------------------------------
/**
* method to get features of this featureHolder. If features are already allocated, simply return.
* Else: depending on the memory mode, create features and return. In memoryMode, the whole
* Feature object is instantiated, in non memory mode, it is lazily instantiated by only passing
* in the id (the Feature itself then can retrive it's data - once it is needed)
*
* @return an ArrayList of features
*/
private Collection getFeatures() {
_log.info("getFeatures()");
if (_features == null) {
_log.info("\t features == null");
final QueryFM q = new QueryFM(_chromosomeID);
_features = new HashSet();
if (_sourceSeq.isMemoryMode()) {
_log.info("\t isMemoryMode");
templatesFeatures(q); //memory mode - load all data
} else { //non memory mode - lazy instantiation, Features keep only ID
_log.info("\t !isMemoryMode");
if (_ids == null) {
_log.info("\t ids == null ~> query for ids");
try {
_ids = q.getIds(q.byClass(BioEntity.class));
} catch (ModelExceptionFM e) {
throw new RuntimeException("Not a BioEntity", e);
}
}
assert (_ids != null);
_log.info("\t iterate over ids and add new lazy Features to features");
for (Iterator it = _ids.iterator(); it.hasNext();) {
final Integer id = (Integer) it.next();
_features.add(new FeatureFM(_sourceSeq, id));
}
} //else not in memory
} //if featuresAllocated
return _features;
}
/**
* part of getFeatures() - helper method
*
* @param pQueryFM
* the QueryFM of getFeatures()
*/
private void templatesFeatures(final QueryFM pQueryFM) {
final Map map;
if (_isAllFeatures) {
_log.info("\t isAllFeatures");
//if this featureHolder has ALL features of the underlying chromosome
_log.info("\t get map [ID-Template], iterate and add !lazy new Features to features");
map = (pQueryFM.getTemplate(null)); //get ALL feature templates of this sequence
for (Iterator it = map.keySet().iterator(); it.hasNext();) {
final Integer id = (Integer) it.next();
final StrandedFeature.Template templ = (StrandedFeature.Template) map.get(id);
_features.add(new FeatureFM(_sourceSeq, id, templ));
}
} else { //if subset of all features
_log.info("\t !isAllFeaures");
map = new HashMap();
//if this featureHolder keeps only a subset of features of the underlying chromosome
final int packageSize = 10000; //chop ids to bunches of 10000's - avoid too complex sql query
_log.info("\t get.Templates of given IDs");
final Iterator iterateIds = _ids.iterator();
for (int i = 0; i < _ids.size(); i += packageSize) {
for (int j = i; j < (i + packageSize); j++) {
final Set bunchOfIds = new HashSet();
if (iterateIds.hasNext()) {
bunchOfIds.add(iterateIds.next());
} else {
break;
}
map.putAll(pQueryFM.getTemplate(bunchOfIds));
} //inner for
if (!iterateIds.hasNext()) {
break;
}
} //outer for
_log.info("\t iterate map [ID-Template] and add !lazy features to features");
for (Iterator it = map.keySet().iterator(); it.hasNext();) {
final Integer id = (Integer) it.next();
final StrandedFeature.Template templ = (StrandedFeature.Template) map.get(id);
_features.add(new FeatureFM(_sourceSeq, id, templ));
}
} //else
}
}