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.io.filterxml;
023
024import java.io.IOException;
025import java.lang.reflect.InvocationTargetException;
026import java.lang.reflect.Method;
027import java.util.HashMap;
028import java.util.Iterator;
029import java.util.Map;
030
031import org.biojava.bio.AnnotationType;
032import org.biojava.bio.BioError;
033import org.biojava.bio.seq.FeatureFilter;
034import org.biojava.bio.seq.FramedFeature;
035import org.biojava.bio.seq.StrandedFeature;
036import org.biojava.bio.symbol.Location;
037import org.biojava.utils.xml.XMLWriter;
038
039/**
040 * Write FeatureFilters in XML format.
041 *
042 * @author Thomas Down
043 * @since 1.3
044 */
045
046public class XMLFilterWriter {
047    public static final String XML_FILTER_NS = "http://www.biojava.org/FeatureFilter";
048    private static final Class[] NO_ARGS = new Class[0];
049    private static final Object[] NO_ARGS_VAL = new Object[0];
050
051    private Map filterWritersByClass = new HashMap();
052    private boolean strict = false;
053
054    /**
055     * Interface for an object which can write a FeatureFilter as XML.  Implement
056     * this to add support for new types of <code>FeatureFilter</code>
057     *
058     * @author Thomas Down
059     * @since 1.3
060     */
061
062    public interface FilterWriter {
063        public void writeFilter(FeatureFilter ff,
064                                XMLWriter xw,
065                                XMLFilterWriter config)
066            throws ClassCastException, IllegalArgumentException, IOException;
067    }
068
069
070    /**
071     * Construct a new <code>XMLFilterWriter</code> which can serialize the buildin types of
072     * <code>FeatureFilter</code>.
073     */
074
075    public XMLFilterWriter() {
076        try {
077            filterWritersByClass.put(
078                FeatureFilter.all,
079                new BlankFilterWriter(XML_FILTER_NS, "all")
080            );
081            filterWritersByClass.put(
082                FeatureFilter.none,
083                new BlankFilterWriter(XML_FILTER_NS, "none")
084            );
085            filterWritersByClass.put(
086                FeatureFilter.ByType.class,
087                new StringFilterWriter(XML_FILTER_NS, "byType", FeatureFilter.ByType.class.getMethod("getType", NO_ARGS))
088            );
089            filterWritersByClass.put(
090                FeatureFilter.BySource.class,
091                new StringFilterWriter(XML_FILTER_NS, "bySource", FeatureFilter.BySource.class.getMethod("getSource", NO_ARGS))
092            );
093            filterWritersByClass.put(
094                FeatureFilter.ByClass.class,
095                new ByClassFilterWriter()
096            );
097            filterWritersByClass.put(
098                FeatureFilter.ContainedByLocation.class,
099                new LocationFilterWriter(XML_FILTER_NS, "containedByLocation", FeatureFilter.ContainedByLocation.class.getMethod("getLocation", NO_ARGS))
100            );
101            filterWritersByClass.put(
102                FeatureFilter.OverlapsLocation.class,
103                new LocationFilterWriter(XML_FILTER_NS, "overlapsLocation", FeatureFilter.OverlapsLocation.class.getMethod("getLocation", NO_ARGS))
104            );
105            filterWritersByClass.put(
106                FeatureFilter.ShadowContainedByLocation.class,
107                new LocationFilterWriter(XML_FILTER_NS, "shadowContainedByLocation", FeatureFilter.ContainedByLocation.class.getMethod("getLocation", NO_ARGS))
108            );
109            filterWritersByClass.put(
110                FeatureFilter.ShadowOverlapsLocation.class,
111                new LocationFilterWriter(XML_FILTER_NS, "shadowOverlapsLocation", FeatureFilter.OverlapsLocation.class.getMethod("getLocation", NO_ARGS))
112            );
113            filterWritersByClass.put(
114                FeatureFilter.Not.class,
115                new FilterFilterWriter(XML_FILTER_NS, "not", FeatureFilter.Not.class.getMethod("getChild", NO_ARGS))
116            );
117            filterWritersByClass.put(
118                FeatureFilter.And.class,
119                new AndFilterWriter()
120            );
121            filterWritersByClass.put(
122                FeatureFilter.Or.class,
123                new OrFilterWriter()
124            );
125            filterWritersByClass.put(
126                FeatureFilter.StrandFilter.class,
127                new StrandFilterWriter()
128            );
129            filterWritersByClass.put(
130                FeatureFilter.FrameFilter.class,
131                new FrameFilterWriter()
132            );
133            filterWritersByClass.put(
134                FeatureFilter.ByParent.class,
135                new FilterFilterWriter(XML_FILTER_NS, "byParent", FeatureFilter.ByParent.class.getMethod("getFilter", NO_ARGS))
136            );
137            filterWritersByClass.put(
138                FeatureFilter.ByAncestor.class,
139                new FilterFilterWriter(XML_FILTER_NS, "byAncestor", FeatureFilter.ByAncestor.class.getMethod("getFilter", NO_ARGS))
140            );
141            filterWritersByClass.put(
142                FeatureFilter.ByChild.class,
143                new FilterFilterWriter(XML_FILTER_NS, "byChild", FeatureFilter.ByChild.class.getMethod("getFilter", NO_ARGS))
144            );
145            filterWritersByClass.put(
146                FeatureFilter.ByDescendant.class,
147                new FilterFilterWriter(XML_FILTER_NS, "byDescendant", FeatureFilter.ByDescendant.class.getMethod("getFilter", NO_ARGS))
148            );
149            filterWritersByClass.put(
150                FeatureFilter.OnlyChildren.class,
151                new FilterFilterWriter(XML_FILTER_NS, "onlyChildren", FeatureFilter.OnlyChildren.class.getMethod("getFilter", NO_ARGS))
152            );
153            filterWritersByClass.put(
154                FeatureFilter.OnlyDescendants.class,
155                new FilterFilterWriter(XML_FILTER_NS, "onlyDescendants", FeatureFilter.OnlyDescendants.class.getMethod("getFilter", NO_ARGS))
156            );
157            filterWritersByClass.put(
158                FeatureFilter.ByComponentName.class,
159                new StringFilterWriter(XML_FILTER_NS, "byComponentName", FeatureFilter.ByComponentName.class.getMethod("getComponentName", NO_ARGS))
160            );
161            filterWritersByClass.put(
162                FeatureFilter.BySequenceName.class,
163                new StringFilterWriter(XML_FILTER_NS, "bySequenceName", FeatureFilter.BySequenceName.class.getMethod("getSequenceName", NO_ARGS))
164            );
165            filterWritersByClass.put(
166                FeatureFilter.top_level,
167                new BlankFilterWriter(XML_FILTER_NS, "isTopLevel")
168            );
169            filterWritersByClass.put(
170                FeatureFilter.leaf,
171                new BlankFilterWriter(XML_FILTER_NS, "isLeaf")
172            );
173            AnnotationTypeFilterWriter atfw = new AnnotationTypeFilterWriter(new XMLAnnotationTypeWriter());
174            filterWritersByClass.put(
175                FeatureFilter.ByAnnotationType.class,
176                atfw
177            );
178            filterWritersByClass.put(
179                FeatureFilter.ByAnnotation.class,
180                atfw
181            );
182            filterWritersByClass.put(
183                FeatureFilter.HasAnnotation.class,
184                atfw
185            );
186            filterWritersByClass.put(
187                FeatureFilter.AnnotationContains.class,
188                atfw
189            );
190            filterWritersByClass.put(
191                FeatureFilter.ByPairwiseScore.class,
192                new ByPairwiseScoreFilterWriter()
193            );
194        } catch (Exception ex) {
195            throw new BioError("Assertion failed: couldn't initialize XMLFilterWriters",ex);
196        }
197    }
198
199    /**
200     * Add a writer for the specified class of filters
201     */
202
203    public void addXMLFilterWriter(Class clazz, FilterWriter xfw) {
204        filterWritersByClass.put(clazz, xfw);
205    }
206
207    /**
208     * Add a writer for a singleton filter.
209     */
210
211    public void addXMLFilterWriter(FeatureFilter ff, FilterWriter xfw) {
212        filterWritersByClass.put(ff, xfw);
213    }
214
215    /**
216     * Determine if this writer is in strict mode.
217     */
218
219    public boolean isStrict() {
220        return strict;
221    }
222
223    /**
224     * Selects strict mode.  In strict mode, the writer will throw an <code>IllegalArgumentException</code>
225     * if it encounters a type of <code>FeatureFilter</code> it doesn't recognize.  When not
226     * in strict model, unrecognized filters are silently replaced by <code>FeatureFilter.all</code>.
227     * Default is <code>false</code>.
228     */
229
230    public void setIsStrict(boolean b) {
231        this.strict = b;
232    }
233
234    /**
235     * Write a FeatureFilter to the supplied XMLWriter
236     *
237     * @throws IllegalArgumentException if the FeatureFilter is unrecognized, and the writer is
238     *                                  in strict mode.
239     * @throws IOException if an error occurs while outputting XML.
240     */
241
242    public void writeFilter(FeatureFilter ff, XMLWriter xw)
243        throws IllegalArgumentException, IOException
244    {
245        FilterWriter xfw = (FilterWriter) filterWritersByClass.get(ff);
246        if (xfw == null) {
247            xfw = (FilterWriter) filterWritersByClass.get(ff.getClass());
248        }
249        if (xfw != null) {
250            try {
251                xfw.writeFilter(ff, xw, this);
252            } catch (ClassCastException ex) {
253                throw new BioError("Filter type mismatch",ex);
254            }
255        } else {
256            if (strict) {
257                throw new IllegalArgumentException("Couldn't find a writer for filter of type " + ff.getClass().getName());
258            } else {
259                writeFilter(FeatureFilter.all, xw);
260            }
261        }
262    }
263
264    private static class BlankFilterWriter implements FilterWriter {
265        private String nsURI;
266        private String localName;
267
268        BlankFilterWriter(String nsURI, String localName) {
269            this.nsURI = nsURI;
270            this.localName =  localName;
271        }
272
273        public void writeFilter(FeatureFilter ff,
274                                XMLWriter xw,
275                                XMLFilterWriter config)
276            throws ClassCastException, IllegalArgumentException, IOException
277        {
278            xw.openTag(nsURI, localName);
279            xw.closeTag(nsURI, localName);
280        }
281    }
282
283    private static abstract class PropertyFilterWriter implements FilterWriter {
284        private final String nsURI;
285        private final String localName;
286        private final Method propMethod;
287
288        PropertyFilterWriter(String nsURI,
289                             String localName,
290                             Method propMethod)
291        {
292            this.nsURI = nsURI;
293            this.localName =  localName;
294            this.propMethod = propMethod;
295        }
296
297        public void writeFilter(FeatureFilter ff,
298                                XMLWriter xw,
299                                XMLFilterWriter config)
300            throws ClassCastException, IllegalArgumentException, IOException
301        {
302            try {
303                Object obj = propMethod.invoke(ff, NO_ARGS_VAL);
304                xw.openTag(nsURI, localName);
305                writeContent(obj, xw, config);
306                xw.closeTag(nsURI, localName);
307            } catch (IllegalAccessException ex) {
308                throw new BioError("Can't access property method",ex);
309            } catch (InvocationTargetException ex) {
310                throw new BioError("Couldn't access property",ex);
311            }
312        }
313
314        protected abstract void writeContent(Object obj,
315                                             XMLWriter xw,
316                                             XMLFilterWriter config)
317            throws ClassCastException, IllegalArgumentException, IOException;
318    }
319
320    private static class StringFilterWriter extends PropertyFilterWriter {
321        StringFilterWriter(String nsURI,
322                           String localName,
323                           Method propMethod)
324        {
325            super(nsURI, localName, propMethod);
326        }
327
328        protected void writeContent(Object obj,
329                                             XMLWriter xw,
330                                             XMLFilterWriter config)
331            throws ClassCastException, IllegalArgumentException, IOException
332        {
333            xw.print(obj.toString());
334        }
335    }
336
337    private static class LocationFilterWriter extends PropertyFilterWriter {
338        LocationFilterWriter(String nsURI,
339                           String localName,
340                           Method propMethod)
341        {
342            super(nsURI, localName, propMethod);
343        }
344
345        protected void writeContent(Object obj,
346                                             XMLWriter xw,
347                                             XMLFilterWriter config)
348            throws ClassCastException, IllegalArgumentException, IOException
349        {
350            Location l = (Location) obj;
351            for (Iterator i = l.blockIterator(); i.hasNext(); ) {
352                Location block = (Location) i.next();
353                xw.openTag("span");
354                xw.attribute("start", "" + block.getMin());
355                xw.attribute("stop", "" + block.getMax());
356                xw.closeTag("span");
357            }
358        }
359    }
360
361    private static class FilterFilterWriter extends PropertyFilterWriter {
362        FilterFilterWriter(String nsURI,
363                           String localName,
364                           Method propMethod)
365        {
366            super(nsURI, localName, propMethod);
367        }
368
369        protected void writeContent(Object obj,
370                                    XMLWriter xw,
371                                    XMLFilterWriter config)
372            throws ClassCastException, IllegalArgumentException, IOException
373        {
374            FeatureFilter ff = (FeatureFilter) obj;
375            config.writeFilter(ff, xw);
376        }
377    }
378
379    private static class AndFilterWriter implements FilterWriter {
380        public void writeFilter(FeatureFilter ff,
381                                XMLWriter xw,
382                                XMLFilterWriter config)
383            throws ClassCastException, IllegalArgumentException, IOException
384        {
385            xw.openTag(XML_FILTER_NS, "and");
386            writeSubFilter(ff, xw, config);
387            xw.closeTag(XML_FILTER_NS, "and");
388        }
389
390        private void writeSubFilter(FeatureFilter ff,
391                                    XMLWriter xw,
392                                    XMLFilterWriter config)
393            throws ClassCastException, IllegalArgumentException, IOException
394        {
395            if (ff instanceof FeatureFilter.And) {
396                FeatureFilter.And ffa = (FeatureFilter.And) ff;
397                writeSubFilter(ffa.getChild1(), xw, config);
398                writeSubFilter(ffa.getChild2(), xw, config);
399            } else {
400                config.writeFilter(ff, xw);
401            }
402        }
403    }
404
405    private static class OrFilterWriter implements FilterWriter {
406        public void writeFilter(FeatureFilter ff,
407                                XMLWriter xw,
408                                XMLFilterWriter config)
409            throws ClassCastException, IllegalArgumentException, IOException
410        {
411            xw.openTag(XML_FILTER_NS, "or");
412            writeSubFilter(ff, xw, config);
413            xw.closeTag(XML_FILTER_NS, "or");
414        }
415
416        private void writeSubFilter(FeatureFilter ff,
417                                    XMLWriter xw,
418                                    XMLFilterWriter config)
419            throws ClassCastException, IllegalArgumentException, IOException
420        {
421            if (ff instanceof FeatureFilter.Or) {
422                FeatureFilter.Or ffa = (FeatureFilter.Or) ff;
423                writeSubFilter(ffa.getChild1(), xw, config);
424                writeSubFilter(ffa.getChild2(), xw, config);
425            } else {
426                config.writeFilter(ff, xw);
427            }
428        }
429    }
430
431    private static class ByPairwiseScoreFilterWriter implements FilterWriter {
432        public void writeFilter(FeatureFilter ff,
433                                XMLWriter xw,
434                                XMLFilterWriter config)
435            throws ClassCastException, IllegalArgumentException, IOException
436        {
437            FeatureFilter.ByPairwiseScore scoreFilter = (FeatureFilter.ByPairwiseScore) ff;
438            xw.openTag(XML_FILTER_NS, "byPairwiseScore");
439              xw.openTag(XML_FILTER_NS, "minScore");
440              xw.print("" + scoreFilter.getMinScore());
441              xw.closeTag(XML_FILTER_NS, "minScore");
442              xw.openTag(XML_FILTER_NS, "maxScore");
443              xw.print("" + scoreFilter.getMaxScore());
444              xw.closeTag(XML_FILTER_NS, "maxScore");
445            xw.closeTag(XML_FILTER_NS, "byPairwiseScore");
446        }
447    }
448
449    private static class ByClassFilterWriter extends PropertyFilterWriter {
450        ByClassFilterWriter()
451            throws NoSuchMethodException
452        {
453                super(XML_FILTER_NS, "byClass", FeatureFilter.ByClass.class.getMethod("getTestClass", NO_ARGS));
454        }
455
456        protected void writeContent(Object obj,
457                                             XMLWriter xw,
458                                             XMLFilterWriter config)
459            throws ClassCastException, IllegalArgumentException, IOException
460        {
461            xw.print(((Class) obj).getName());
462        }
463    }
464
465    private static class StrandFilterWriter extends PropertyFilterWriter {
466        StrandFilterWriter()
467            throws NoSuchMethodException
468        {
469                super(XML_FILTER_NS, "byStrand", FeatureFilter.StrandFilter.class.getMethod("getStrand", NO_ARGS));
470        }
471
472        protected void writeContent(Object obj,
473                                             XMLWriter xw,
474                                             XMLFilterWriter config)
475            throws ClassCastException, IllegalArgumentException, IOException
476        {
477            xw.print(((StrandedFeature.Strand) obj).toString());
478        }
479    }
480
481    private static class FrameFilterWriter extends PropertyFilterWriter {
482        FrameFilterWriter()
483            throws NoSuchMethodException
484        {
485                super(XML_FILTER_NS, "byFrame", FeatureFilter.FrameFilter.class.getMethod("getFrame", NO_ARGS));
486        }
487
488        protected void writeContent(Object obj,
489                                             XMLWriter xw,
490                                             XMLFilterWriter config)
491            throws ClassCastException, IllegalArgumentException, IOException
492        {
493            xw.print("" + ((FramedFeature.ReadingFrame) obj).getFrame());
494        }
495    }
496
497    private static class AnnotationTypeFilterWriter extends PropertyFilterWriter {
498        private XMLAnnotationTypeWriter xatw;
499
500        AnnotationTypeFilterWriter(XMLAnnotationTypeWriter xatw)
501            throws NoSuchMethodException
502        {
503                super(XML_FILTER_NS, "byAnnotationType", FeatureFilter.ByAnnotationType.class.getMethod("getType", NO_ARGS));
504                this.xatw = xatw;
505        }
506
507        protected void writeContent(Object obj,
508                                    XMLWriter xw,
509                                    XMLFilterWriter config)
510            throws ClassCastException, IllegalArgumentException, IOException
511        {
512            xatw.writeAnnotationType((AnnotationType) obj, xw);
513        }
514    }
515}