001package org.biojava.bio.seq.projection;
002
003
004
005import java.io.Serializable;
006import java.util.HashMap;
007import java.util.Iterator;
008import java.util.Map;
009
010import org.biojava.bio.BioException;
011import org.biojava.bio.BioRuntimeException;
012import org.biojava.bio.seq.Feature;
013import org.biojava.bio.seq.FeatureFilter;
014import org.biojava.bio.seq.FeatureHolder;
015import org.biojava.bio.seq.FilterUtils;
016import org.biojava.bio.seq.Sequence;
017import org.biojava.utils.ChangeEvent;
018import org.biojava.utils.ChangeListener;
019import org.biojava.utils.ChangeSupport;
020import org.biojava.utils.ChangeType;
021import org.biojava.utils.ChangeVetoException;
022import org.biojava.utils.Unchangeable;
023
024
025
026/**
027 * A good base class to implement ProjectionContext from.
028 *
029
030 * <p>
031
032 * ReparentContext takes care of much of the ugliness of implementing
033
034 * ProjectionContext, such as handling listeners and grafting features onto
035
036 * a new parent. It also sets up a framework for mutating feature filters.
037
038 * Think carefully before overriding methods in this class.
039
040 * </p>
041
042 *
043
044 * @author Matthew Pocock
045
046 * @author Thomas Down
047
048 */
049
050public class ReparentContext implements ProjectionContext, Serializable {
051        private transient final Map forwardersByFeature = new HashMap();
052        private final FeatureHolder parent;
053        private final FeatureHolder wrapped;
054
055
056
057        public ReparentContext(FeatureHolder parent,
058                        FeatureHolder wrapped) 
059        {
060                this.parent = parent;
061                this.wrapped = wrapped;
062        }
063
064        public final FeatureHolder getParent() {
065                return parent;
066        }
067
068        public final FeatureHolder getUnprojectedFeatures() {
069                return wrapped;
070        }
071
072        /**
073         * Create a single projected feature using the rules of this <code>ProjectedFeatureHolder</code>.
074         */
075
076        public Feature projectFeature(Feature feat) {
077                return ProjectionEngine.DEFAULT.projectFeature(feat, this);
078        }
079
080        public Feature revertFeature(Feature feat) {
081                return ((Projection) feat).getViewedFeature();
082        }
083
084        public final FeatureFilter projectFilter(FeatureFilter ff) {
085                return FilterUtils.transformFilter(ff, getTransformer());
086        }
087
088        public final FeatureFilter revertFilter(FeatureFilter ff) {
089                return FilterUtils.transformFilter(ff, getReverter());
090        }
091
092        protected FilterUtils.FilterTransformer getTransformer() {
093                return new FilterUtils.FilterTransformer() {
094                        public FeatureFilter transform(FeatureFilter ff) {
095                                // fixme: should we be mapping filters on feature instance or sequence
096                                // instance?
097
098                                return ff;
099                        }
100                };
101        }
102
103        protected FilterUtils.FilterTransformer getReverter() {
104                return new FilterUtils.FilterTransformer() {
105                        public FeatureFilter transform(FeatureFilter ff) {
106                                // fixme: should we be mapping filters on feature instance or sequence
107                                // instance?
108
109                                return ff;
110                        }
111                };
112        }
113
114        public final FeatureHolder getParent(Feature f) {
115                FeatureHolder oldP = f.getParent();
116                if (oldP instanceof Feature) {
117                        if (wrapped.containsFeature(f)) {
118                                return parent;
119                        } else {
120                                return projectFeature((Feature) oldP);
121                        }
122                } else {
123                        return parent;
124                }
125        }
126
127        public final Sequence getSequence(Feature f) {
128                FeatureHolder fh = getParent();
129                while (fh instanceof Feature) {
130                        fh = ((Feature) fh).getParent();
131                }
132
133                if (! (fh instanceof Sequence)) {
134                        throw new BioRuntimeException("Chasing up parents to get sequence: actually hit: " + fh.toString());
135                }
136
137                return (Sequence) fh;
138        }
139
140        public FeatureHolder projectChildFeatures(Feature f, FeatureHolder parent) {
141                return new ProjectionSet(f);
142        }
143
144        public final Feature createFeature(Feature.Template projTempl)
145        throws BioException, ChangeVetoException
146        {
147
148                Feature f = null;
149
150                synchronized(wrapped){
151                        f = wrapped.createFeature(
152                                        ProjectionEngine.DEFAULT.revertTemplate(projTempl, this));
153                }
154
155                f = projectFeature(f);
156
157
158
159
160
161                return f;
162
163
164        }
165        public final void removeFeature(Feature dyingChild)
166        throws BioException, ChangeVetoException 
167        {
168                wrapped.removeFeature(revertFeature(dyingChild));
169        }
170
171        public final Feature createFeature(Feature f, Feature.Template projTempl)
172        throws BioException, ChangeVetoException
173        {
174
175
176
177                Feature f1 = null;
178                synchronized (f) {
179                        f1 = revertFeature(f);
180                }
181
182                Feature f2 = null;
183                synchronized(f1){
184                        f2 = f1.createFeature(ProjectionEngine.DEFAULT.revertTemplate(projTempl, this));
185                }
186
187                Feature newF = null;
188
189                synchronized (f2) {
190                        newF = projectFeature(f2);
191                }
192
193                return newF;
194
195
196        }
197
198        public final void removeFeature(Feature f, Feature f2)
199        throws ChangeVetoException, BioException 
200        {
201                revertFeature(f).removeFeature(revertFeature(f2));
202        }
203
204        public final FeatureFilter getSchema(Feature f) {
205                return projectFilter(f.getSchema());
206        }
207
208
209
210        public final void addChangeListener(Feature f, ChangeListener cl, ChangeType ct) {
211                if (!f.isUnchanging(ct)) {
212                        PFChangeForwarder forwarder = (PFChangeForwarder) forwardersByFeature.get(f);
213                        if (forwarder == null) {
214                                forwarder = new PFChangeForwarder(f);
215                                forwardersByFeature.put(f, forwarder);
216                                f.addChangeListener(forwarder, ChangeType.UNKNOWN);
217                        }
218
219                        forwarder.addChangeListener(cl, ct);
220                }
221        }
222
223        public final void removeChangeListener(Feature f, ChangeListener cl, ChangeType ct) {
224                PFChangeForwarder forwarder = (PFChangeForwarder) forwardersByFeature.get(f);
225                if (forwarder != null) {
226                        forwarder.removeChangeListener(cl, ct);
227                        if (!forwarder.hasListeners()) {
228                                forwardersByFeature.remove(f);
229                                f.removeChangeListener(forwarder, ChangeType.UNKNOWN);
230                        }
231                }
232        }
233
234        public final FeatureHolder projectFeatures(FeatureHolder fh) {
235                return new ProjectionSet(fh);
236        }
237
238        //
239        // Dumb set of features to which we delegate everything except the
240        // ChangeEvent stuff.
241        //
242
243        private class ProjectionSet
244        extends Unchangeable
245        implements FeatureHolder, Serializable
246        {
247                private final FeatureHolder baseSet;
248
249                ProjectionSet(FeatureHolder baseSet) {
250                        this.baseSet = baseSet;
251                }
252
253                public int countFeatures() {
254                        return baseSet.countFeatures();
255                }
256
257                public Iterator features() {
258                        final Iterator wrappedIterator = baseSet.features();
259
260                        return new Iterator() {
261                                public boolean hasNext() {
262                                        return wrappedIterator.hasNext();
263                                }
264
265                                public Object next() {
266                                        return projectFeature((Feature) wrappedIterator.next());
267                                }
268
269                                public void remove() {
270                                        throw new UnsupportedOperationException();
271                                }
272                        };
273                }
274
275                public boolean containsFeature(Feature f) { 
276                        if (! (f instanceof Projection)) {
277                                return false;
278                        } else {
279                                Projection p = (Projection) f;
280                                return p.getProjectionContext() == ReparentContext.this && baseSet.containsFeature(p.getViewedFeature());
281                        }
282                }
283
284                public FeatureHolder filter(FeatureFilter ff) {
285                        return filter(ff, true); // bit of a hack for now.
286                }
287
288                public FeatureHolder filter(FeatureFilter ff, boolean recurse) {
289                        ff = revertFilter(ff);
290                        FeatureHolder toProject = baseSet.filter(ff, recurse);
291                        return new ProjectionSet(toProject);
292                }
293
294                public Feature createFeature(Feature.Template templ)
295                throws ChangeVetoException, BioException 
296                {
297                        throw new ChangeVetoException("Can't create features in this projection");
298                }
299
300                public void removeFeature(Feature toDie)
301                throws ChangeVetoException, BioException 
302                {
303                        throw new ChangeVetoException("Can't remove features in this projection");
304                }
305
306                public FeatureFilter getSchema() {
307                        return projectFilter(baseSet.getSchema());
308                }
309        }
310
311        private class PFChangeForwarder
312        extends ChangeSupport
313        implements ChangeListener
314        {
315                private Feature master;
316
317                public PFChangeForwarder(Feature master) {
318                        super(1);
319                        this.master = master;
320                }
321
322                public void preChange(ChangeEvent cev)
323                throws ChangeVetoException 
324                {
325                        ChangeEvent cev2 = forwardFeatureChangeEvent(master, cev);
326                        if (cev2 != null) {
327                                synchronized (cev2) {
328                                        firePreChangeEvent(cev2);
329                                }
330
331                        }
332                }
333
334                public void postChange(ChangeEvent cev) {
335                        ChangeEvent cev2 = forwardFeatureChangeEvent(master, cev);
336                        if (cev2 != null) {
337                                synchronized (cev2) {
338                                        firePostChangeEvent(cev2);
339                                }
340                        }
341                }
342
343                /**
344                 * Called internally to generate a forwarded version of a ChangeEvent from a ProjectedFeature
345                 *
346                 * @param f the feature who's projection is due to receive an event.
347                 * @return a tranformed event, or <code>null</code> to cancel the event.
348                 */
349
350                protected ChangeEvent forwardFeatureChangeEvent(Feature f, ChangeEvent cev) {
351                        return new ChangeEvent(projectFeature(f),
352                                        cev.getType(),
353                                        cev.getChange(),
354                                        cev.getPrevious(),
355                                        cev);
356
357                }
358        }
359}
360