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.util.HashMap;
026import java.util.Iterator;
027import java.util.Map;
028import java.util.Set;
029
030import org.biojava.bio.AnnotationType;
031import org.biojava.bio.BioError;
032import org.biojava.bio.CardinalityConstraint;
033import org.biojava.bio.CollectionConstraint;
034import org.biojava.bio.PropertyConstraint;
035import org.biojava.bio.symbol.Location;
036import org.biojava.utils.xml.XMLWriter;
037
038/**
039 * Main class for writing AnnotationTypes as XML.  Knows about all the builtin
040 * classes of AnnotationType.  It's possible to plug new ones in by calling
041 * one of the addXMLPropertyConstraintWriter methods.
042 *
043 * @author Thomas Down
044 * @since 1.3
045 */
046
047public class XMLAnnotationTypeWriter {
048    /**
049     * XML namespace string used to the AnnotationType representation
050     */
051
052    public static final String XML_ANNOTATIONTYPE_NS = "http://www.biojava.org/AnnotationType";
053
054    private Map constraintWritersByObject = new HashMap();
055    private Map constraintWritersByClass = new HashMap();
056    private Map colConstraintWritersByObject = new HashMap();
057    private Map colConstraintWritersByClass = new HashMap();
058    private boolean strict = false;
059
060    /**
061     * Writer for types of CollectionConstraint.  Implement this to add support
062     * for a new type of CollectionConstraint.
063     *
064     * @author Thomas Down
065     * @since 1.3
066     */
067
068    public interface XMLCollectionConstraintWriter {
069        public void writeCollectionConstraint(CollectionConstraint pc,
070                                              XMLWriter xw,
071                                              XMLAnnotationTypeWriter config)
072            throws ClassCastException, IOException, IllegalArgumentException;
073    }
074
075    /**
076     * Writer for types of PropertyConstraint.  Implement this to add support
077     * for a new type of PropertyConstraint.
078     *
079     * @author Thomas Down
080     * @since 1.3
081     */
082
083    public interface XMLPropertyConstraintWriter {
084        public void writePropertyConstraint(PropertyConstraint pc,
085                                            XMLWriter xw,
086                                            XMLAnnotationTypeWriter config)
087            throws ClassCastException, IOException, IllegalArgumentException;
088    }
089
090    /**
091     * Construct a new AnnotationTypeWriter which knows about the builtin types of PropertyConstraint
092     */
093
094    public XMLAnnotationTypeWriter() {
095        try {
096            addXMLPropertyConstraintWriter(
097                    PropertyConstraint.ANY,
098                    new BlankConstraintWriter(XML_ANNOTATIONTYPE_NS, "any")
099            );
100            addXMLPropertyConstraintWriter(
101                    PropertyConstraint.NONE,
102                    new BlankConstraintWriter(XML_ANNOTATIONTYPE_NS, "none")
103            );
104            addXMLPropertyConstraintWriter(
105                    PropertyConstraint.ExactValue.class,
106                    new ExactValueConstraintWriter()
107            );
108            addXMLPropertyConstraintWriter(
109                    PropertyConstraint.ByClass.class,
110                    new ByClassConstraintWriter()
111            );
112            addXMLPropertyConstraintWriter(
113                    PropertyConstraint.And.class,
114                    new AndConstraintWriter()
115            );
116            addXMLPropertyConstraintWriter(
117                    PropertyConstraint.Or.class,
118                    new OrConstraintWriter()
119            );
120            addXMLPropertyConstraintWriter(
121                    PropertyConstraint.Enumeration.class,
122                    new EnumConstraintWriter()
123            );
124            addXMLPropertyConstraintWriter(
125                    PropertyConstraint.ByAnnotationType.class,
126                    new AnnotationTypeConstraintWriter()
127            );
128
129            addXMLCollectionConstraintWriter(
130                    CollectionConstraint.ANY,
131                    new BlankCollectionConstraintWriter(XML_ANNOTATIONTYPE_NS, "any")
132            );
133            addXMLCollectionConstraintWriter(
134                    CollectionConstraint.NONE,
135                    new BlankCollectionConstraintWriter(XML_ANNOTATIONTYPE_NS, "none")
136            );
137            addXMLCollectionConstraintWriter(
138                    CollectionConstraint.EMPTY,
139                    new BlankCollectionConstraintWriter(XML_ANNOTATIONTYPE_NS, "empty")
140            );
141            addXMLCollectionConstraintWriter(
142                    CollectionConstraint.And.class,
143                    new AndCollectionConstraintWriter()
144            );
145            addXMLCollectionConstraintWriter(
146                    CollectionConstraint.Or.class,
147                    new OrCollectionConstraintWriter()
148            );
149            addXMLCollectionConstraintWriter(
150                    CollectionConstraint.AllValuesIn.class,
151                    new AllValuesInCollectionConstraintWriter()
152            );
153            addXMLCollectionConstraintWriter(
154                    CollectionConstraint.Contains.class,
155                    new ContainsCollectionConstraintWriter()
156            );
157        } catch (Exception ex) {
158            throw new BioError("Assertion failed: couldn't initialize XMLFilterWriters", ex);
159        }
160    }
161
162    /**
163     * Register a writer for the specified class of property constraint
164     */
165
166    public void addXMLPropertyConstraintWriter(Class clazz, XMLPropertyConstraintWriter xfw) {
167        constraintWritersByClass.put(clazz, xfw);
168    }
169
170    /**
171     * Register a writer for a singleton property constraint.
172     */
173
174    public void addXMLPropertyConstraintWriter(PropertyConstraint pc, XMLPropertyConstraintWriter xfw) {
175        constraintWritersByObject.put(pc, xfw);
176    }
177
178    /**
179     * Register a writer for the specified class of collection constraint
180     */
181
182    public void addXMLCollectionConstraintWriter(Class clazz, XMLCollectionConstraintWriter xfw) {
183        colConstraintWritersByClass.put(clazz, xfw);
184    }
185
186    /**
187     * Register a writer for a singleton property constraint.
188     */
189
190    public void addXMLCollectionConstraintWriter(CollectionConstraint pc, XMLCollectionConstraintWriter xfw) {
191        colConstraintWritersByObject.put(pc, xfw);
192    }
193
194    /**
195     * Determine if this writer is in strict mode.
196     */
197
198    public boolean isStrict() {
199        return strict;
200    }
201
202    /**
203     * Selects strict mode.  In strict mode, the writer will throw an <code>IllegalArgumentException</code>
204     * if it encounters a type of <code>PropertyConstraint</code> it doesn't recognize.  When not
205     * in strict model, unrecognized constraints are silently replaced by <code>PropertyConstraint.ANY</code>.
206     * Default is <code>false</code>.
207     */
208
209    public void setIsStrict(boolean b) {
210        this.strict = b;
211    }
212
213    private void writeCardinality(Location card,
214                                  XMLWriter xw)
215        throws IllegalArgumentException, IOException
216    {
217        if (card == CardinalityConstraint.ANY) {
218            xw.openTag(XML_ANNOTATIONTYPE_NS, "cardinalityAny");
219            xw.closeTag(XML_ANNOTATIONTYPE_NS, "cardinalityAny");
220        } else if (card == CardinalityConstraint.ZERO) {
221            xw.openTag(XML_ANNOTATIONTYPE_NS, "cardinalityZero");
222            xw.closeTag(XML_ANNOTATIONTYPE_NS, "cardinalityZero");
223        } else if (card == CardinalityConstraint.ONE) {
224            xw.openTag(XML_ANNOTATIONTYPE_NS, "cardinalityOne");
225            xw.closeTag(XML_ANNOTATIONTYPE_NS, "cardinalityOne");
226        } else if (card == CardinalityConstraint.NONE) {
227            xw.openTag(XML_ANNOTATIONTYPE_NS, "cardinalityNone");
228            xw.closeTag(XML_ANNOTATIONTYPE_NS, "cardinalityNone");
229        } else {
230            xw.openTag(XML_ANNOTATIONTYPE_NS, "cardinality");
231            for (Iterator bi = card.blockIterator(); bi.hasNext(); ) {
232                Location bloc = (Location) bi.next();
233                xw.openTag("span");
234                xw.attribute("start", intToString(bloc.getMin()));
235                xw.attribute("stop",  intToString(bloc.getMax()));
236                xw.closeTag("span");
237            }
238            xw.closeTag(XML_ANNOTATIONTYPE_NS, "cardinality");
239        }
240    }
241
242    private String intToString(int i) {
243        if (i == Integer.MAX_VALUE) {
244            return "infinity";
245        } else {
246            return "" + i;
247        }
248    }
249
250    /**
251     * Writes a single property constraint.
252     */
253
254    void writePropertyConstraint(PropertyConstraint pc,
255                                 XMLWriter xw)
256        throws IllegalArgumentException, IOException
257    {
258        XMLPropertyConstraintWriter xpcw = (XMLPropertyConstraintWriter) constraintWritersByObject.get(pc);
259        if (xpcw == null) {
260            xpcw = (XMLPropertyConstraintWriter) constraintWritersByClass.get(pc.getClass());
261        }
262        if (xpcw == null) {
263            if (strict) {
264                throw new IllegalArgumentException("Couldn't find a writer for constraint of type " + pc.getClass().getName());
265            } else {
266                xpcw = (XMLPropertyConstraintWriter) constraintWritersByObject.get(PropertyConstraint.ANY);
267            }
268        }
269        xpcw.writePropertyConstraint(pc, xw, this);
270    }
271
272    /**
273     * Writes a single collection constraint
274     */
275
276    void writeCollectionConstraint(CollectionConstraint pc,
277                                   XMLWriter xw)
278        throws IllegalArgumentException, IOException
279    {
280        XMLCollectionConstraintWriter xpcw = (XMLCollectionConstraintWriter) colConstraintWritersByObject.get(pc);
281        if (xpcw == null) {
282            xpcw = (XMLCollectionConstraintWriter) colConstraintWritersByClass.get(pc.getClass());
283        }
284        if (xpcw == null) {
285            if (strict) {
286                throw new IllegalArgumentException("Couldn't find a writer for constraint of type " + pc.getClass().getName());
287            } else {
288                xpcw = (XMLCollectionConstraintWriter) constraintWritersByObject.get(CollectionConstraint.ANY);
289            }
290        }
291        xpcw.writeCollectionConstraint(pc, xw, this);
292    }
293
294    /**
295     * Write an <code>AnnotationType</code> to the specified XMLWriter.
296     *
297     * @throws IllegalArgumentException if the AnnotationType contains unrecognized
298     *                                  constraints, and the writer is in strict mode.
299     * @throws IOException if an error occurs while outputting XML.
300     */
301
302    public void writeAnnotationType(AnnotationType at, XMLWriter xw)
303        throws IllegalArgumentException, IOException
304    {
305        xw.openTag(XML_ANNOTATIONTYPE_NS, "annotationType");
306        Set propKeys = at.getProperties();
307
308        xw.openTag(XML_ANNOTATIONTYPE_NS, "propertyDefault");
309        CollectionConstraint.AllValuesIn defaultcc = (CollectionConstraint.AllValuesIn) at.getDefaultConstraint();
310        // writeCardinality(defaultcc.getCardinalityConstraint(), xw);
311        // writePropertyConstraint(defaultcc.getPropertyConstraint(), xw);
312        writeCollectionConstraint(defaultcc, xw);
313        xw.closeTag(XML_ANNOTATIONTYPE_NS, "propertyDefault");
314
315        for (Iterator pi = propKeys.iterator(); pi.hasNext(); ) {
316            Object propKey = pi.next();
317            CollectionConstraint cc = at.getConstraint(propKey);
318
319            xw.openTag(XML_ANNOTATIONTYPE_NS, "property");
320            xw.attribute(XML_ANNOTATIONTYPE_NS, "name", propKey.toString());
321            // CollectionConstraint.AllValuesIn cc = (CollectionConstraint.AllValuesIn) at.getConstraint(propKey);
322            // writeCardinality(cc.getCardinalityConstraint(), xw);
323            // writePropertyConstraint(cc.getPropertyConstraint(), xw);
324            writeCollectionConstraint(cc, xw);
325            xw.closeTag(XML_ANNOTATIONTYPE_NS, "property");
326        }
327        xw.closeTag(XML_ANNOTATIONTYPE_NS, "annotationType");
328    }
329
330    private static class BlankConstraintWriter implements XMLPropertyConstraintWriter {
331        private String nsURI;
332        private String localName;
333
334        BlankConstraintWriter(String nsURI, String localName) {
335            this.nsURI = nsURI;
336            this.localName =  localName;
337        }
338
339        public void writePropertyConstraint(PropertyConstraint pc,
340                                            XMLWriter xw,
341                                            XMLAnnotationTypeWriter config)
342            throws ClassCastException, IllegalArgumentException, IOException
343        {
344            xw.openTag(nsURI, localName);
345            xw.closeTag(nsURI, localName);
346        }
347    }
348
349    private static class ExactValueConstraintWriter implements XMLPropertyConstraintWriter {
350        public void writePropertyConstraint(PropertyConstraint pc,
351                                            XMLWriter xw,
352                                            XMLAnnotationTypeWriter config)
353            throws ClassCastException, IllegalArgumentException, IOException
354        {
355            PropertyConstraint.ExactValue pcev = (PropertyConstraint.ExactValue) pc;
356            xw.openTag(XML_ANNOTATIONTYPE_NS, "value");
357            xw.print(pcev.getValue().toString());
358            xw.closeTag(XML_ANNOTATIONTYPE_NS, "value");
359        }
360    }
361
362    private static class ByClassConstraintWriter implements XMLPropertyConstraintWriter {
363        public void writePropertyConstraint(PropertyConstraint pc,
364                                            XMLWriter xw,
365                                            XMLAnnotationTypeWriter config)
366            throws ClassCastException, IllegalArgumentException, IOException
367        {
368            PropertyConstraint.ByClass pcev = (PropertyConstraint.ByClass) pc;
369            xw.openTag(XML_ANNOTATIONTYPE_NS, "byClass");
370            xw.print(pcev.getPropertyClass().getName());
371            xw.closeTag(XML_ANNOTATIONTYPE_NS, "byClass");
372        }
373    }
374
375    private static class AndConstraintWriter implements XMLPropertyConstraintWriter {
376        public void writePropertyConstraint(PropertyConstraint pc,
377                                            XMLWriter xw,
378                                            XMLAnnotationTypeWriter config)
379            throws ClassCastException, IllegalArgumentException, IOException
380        {
381            PropertyConstraint.And pca = (PropertyConstraint.And) pc;
382            xw.openTag(XML_ANNOTATIONTYPE_NS, "and");
383            writeSubConstraint(pca, xw, config);
384            xw.closeTag(XML_ANNOTATIONTYPE_NS, "and");
385        }
386
387        private void writeSubConstraint(PropertyConstraint pc,
388                                        XMLWriter xw,
389                                        XMLAnnotationTypeWriter config)
390            throws ClassCastException, IllegalArgumentException, IOException
391        {
392            if (pc instanceof PropertyConstraint.And) {
393                PropertyConstraint.And ffa = (PropertyConstraint.And) pc;
394                writeSubConstraint(ffa.getChild1(), xw, config);
395                writeSubConstraint(ffa.getChild2(), xw, config);
396            } else {
397                config.writePropertyConstraint(pc, xw);
398            }
399        }
400    }
401
402    private static class OrConstraintWriter implements XMLPropertyConstraintWriter {
403        public void writePropertyConstraint(PropertyConstraint pc,
404                                            XMLWriter xw,
405                                            XMLAnnotationTypeWriter config)
406            throws ClassCastException, IllegalArgumentException, IOException
407        {
408            PropertyConstraint.Or pca = (PropertyConstraint.Or) pc;
409            xw.openTag(XML_ANNOTATIONTYPE_NS, "or");
410            writeSubConstraint(pca, xw, config);
411            xw.closeTag(XML_ANNOTATIONTYPE_NS, "or");
412        }
413
414        private void writeSubConstraint(PropertyConstraint pc,
415                                        XMLWriter xw,
416                                        XMLAnnotationTypeWriter config)
417            throws ClassCastException, IllegalArgumentException, IOException
418        {
419            if (pc instanceof PropertyConstraint.Or) {
420                PropertyConstraint.Or ffa = (PropertyConstraint.Or) pc;
421                writeSubConstraint(ffa.getChild1(), xw, config);
422                writeSubConstraint(ffa.getChild2(), xw, config);
423            } else {
424                config.writePropertyConstraint(pc, xw);
425            }
426        }
427    }
428
429    private static class EnumConstraintWriter implements XMLPropertyConstraintWriter {
430        public void writePropertyConstraint(PropertyConstraint pc,
431                                            XMLWriter xw,
432                                            XMLAnnotationTypeWriter config)
433            throws ClassCastException, IllegalArgumentException, IOException
434        {
435            PropertyConstraint.Enumeration pcenum = (PropertyConstraint.Enumeration) pc;
436            xw.openTag(XML_ANNOTATIONTYPE_NS, "or");
437            for (Iterator vi = pcenum.getValues().iterator(); vi.hasNext(); ) {
438                xw.openTag(XML_ANNOTATIONTYPE_NS, "value");
439                xw.print(vi.next().toString());
440                xw.closeTag(XML_ANNOTATIONTYPE_NS, "value");
441            }
442            xw.closeTag(XML_ANNOTATIONTYPE_NS, "or");
443        }
444    }
445
446    private static class AnnotationTypeConstraintWriter implements XMLPropertyConstraintWriter {
447        public void writePropertyConstraint(PropertyConstraint pc,
448                                            XMLWriter xw,
449                                            XMLAnnotationTypeWriter config)
450            throws ClassCastException, IllegalArgumentException, IOException
451        {
452            PropertyConstraint.ByAnnotationType pcbat = (PropertyConstraint.ByAnnotationType) pc;
453            config.writeAnnotationType(pcbat.getAnnotationType(), xw);
454        }
455    }
456
457
458
459    private static class BlankCollectionConstraintWriter implements XMLCollectionConstraintWriter {
460        private String nsURI;
461        private String localName;
462
463        BlankCollectionConstraintWriter(String nsURI, String localName) {
464            this.nsURI = nsURI;
465            this.localName =  localName;
466        }
467
468        public void writeCollectionConstraint(CollectionConstraint pc,
469                                              XMLWriter xw,
470                                              XMLAnnotationTypeWriter config)
471            throws ClassCastException, IllegalArgumentException, IOException
472        {
473            xw.openTag(nsURI, localName);
474            xw.closeTag(nsURI, localName);
475        }
476    }
477
478    private static class AndCollectionConstraintWriter implements XMLCollectionConstraintWriter {
479        public void writeCollectionConstraint(CollectionConstraint pc,
480                                              XMLWriter xw,
481                                              XMLAnnotationTypeWriter config)
482            throws ClassCastException, IllegalArgumentException, IOException
483        {
484            CollectionConstraint.And pca = (CollectionConstraint.And) pc;
485            xw.openTag(XML_ANNOTATIONTYPE_NS, "and");
486            writeSubConstraint(pca, xw, config);
487            xw.closeTag(XML_ANNOTATIONTYPE_NS, "and");
488        }
489
490        private void writeSubConstraint(CollectionConstraint pc,
491                                        XMLWriter xw,
492                                        XMLAnnotationTypeWriter config)
493            throws ClassCastException, IllegalArgumentException, IOException
494        {
495            if (pc instanceof CollectionConstraint.And) {
496                CollectionConstraint.And ffa = (CollectionConstraint.And) pc;
497                writeSubConstraint(ffa.getChild1(), xw, config);
498                writeSubConstraint(ffa.getChild2(), xw, config);
499            } else {
500                config.writeCollectionConstraint(pc, xw);
501            }
502        }
503    }
504
505    private static class OrCollectionConstraintWriter implements XMLCollectionConstraintWriter {
506        public void writeCollectionConstraint(CollectionConstraint pc,
507                                              XMLWriter xw,
508                                              XMLAnnotationTypeWriter config)
509            throws ClassCastException, IllegalArgumentException, IOException
510        {
511            CollectionConstraint.Or pca = (CollectionConstraint.Or) pc;
512            xw.openTag(XML_ANNOTATIONTYPE_NS, "or");
513            writeSubConstraint(pca, xw, config);
514            xw.closeTag(XML_ANNOTATIONTYPE_NS, "or");
515        }
516
517        private void writeSubConstraint(CollectionConstraint pc,
518                                        XMLWriter xw,
519                                        XMLAnnotationTypeWriter config)
520            throws ClassCastException, IllegalArgumentException, IOException
521        {
522            if (pc instanceof CollectionConstraint.Or) {
523                CollectionConstraint.Or ffa = (CollectionConstraint.Or) pc;
524                writeSubConstraint(ffa.getChild1(), xw, config);
525                writeSubConstraint(ffa.getChild2(), xw, config);
526            } else {
527                config.writeCollectionConstraint(pc, xw);
528            }
529        }
530    }
531
532    private static class AllValuesInCollectionConstraintWriter implements XMLCollectionConstraintWriter {
533        public void writeCollectionConstraint(CollectionConstraint pc,
534                                              XMLWriter xw,
535                                              XMLAnnotationTypeWriter config)
536            throws ClassCastException, IllegalArgumentException, IOException
537        {
538            CollectionConstraint.AllValuesIn cc = (CollectionConstraint.AllValuesIn) pc;
539            xw.openTag(XML_ANNOTATIONTYPE_NS, "allValuesIn");
540            config.writeCardinality(cc.getCardinalityConstraint(), xw);
541            config.writePropertyConstraint(cc.getPropertyConstraint(), xw);
542            xw.closeTag(XML_ANNOTATIONTYPE_NS, "allValuesIn");
543        }
544
545    }
546
547    private static class ContainsCollectionConstraintWriter implements XMLCollectionConstraintWriter {
548        public void writeCollectionConstraint(CollectionConstraint pc,
549                                              XMLWriter xw,
550                                              XMLAnnotationTypeWriter config)
551            throws ClassCastException, IllegalArgumentException, IOException
552        {
553            CollectionConstraint.Contains cc = (CollectionConstraint.Contains) pc;
554            xw.openTag(XML_ANNOTATIONTYPE_NS, "contains");
555            config.writeCardinality(cc.getCardinalityConstraint(), xw);
556            config.writePropertyConstraint(cc.getPropertyConstraint(), xw);
557            xw.closeTag(XML_ANNOTATIONTYPE_NS, "contains");
558        }
559
560    }
561}