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.projection;
023
024import java.io.Serializable;
025import java.util.Iterator;
026
027import org.biojava.bio.BioException;
028import org.biojava.bio.seq.AbstractFeatureHolder;
029import org.biojava.bio.seq.Feature;
030import org.biojava.bio.seq.FeatureFilter;
031import org.biojava.bio.seq.FeatureHolder;
032import org.biojava.utils.ChangeEvent;
033import org.biojava.utils.ChangeListener;
034import org.biojava.utils.ChangeSupport;
035import org.biojava.utils.ChangeVetoException;
036
037/**
038 * Helper class for projecting Feature objects into an alternative
039 * coordinate system.  This class offers a view onto a set of features,
040 * projecting them into a different coordinate system, and also changing
041 * their <code>parent</code> property.  The destination coordinate system
042 * can run in the opposite direction from the source, in which case the
043 * <code>strand</code> property of StrandedFeatures is flipped.
044 *
045 * <p>
046 * The projected features returned by this class are small proxy objects.
047 * Proxy classes are autogenerated on demand for any sub-interface of
048 * <code>Feature</code> by the <code>ProjectionEngine</code> class.
049 * </p>
050 *
051 * @author Thomas Down
052 * @author Matthew Pocock
053 * @since 1.1
054 */
055
056public final class ProjectedFeatureHolder
057extends AbstractFeatureHolder
058implements FeatureHolder, Serializable {
059        private final ProjectionContext context;
060        private transient final ChangeListener underlyingFeaturesChange;
061        private FeatureHolder topLevelFeatures;
062
063        // don't ask why we need an initializer - blame it on the statics
064        {
065                underlyingFeaturesChange = new ChangeListener() {
066                        public void preChange(ChangeEvent e)
067                        throws ChangeVetoException
068                        {
069                                if (hasListeners()) {
070                                        ChangeEvent cev2 = forwardChangeEvent(e);
071                                        if (cev2 != null) {
072                                                ChangeSupport cs = getChangeSupport(FeatureHolder.FEATURES);
073                                                synchronized (cs){
074                                                        cs.firePreChangeEvent(cev2);
075
076                                                }
077                                        }
078                                }
079                        }
080
081                        public void postChange(ChangeEvent e) {
082                                if (hasListeners()) {
083                                        ChangeEvent cev2 = forwardChangeEvent(e);
084                                        if (cev2 != null) {
085                                                ChangeSupport cs =  getChangeSupport(FeatureHolder.FEATURES);
086                                                synchronized (cs){
087                                                        cs.firePostChangeEvent(cev2);
088                                                }
089                                        }
090                                }
091                        }
092                } ;
093        }
094
095        public ProjectedFeatureHolder(
096                        ProjectionContext context)
097        {
098                this.context = context;
099                context.getUnprojectedFeatures().addChangeListener(underlyingFeaturesChange);
100        }
101
102        public ProjectionContext getContext() {
103                return context;
104        }
105
106        private FeatureHolder getTopLevelFeatures() {
107                if (topLevelFeatures == null) {
108                        topLevelFeatures = makeProjectionSet(context.getUnprojectedFeatures());
109                }
110                return topLevelFeatures;
111        }
112
113        //
114        // Normal FeatureHolder methods get delegated to our top-level ProjectionSet
115        //
116
117        public Iterator features() {
118                return getTopLevelFeatures().features();
119        }
120
121        public int countFeatures() {
122                return getTopLevelFeatures().countFeatures();
123        }
124
125        public boolean containsFeature(Feature f) {
126                return getTopLevelFeatures().containsFeature(f);
127        }
128
129        public FeatureHolder filter(FeatureFilter ff) {
130                return getTopLevelFeatures().filter(ff);
131        }
132
133        public FeatureHolder filter(FeatureFilter ff, boolean recurse) {
134                return getTopLevelFeatures().filter(ff, recurse);
135        }
136
137        public Feature createFeature(Feature.Template templ)
138        throws ChangeVetoException, BioException
139        {
140
141                Feature f = null;
142                synchronized(context){
143                        f = context.createFeature(templ);
144                }         
145                return f;
146
147        }
148
149        public void removeFeature(Feature dyingChild)
150        throws ChangeVetoException, BioException
151        {
152                synchronized(context){
153                        context.removeFeature(dyingChild);
154                }
155        }
156
157        public FeatureFilter getSchema() {
158                return getTopLevelFeatures().getSchema();
159        }
160
161        /**
162         * Called internally to construct a lightweight projected view of a set of
163         * features
164         */
165
166        protected FeatureHolder makeProjectionSet(FeatureHolder fh) {
167                return context.projectFeatures(fh);
168        }
169
170        /**
171         * Called internally to generate a forwarded version of a ChangeEvent from our
172         * underlying FeatureHolder
173         */
174
175        protected ChangeEvent forwardChangeEvent(ChangeEvent cev) {
176                return new ChangeEvent(this,
177                                cev.getType(),
178                                cev.getChange(),
179                                cev.getPrevious(),
180                                cev);
181        }
182
183}