/* * BioJava development code * * This code may be freely distributed and modified under the * terms of the GNU Lesser General Public Licence. This should * be distributed with the code. If you do not have a copy, * see: * * http://www.gnu.org/copyleft/lesser.html * * Copyright for this code is held jointly by the individual * authors. These should be listed in @author doc comments. * * For more information on the BioJava project and its aims, * or to join the biojava-l mailing list, visit the home page * at: * * http://www.biojava.org/ * */ package org.biojavax.bio.seq; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeSet; import org.biojava.bio.Annotation; import org.biojava.bio.symbol.IllegalSymbolException; import org.biojava.bio.symbol.Location; import org.biojava.bio.symbol.SimpleSymbolList; import org.biojava.bio.symbol.SymbolList; import org.biojava.utils.ChangeVetoException; import org.biojavax.CrossReferenceResolver; import org.biojavax.RichAnnotation; import org.biojavax.RichObjectFactory; import org.biojavax.ontology.ComparableTerm; /** * An implementation of RichLocation which covers multiple locations, * but on the same strand of the same (optionally circular) sequence. * @author Richard Holland * @author Mark Schreiber * @since 1.5 */ public class CompoundRichLocation extends SimpleRichLocation implements RichLocation { protected List members; protected int size = 0; /** * Getter for the "join" term * @return the "join" term */ public static ComparableTerm getJoinTerm() { return RichObjectFactory.getDefaultOntology().getOrCreateTerm("join"); } /** * Getter for the "order" term * @return the "order" term */ public static ComparableTerm getOrderTerm() { return RichObjectFactory.getDefaultOntology().getOrCreateTerm("order"); } /** * Constructs a CompoundRichLocation from the given set of members, with * the default term of "join". Note that you really shouldn't use this if * you are unsure if your members set contains overlapping members. Use * RichLocation.Tools.construct() instead. The members collection * must only contain Location instances. Any that are not RichLocations will * be converted using RichLocation.Tools.enrich(). All members must come from * the same strand of the same sequence with the same circular length. * @param members the members to put into the compound location. * @see RichLocation.Tools */ public CompoundRichLocation(Collection members) { this(getJoinTerm(), members); } /** * Constructs a CompoundRichLocation from the given set of members. * Note that you really shouldn't use this if * you are unsure if your members set contains overlapping members. Use * RichLocation.Tools.construct(members) instead. The members collection * must only contain Location instances. Any that are not RichLocations will * be converted using RichLocation.Tools.enrich(). * @param term the term to use when describing the group of members. * @param members the members to put into the compound location. * @see RichLocation.Tools */ public CompoundRichLocation(ComparableTerm term, Collection members) { if (term == null) { throw new IllegalArgumentException("Term cannot be null"); } if (members == null || members.size() < 2) { throw new IllegalArgumentException("Must have at least two members"); } if (RichLocation.Tools.isMultiSource(members)) { throw new IllegalArgumentException("All members must be from the same source"); } this.term = term; this.members = new ArrayList(); for (Iterator i = members.iterator(); i.hasNext();) { // Convert each member into a RichLocation Object o = i.next(); if (!(o instanceof RichLocation)) { o = RichLocation.Tools.enrich((Location) o); } // Convert RichLocation rl = (RichLocation) o; // Add in member this.members.add(rl); // Update our cross ref this.setCrossRef(rl.getCrossRef()); // Update our circular length this.circularLength = rl.getCircularLength(); // Update our strand this.setStrand(rl.getStrand()); // Update our size and min/max this.size += rl.getMax() - rl.getMin(); if (this.getMinPosition() == null) { this.setMinPosition(rl.getMinPosition()); } else { this.setMinPosition(this.posmin(this.getMinPosition(), rl.getMinPosition())); } if (this.getMaxPosition() == null) { this.setMaxPosition(rl.getMaxPosition()); } else { this.setMaxPosition(this.posmax(this.getMaxPosition(), rl.getMaxPosition())); } } } // for internal use only protected CompoundRichLocation() { } /** * {@inheritDoc} */ public void sort() { Collections.sort(this.members); } /** * {@inheritDoc} * Passes the call on to each of its members in turn. */ public void setFeature(RichFeature feature) throws ChangeVetoException { super.setFeature(feature); for (Iterator i = this.members.iterator(); i.hasNext();) { ((RichLocation) i.next()).setFeature(feature); } } /** * {@inheritDoc} * ALWAYS RETURNS THE EMPTY ANNOTATION */ public Annotation getAnnotation() { return getRichAnnotation(); } /** * {@inheritDoc} * ALWAYS RETURNS THE EMPTY ANNOTATION */ public RichAnnotation getRichAnnotation() { return RichAnnotation.EMPTY_ANNOTATION; } /** * {@inheritDoc} * ALWAYS RETURNS THE EMPTY ANNOTATION NOTE SET */ public Set getNoteSet() { return RichAnnotation.EMPTY_ANNOTATION.getNoteSet(); } /** * {@inheritDoc} * NOT IMPLEMENTED * @throws ChangeVetoException ALWAYS */ public void setNoteSet(Set notes) throws ChangeVetoException { throw new ChangeVetoException("Cannot annotate compound locations."); } /** * {@inheritDoc} * RECURSIVELY APPLIES CALL TO ALL MEMBERS */ public void setCircularLength(int sourceSeqLength) throws ChangeVetoException { super.setCircularLength(sourceSeqLength); for (Iterator i = this.members.iterator(); i.hasNext();) { ((RichLocation) i.next()).setCircularLength(sourceSeqLength); } } /** * {@inheritDoc} */ public Iterator blockIterator() { final List sortedMembers = new ArrayList(this.members); Collections.sort(sortedMembers); return sortedMembers.iterator(); } /** * {@inheritDoc} * ALWAYS RETURNS FALSE */ public boolean isContiguous() { return false; } /** * {@inheritDoc} * Recursively applies this call to all members. */ public boolean contains(int p) { for (Iterator i = this.members.iterator(); i.hasNext();) { if (((RichLocation) i.next()).contains(p)) { return true; } } return false; } /** * {@inheritDoc} * ALWAYS RETURNS NULL */ public Location getDecorator(Class decoratorClass) { return null; } /** * {@inheritDoc} * ALWAYS RETURNS SELF */ public Location newInstance(Location loc) { return loc; } /** * {@inheritDoc} * Recursively translates all members of this location. */ public Location translate(int dist) { if (this.members.isEmpty()) { return this; } List newmembers = new ArrayList(); for (Iterator i = this.members.iterator(); i.hasNext();) { RichLocation rl = (RichLocation) i.next(); newmembers.add(rl.translate(dist)); } return new CompoundRichLocation(this.getTerm(), newmembers); } /** * {@inheritDoc} * Recursively applies this call to all members. If passed a Location * which is not a RichLocation, it converts it first using * RichLocation.Tools.enrich(). * @see RichLocation.Tools * @return true if an only if one of the members of this Location * wholey contains l. */ public boolean contains(Location l) { if (!(l instanceof RichLocation)) { l = RichLocation.Tools.enrich(l); } if (l instanceof EmptyRichLocation) { return l.contains(this); // let them do the hard work! } else { RichLocation rl = (RichLocation) l; if (rl instanceof CompoundRichLocation) { CompoundRichLocation crl = (CompoundRichLocation) rl; Map matches = new HashMap(); for (Iterator i = crl.members.iterator(); i.hasNext();) { matches.put(i.next(), Boolean.FALSE); } for (Iterator i = this.members.iterator(); i.hasNext();) { RichLocation member = (RichLocation) i.next(); for (Iterator j = matches.entrySet().iterator(); j.hasNext();) { Map.Entry entry = (Map.Entry)j.next(); if (entry.getValue().equals(Boolean.TRUE)) { continue; } RichLocation match = (RichLocation) entry.getKey(); if (member.contains(match)) { entry.setValue(Boolean.TRUE); } } } for (Iterator i = matches.values().iterator(); i.hasNext(); ) { if (i.next().equals(Boolean.FALSE)) { return false; } } return true; } else { for (Iterator i = this.members.iterator(); i.hasNext();) { if (((RichLocation) i.next()).contains(rl)) { return true; } } } return false; } } /** * {@inheritDoc} * Recursively applies this call to all members. * @return true if and only if at least on of the members overlaps l */ public boolean overlaps(Location l) { for (Iterator i = this.members.iterator(); i.hasNext();) { RichLocation rl = (RichLocation) i.next(); if (rl.overlaps(l)) { return true; } } return false; } /** * {@inheritDoc} * If passed a Location which is not a RichLocation, it converts it first * using RichLocation.Tools.enrich(). * The resulting location may or may not be a compound location. If it is * a compound location, its contents will be a set of simple locations. * Regions that overlap will be merged into a single location. * @see RichLocation.Tools * @return a CompoundLocation if the components of the union * cannot be merged else a SimpleRichLocation */ public Location union(Location l) { if (!(l instanceof RichLocation)) { l = RichLocation.Tools.enrich(l); } if (l instanceof EmptyRichLocation) { return this; } else { // Easy - construct a new location based on the members of both // ourselves and the location passed as a parameter List members = new ArrayList(); members.addAll(RichLocation.Tools.flatten(this)); members.addAll(RichLocation.Tools.flatten((RichLocation) l)); return RichLocation.Tools.construct(RichLocation.Tools.merge(members)); } } /** * {@inheritDoc} * If passed a Location which is not a RichLocation, it converts it first * using RichLocation.Tools.enrich(). * The resulting location may or may not be a compound location. If it is * a compound location, its contents will be a set of simple locations. * @return a CompoundLocation if there is more than one region * of intersection that cannot be merged. Else a SimpleRichLocation. */ public Location intersection(Location l) { if (!(l instanceof RichLocation)) { l = RichLocation.Tools.enrich(l); } if (l instanceof EmptyRichLocation) { return l; } else if (l instanceof CompoundRichLocation) { Collection theirMembers = RichLocation.Tools.flatten((RichLocation) l); // For every member of the location passed as a parameter, intersect // with ourselves. Then construct a new location from the // results of all the intersections. Set results = new TreeSet(); for (Iterator i = theirMembers.iterator(); i.hasNext();) { RichLocation member = (RichLocation) i.next(); results.add(this.intersection(member)); } return RichLocation.Tools.construct(RichLocation.Tools.merge(results)); } else { // Simple vs. ourselves // For every member of ourselves, intersect with the location // passed as a parameter. Then construct a new location from the // results of all the intersections. Set results = new TreeSet(); for (Iterator i = this.members.iterator(); i.hasNext();) { RichLocation member = (RichLocation) i.next(); results.add(member.intersection(l)); } return RichLocation.Tools.construct(RichLocation.Tools.merge(results)); } } /** * {@inheritDoc} * Recursively applies this call to all members. */ public void setCrossRefResolver(CrossReferenceResolver r) { for (Iterator i = this.members.iterator(); i.hasNext();) { ((RichLocation) i.next()).setCrossRefResolver(r); } } /** * {@inheritDoc} * This function concatenates the symbols of all its child locations. *

* The most obvious application of this method to a CompoundRichLocation * is the contatenation of the components of a gene with multiple exons. */ public SymbolList symbols(SymbolList seq) { if (seq == null) { throw new IllegalArgumentException("Sequence cannot be null"); } List res = new ArrayList(); for (Iterator i = this.members.iterator(); i.hasNext();) { RichLocation l = (RichLocation) i.next(); res.addAll(l.symbols(seq).toList()); } try { return new SimpleSymbolList(seq.getAlphabet(), res); } catch (IllegalSymbolException ex) { throw new RuntimeException("Could not build compound sequence string", ex); } } /** * {@inheritDoc} */ public void setTerm(ComparableTerm term) throws ChangeVetoException { if (term == null) { throw new ChangeVetoException("Cannot set term to null"); } super.setTerm(term); } /** * {@inheritDoc} */ public int hashCode() { int code = 17; code = 31 * code + this.getTerm().hashCode(); for (Iterator i = this.members.iterator(); i.hasNext();) { code = 31 * i.next().hashCode(); } return code; } /** * {@inheritDoc} * Compound locations are only equal to other Locations if all their * components are equal. */ public boolean equals(Object o) { if (o == this) { return true; } if (!(o instanceof Location)) { return false; } Location them = (Location) o; if (them.isContiguous()) { return false; } //because this is not! // ok - both compound. The blocks returned from blockIterator should each be // equivalent. Iterator i1 = this.blockIterator(); Iterator i2 = them.blockIterator(); // while there are more pairs to check... while (i1.hasNext() && i2.hasNext()) { // check that this pair is equivalent Location l1 = (Location) i1.next(); Location l2 = (Location) i2.next(); if (!(l1.equals(l2))) // not equivalent blocks so not equal { return false; } } if (i1.hasNext() || i2.hasNext()) { // One of the locations had more blocks than the other return false; } // Same number of blocks, all equivalent. Must be equal. return true; } /** * {@inheritDoc} * */ public int compareTo(Object o) { Location fo = (Location) o; if (this.equals(fo)) { return 0; } else { return this.getMin() - fo.getMin(); } } /** * {@inheritDoc} * Form: "term:[location,location...]" */ public String toString() { StringBuffer sb = new StringBuffer(); sb.append(this.getTerm()); sb.append(":["); for (Iterator i = this.blockIterator(); i.hasNext();) { sb.append(i.next()); if (i.hasNext()) { sb.append(","); } } sb.append("]"); return sb.toString(); } }