001/*
002 *                    BioJava development code
003 *
004 * This code may be freely distributed and modified under the
005 * terms of the GNU Lesser General Public Licence.  This should
006 * be distributed with the code.  If you do not have a copy,
007 * see:
008 *
009 *      http://www.gnu.org/copyleft/lesser.html
010 *
011 * Copyright for this code is held jointly by the individual
012 * authors.  These should be listed in @author doc comments.
013 *
014 * For more information on the BioJava project and its aims,
015 * or to join the biojava-l mailing list, visit the home page
016 * at:
017 *
018 *      http://www.biojava.org/
019 *
020 */
021
022package org.biojava.bio.seq;
023
024import java.io.Serializable;
025import java.util.ArrayList;
026import java.util.Collections;
027import java.util.Iterator;
028import java.util.List;
029import java.util.NoSuchElementException;
030
031import org.biojava.utils.ChangeEvent;
032import org.biojava.utils.ChangeSupport;
033import org.biojava.utils.ChangeVetoException;
034
035/**
036 * FeatureHolder which exposes all the features in a set
037 * of sub-FeatureHolders.  This is provided primarily as
038 * a support class for ViewSequence.  It may also be useful
039 * for other applications, such as simple distributed
040 * annotation systems.
041 *
042 * @author Thomas Down
043 * @author Matthew Pocock
044 * @see org.biojavax.bio.seq.RichFeatureRelationshipHolder
045 */
046
047public class MergeFeatureHolder extends AbstractFeatureHolder
048  implements Serializable{
049
050    private List featureHolders;
051
052    /**
053     * Create a new, empty, MergeFeatureHolder.
054     */
055
056    public MergeFeatureHolder() {
057        featureHolders = new ArrayList();
058    }
059
060    /**
061     * Create a populated MFH
062     */
063
064    private MergeFeatureHolder(List m) {
065        featureHolders = m;
066    }
067
068    /**
069     * Add an extra FeatureHolder to the set of FeatureHolders which
070     * are merged.  This method is provided for backward compatibility,
071     * and is equivalent to:
072     *
073     * <pre>
074     *     mfh.addFeatureHolder(fh, FeatureFilter.all);
075     * </pre>
076     *
077     * <p>
078     * You should always use the two-arg version in preference if you
079     * are able to define the membership of a FeatureHolder.
080     * </p>
081     */
082
083    public void addFeatureHolder(FeatureHolder fh)
084        throws ChangeVetoException
085    {
086        if(!hasListeners()) {
087            featureHolders.add(fh);
088        } else {
089            ChangeSupport changeSupport = getChangeSupport(FeatureHolder.FEATURES);
090            synchronized(changeSupport) {
091                ChangeEvent ce = new ChangeEvent(this, FeatureHolder.FEATURES);
092                changeSupport.firePreChangeEvent(ce);
093                featureHolders.add(fh);
094                changeSupport.firePostChangeEvent(ce);
095            }
096       }
097    }
098
099    /**
100     * Remove a FeatureHolder from the set of FeatureHolders which
101     * are merged.
102     */
103
104     public void removeFeatureHolder(FeatureHolder fh)
105     throws ChangeVetoException
106     {
107       if(!hasListeners()) {
108         featureHolders.remove(fh);
109       } else {
110         ChangeSupport changeSupport = getChangeSupport(FeatureHolder.FEATURES);
111         synchronized(changeSupport) {
112           ChangeEvent ce = new ChangeEvent(this, FeatureHolder.FEATURES);
113           changeSupport.firePreChangeEvent(ce);
114           featureHolders.remove(fh);
115           changeSupport.firePostChangeEvent(ce);
116         }
117       }
118     }
119
120    public int countFeatures() {
121        int fc = 0;
122        for (Iterator i = featureHolders.iterator(); i.hasNext(); ) {
123            fc += ((FeatureHolder) i.next()).countFeatures();
124        }
125        return fc;
126    }
127
128    public boolean containsFeature(Feature f) {
129        for (Iterator i = featureHolders.iterator(); i.hasNext(); ) {
130            FeatureHolder subFH = (FeatureHolder) i.next();
131            FeatureFilter membership = subFH.getSchema();
132
133            if (membership.accept(f)) {
134                if(subFH.containsFeature(f)) {
135                    return true;
136                }
137            }
138        }
139
140        return false;
141    }
142
143    /**
144     * Iterate over all the features in all child FeatureHolders.
145     * The Iterator may throw ConcurrantModificationException if
146     * there is a change in the underlying collections during
147     * iteration.
148     */
149
150    public Iterator features() {
151        return new MFHIterator();
152    }
153
154    /**
155     * When applied to a MergeFeatureHolder, this filters each child
156     * FeatureHolder independently.
157     */
158
159    public FeatureHolder filter(FeatureFilter ff, boolean recurse) {
160        List results = new ArrayList();
161        for (Iterator fhi = featureHolders.iterator(); fhi.hasNext(); ) {
162            FeatureHolder fh = (FeatureHolder) fhi.next();
163            FeatureFilter mf = fh.getSchema();
164            if (recurse && !FilterUtils.areProperSubset(mf, FeatureFilter.leaf)) {
165                if (FilterUtils.areDisjoint(new FeatureFilter.Or(mf, new FeatureFilter.ByAncestor(mf)),
166                                            ff))
167                {
168                    continue;
169                }
170            } else {
171                if (FilterUtils.areDisjoint(mf, ff)) {
172                    continue;
173                }
174            }
175
176            FeatureHolder filterResult = fh.filter(ff, recurse);
177            results.add(filterResult);
178        }
179
180        if (results.size() == 0) {
181            return FeatureHolder.EMPTY_FEATURE_HOLDER;
182        } else if (results.size() == 1) {
183            return (FeatureHolder) results.get(0);
184        } else {
185            return new MergeFeatureHolder(results);
186        }
187    }
188
189    public FeatureFilter getSchema() {
190        FeatureFilter[] filters = new FeatureFilter[featureHolders.size()];
191        for (int i = 0; i < filters.length; ++i) {
192            filters[i] = ((FeatureHolder) featureHolders.get(i)).getSchema();
193        }
194        return FilterUtils.or(filters);
195    }
196
197    private class MFHIterator implements Iterator {
198        private Iterator fhIterator;
199        private Iterator fIterator;
200
201        public MFHIterator() {
202            fhIterator = featureHolders.iterator();
203            if (fhIterator.hasNext())
204                fIterator = ((FeatureHolder) fhIterator.next()).features();
205            else
206                fIterator = Collections.EMPTY_SET.iterator();
207        }
208
209        public boolean hasNext() {
210            if (fIterator.hasNext())
211                return true;
212            if (fhIterator.hasNext()) {
213                fIterator = ((FeatureHolder) fhIterator.next()).features();
214                return hasNext();
215            }
216            return false;
217        }
218
219        public Object next() {
220            if (fIterator.hasNext())
221                return fIterator.next();
222            if (fhIterator.hasNext()) {
223                fIterator = ((FeatureHolder) fhIterator.next()).features();
224                return next();
225            }
226            throw new NoSuchElementException();
227        }
228
229        public void remove() {
230            throw new UnsupportedOperationException();
231        }
232    }
233}