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.program.xff;
023
024import org.biojava.bio.Annotation;
025import org.biojava.bio.MergeAnnotation;
026import org.biojava.bio.OverlayAnnotation;
027import org.biojava.bio.SmallAnnotation;
028import org.biojava.bio.seq.Feature;
029import org.biojava.bio.seq.io.ParseException;
030import org.biojava.bio.symbol.Location;
031import org.biojava.utils.AssertionFailure;
032import org.biojava.utils.ChangeVetoException;
033import org.biojava.utils.stax.DelegationManager;
034import org.biojava.utils.stax.StAXContentHandler;
035import org.biojava.utils.stax.StAXContentHandlerBase;
036import org.biojava.utils.stax.StringElementHandlerBase;
037import org.xml.sax.Attributes;
038import org.xml.sax.SAXException;
039
040/**
041 * StAX handler for the basic <code>feature</code> type of XFF.
042 * This class can also be subclassed to handle other XFF types.
043 *
044 * <p>
045 * In general, to handle a <code>feature</code> subclass, you will
046 * with to:
047 * </p>
048 *
049 * <ul>
050 * <li>If necessary, override createFeatureTemplate to build the appropriate BioJava
051 *     Feature.Template</li>
052 * <li>Add your own <code>startElement</code> and <code>endElement</code>
053 *     methods which handle extra extra elements in your feature type.  These
054 *     should normally pass on all the standard elements to
055 *     <code>super.startElement</code> and <code>super.endElement</code>.</li>
056 * </ul>
057 *
058 * <p>
059 * Note that, since <code>FeatureHandler</code> does some basic housekeeping,
060 * if you `consume' a startElement notification (i.e. don't pass it on to the
061 * superclass) you must also consume the matching endElement.  Since FeatureHandler
062 * silently ignores all unrecognized elements, it is usually safe to pass on
063 * all startElement and endElement notifications -- even those which are specific
064 * to your feature type.
065 * </p>
066 *
067 * @author Thomas Down
068 * @since 1.2
069 */
070
071public class FeatureHandler extends StAXContentHandlerBase {
072    public static final XFFPartHandlerFactory FEATURE_HANDLER_FACTORY = new XFFPartHandlerFactory() {
073            public StAXContentHandler getPartHandler(XFFFeatureSetHandler xffenv) {
074                return new FeatureHandler(xffenv);
075            }
076        } ;
077
078    private XFFFeatureSetHandler xffenv;
079    private Feature.Template template = null;
080    private boolean startFired = false;
081    private boolean endFired = false;
082    private int level = 0;
083
084    /**
085     * Construct a new Feature handler, passing in an XFF-parsing environment.
086     */
087
088    public FeatureHandler(XFFFeatureSetHandler xffenv) {
089        this.xffenv = xffenv;
090    }
091
092    /**
093     * Return the XFF processing environment passed in when this handler was
094     * created.
095     */
096
097    public XFFFeatureSetHandler getXFFEnvironment() {
098        return xffenv;
099    }
100
101    /**
102     * Get the template for the feature being constructed.  This should
103     * usually not be overridden.  Delegates to <code>createFeatureTemplate</code>
104     * for template construction.
105     */
106
107    protected Feature.Template getFeatureTemplate() {
108        if (template == null) {
109            template = createFeatureTemplate();
110        }
111        return template;
112    }
113
114    /**
115     * Create a new template of the appropriate type.  Override this method
116     * if you wish to use a template type other than Feature.Template.
117     */
118
119    protected Feature.Template createFeatureTemplate() {
120        return new Feature.Template();
121    }
122
123  /**
124   * Fire the startFeature event.  You should wrap this method if you want
125   * to perform any validation on the Feature.Template before the
126   * startFeature is fired.
127   *
128   * @throws ParseException if the startFeature notification fails, or if
129   *                        it has already been made.
130   */
131
132  protected void fireStartFeature()
133          throws ParseException
134  {
135    if (startFired) {
136      throw new ParseException("startFeature event has already been fired for this feature");
137    }
138
139    Feature.Template templ = getFeatureTemplate();
140    Annotation ann = getXFFEnvironment().getMergeAnnotation();
141    Annotation orig = templ.annotation;
142    Annotation res = null;
143
144    if(ann != null && orig != null) {
145      try {
146        MergeAnnotation ma = new MergeAnnotation();
147        ma.addAnnotation(templ.annotation);
148        ma.addAnnotation(ann);
149        res = ma;
150      } catch (ChangeVetoException cve) {
151        throw new AssertionFailure(cve);
152      }
153    } else if(ann != null) {
154      res = ann;
155    } else if(orig != null) {
156      res = orig;
157    }
158
159    if(res == null) {
160      res = new SmallAnnotation();
161    } else {
162      res = new OverlayAnnotation(res);
163    }
164
165    templ.annotation = res;
166
167    getXFFEnvironment().getFeatureListener().startFeature(templ);
168    startFired = true;
169  }
170
171    /**
172     * Fire the endFeature event.
173     */
174
175    protected void fireEndFeature()
176        throws ParseException
177    {
178        if (!startFired) {
179            throw new ParseException("startFeature has not yet been fired for this feature.");
180        }
181
182        if (endFired) {
183            throw new ParseException("endFeature event has already been fired for this feature");
184        }
185
186        getXFFEnvironment().getFeatureListener().endFeature();
187        endFired = true;
188    }
189
190    /**
191     * Set a property.  If the startFeature notification has not yet been fired,
192     * the property is added directly to the annotation bundle in the feature
193     * template, otherwise an addFeatureProperty event is generated.
194     */
195
196    protected void setFeatureProperty(Object key, Object value)
197        throws ChangeVetoException, ParseException
198    {
199        if (startFired) {
200            getXFFEnvironment().getFeatureListener().addFeatureProperty(key, value);
201        } else {
202            Feature.Template ft = getFeatureTemplate();
203            if (ft.annotation == null) {
204                ft.annotation = new SmallAnnotation();
205            }
206            ft.annotation.setProperty(key, value);
207        }
208    }
209
210    /**
211     * StAX callback for element starts.  Wrap this method to handle extra
212     * elements within your own feature types.
213     */
214
215    public void startElement(String nsURI,
216                             String localName,
217                             String qName,
218                             Attributes attrs,
219                             DelegationManager dm)
220         throws SAXException
221    {
222        level++;
223        if (level == 1) {
224            // This was our initial startElement.
225            String id = attrs.getValue("id");
226            if (id != null) {
227                try {
228                    setFeatureProperty(XFFFeatureSetHandler.PROPERTY_XFF_ID, id);
229                } catch (Exception ex) {
230                    throw new SAXException("Couldn't set property", ex);
231                }
232            }
233        } else {
234            if (localName.equals("type")) {
235                dm.delegate(getTypeHandler());
236            } else if (localName.equals("source")) {
237                dm.delegate(getSourceHandler());
238            } else if (localName.equals("location")) {
239                dm.delegate(getLocationHandler());
240            } else if (localName.equals("id")) {
241                dm.delegate(getOldIDHandler());
242            } else if (localName.equals("details")) {
243                if (!startFired) {
244                    try {
245                        fireStartFeature();
246                    } catch (ParseException ex) {
247                        throw new SAXException(ex);
248                    }
249                }
250
251                dm.delegate(xffenv.getDetailsHandler());
252
253                // Need to handle details properly
254            } else if (localName.equals("featureSet")) {
255                if (!startFired) {
256                    try {
257                        fireStartFeature();
258                    } catch (ParseException ex) {
259                        throw new SAXException(ex);
260                    }
261                }
262
263                dm.delegate(xffenv);
264            } else {
265                // throw new SAXException("Couldn't recognize element " + localName + " in namespace " + nsURI);
266            }
267        }
268    }
269
270    /**
271     * StAX callback for element ends.  Wrap this method to handle extra
272     * elements within your own feature types.
273     */
274
275    public void endElement(String nsURI,
276                           String localName,
277                           String qName,
278                           StAXContentHandler handler)
279        throws SAXException
280    {
281        level--;
282
283        if (level == 0) {
284            // Our tree is done.
285
286            try {
287                if (!startFired) {
288                    fireStartFeature();
289                }
290
291                if (!endFired) {
292                    fireEndFeature();
293                }
294            } catch (ParseException ex) {
295                throw new SAXException(ex);
296            }
297        }
298    }
299
300    protected StAXContentHandler getTypeHandler() {
301        return new StringElementHandlerBase() {
302                protected void setStringValue(String s) {
303                    getFeatureTemplate().type = s.trim();
304                }
305            } ;
306    }
307
308    protected StAXContentHandler getSourceHandler() {
309        return new StringElementHandlerBase() {
310                protected void setStringValue(String s) {
311                    getFeatureTemplate().source = s.trim();
312                }
313            } ;
314    }
315
316    protected StAXContentHandler getOldIDHandler() {
317        return new StringElementHandlerBase() {
318                protected void setStringValue(String s)
319                    throws SAXException
320                {
321                    try {
322                        setFeatureProperty(XFFFeatureSetHandler.PROPERTY_XFF_ID, s.trim());
323                    } catch (Exception ex) {
324                        throw new SAXException("Couldn't set property", ex);
325                    }
326                }
327            } ;
328    }
329
330    protected StAXContentHandler getLocationHandler() {
331        return new LocationHandlerBase() {
332                protected void setLocationValue(Location l) {
333                    getFeatureTemplate().location = l;
334                }
335            } ;
336    }
337}