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.game;
023
024import java.util.ArrayList;
025import java.util.List;
026import java.util.ListIterator;
027
028import org.biojava.bio.Annotation;
029import org.biojava.bio.SmallAnnotation;
030import org.biojava.bio.seq.Feature;
031import org.biojava.bio.seq.io.ParseException;
032import org.biojava.bio.seq.io.SeqIOListener;
033import org.biojava.utils.stax.DelegationManager;
034import org.biojava.utils.stax.StAXContentHandler;
035import org.biojava.utils.stax.StAXContentHandlerBase;
036import org.xml.sax.Attributes;
037import org.xml.sax.SAXException;
038
039/**
040 * StAX handler shamelessly ripped off from Thomas Down's
041 * XFFFeatureSetHandler.  It was modified for greater 
042 * generality.
043 *
044 * <strong>NOTE</strong> This class is not thread-safe -- it
045 * must only be used for one parse at any time.
046 *
047 * @author Thomas Down
048 * @author David Huen
049 *
050 * @since 1.8
051 */
052
053public class StAXFeatureHandler extends StAXContentHandlerBase
054{
055  // class variables
056  private boolean setOnceFired = false;
057  private String myLocalName;
058  private boolean hasCallback = false;
059  private List handlers;
060  private boolean startFired=false;  
061  private boolean endFired=false;
062  private boolean inFeature=false;  // have we been here before?
063
064  // class variables for use by nested elements
065  protected Feature.Template featureTemplate;
066  protected SeqIOListener featureListener;
067  protected int startLoc;
068  protected int endLoc;  // needed because some XML don't give start and stops as attributes.
069/**
070 * this is the stack of handler objects for the current feature.
071  * The base value is the FeatureHandler itself.
072  * your feature and property handlers place and remove themselves
073  * from this stack.
074  *
075  * the purpose of all this is to implement context sensitivty for 
076  * property handlers translucently.  Property handlers can pop the
077  * stack for other handlers that implement interfaces that process
078  * that element.  This way the context code is within the object that
079  * defines that context rather than in a child property handler.
080  */
081  protected List callbackStack;
082  protected int stackLevel;
083
084  {
085    handlers = new ArrayList();
086    callbackStack = new ArrayList();
087  }
088
089  // there should be a factory method here to make this class
090
091/**
092 * Sets the element name that the class responds to.
093 */
094  public void setHandlerCharacteristics(String localName, boolean hasCallback) {
095    if (!setOnceFired) {
096      myLocalName = localName;
097      this.hasCallback = hasCallback;
098      setOnceFired = true;
099    }
100    else
101      System.err.println("setHandlerChracteristics called twice on same handler.");
102  }
103
104  public void setFeatureListener(SeqIOListener siol) {
105    featureListener = siol;
106  }
107
108  // Class to implement bindings
109  class Binding {
110    final ElementRecognizer recognizer;
111    final StAXHandlerFactory handlerFactory;
112    Binding(ElementRecognizer er, StAXHandlerFactory hf)
113    {
114      recognizer = er;
115      handlerFactory = hf;
116    }
117  }
118
119  // method to add a handler
120  // we do not distinguish whither it is a feature or property
121  // handler.  The factory method creates the right type subclassed
122  // from the correct type of handler
123  protected void addHandler(
124                   ElementRecognizer rec,
125                   StAXHandlerFactory handler)
126  {
127    handlers.add(new Binding(rec, handler));
128  }
129
130/**
131 *  generates a very basic Template for the feature with
132 *  SmallAnnotation in the annotation field.
133 *  <p>
134 *  Override if you wish a more specialised Template.
135 *
136 */
137  protected Feature.Template createTemplate() {
138    Feature.Template ft = new Feature.Template();
139    ft.annotation = new SmallAnnotation();
140    return ft;
141  }
142
143/**
144 * return current stack level.  Remember that the
145 * stack level is incremented/decremented AFTER
146 * the push()/pop() calls and superclass
147 * startElement()/StopElement calls.
148 */
149  protected int getLevel() {
150    return stackLevel;
151  }
152
153/**
154 * return iterator to callbackStack
155 */
156  protected ListIterator getHandlerStackIterator(int level) {
157    return callbackStack.listIterator(level);
158  }
159
160/**
161 * Push StAXContentHandler object onto stack
162 */
163  protected void push(StAXContentHandler handler) {
164    // push handler
165    callbackStack.add(handler);
166
167    // increment pointer
168    stackLevel++;
169  }
170
171/**
172 * pop a StAXContentHandler off the stack.
173 */
174  protected void pop() {
175    // decrement pointer
176    stackLevel--;
177
178    // pop handler
179    callbackStack.remove(stackLevel);
180  }
181
182/**
183 * Return current feature listener
184 */
185  public SeqIOListener getFeatureListener() {
186    return featureListener;
187  }
188
189/**
190 *  Fire the startFeature event.
191 */
192  private void fireStartFeature()
193    throws ParseException
194  {
195    if (startFired)
196      throw new ParseException("startFeature event has already been fired");
197
198    if (featureTemplate == null)
199      featureTemplate = createTemplate();
200
201    if (featureTemplate.annotation == null)
202      featureTemplate.annotation = Annotation.EMPTY_ANNOTATION;
203
204    featureListener.startFeature(featureTemplate);
205    startFired = true;
206  }
207
208/**
209 * Fire the endFeature event.
210 */
211  private void fireEndFeature()
212    throws ParseException
213  {
214    if (!startFired)
215      throw new ParseException("startFeature has not yet been fired!");
216
217    if (endFired)
218      throw new ParseException("endFeature event has already been fired!");
219
220    featureListener.endFeature();
221    endFired = true;
222  }
223
224/**
225 * Element-specific handler.
226 * Subclass this to do something useful!
227 */
228  public void startElementHandler(
229                String nsURI,
230                String localName,
231                String qName,
232                Attributes attrs)
233         throws SAXException
234  {
235  }
236
237
238/**
239 * Handles basic entry processing for all feature handlers.
240 */
241  public void startElement(
242                String nsURI,
243                String localName,
244                String qName,
245                Attributes attrs,
246                DelegationManager dm)
247         throws SAXException
248  {
249//    System.out.println("StaxFeaturehandler.startElement starting. localName: " + localName);
250    // sanity check
251    if (!setOnceFired)
252      throw new SAXException("StAXFeaturehandler not initialised before use!");
253
254    // find out if this element is really for me.
255    // perform delegation
256    for (int i = handlers.size() - 1; i >= 0; --i) {
257      Binding b = (Binding) handlers.get(i);
258      if (b.recognizer.filterStartElement(nsURI, localName, qName, attrs)) {
259//      System.out.println("StaxFeaturehandler.startElement delegating.");
260      dm.delegate(b.handlerFactory.getHandler(this));
261      return;
262      }
263    }
264
265    // I don't have a delegate for it but it might be a stray...
266    if (!(myLocalName.equals(localName)) ) {
267      //this one's not for me!
268      return;
269    }
270
271    // this one's mine.
272    // initialise if this is the first time thru'
273    if (!inFeature) {
274      inFeature = true;
275
276      if (hasCallback) {
277        // push ourselves onto the stack at bedrock.
278        // Except in the event of great stupidity, you will
279        // never call this method twice in an instance since
280        // each feature handler starts a new context.
281//        System.out.println("StAXFeatureHandler startElement called, localName, level: " + localName + " " + stackLevel);
282        if (stackLevel == 0) push(this);
283      }
284
285      // indicate start of feature to listener
286      try {
287        if (!startFired) fireStartFeature();
288      }
289      catch (ParseException pe) {
290        throw new SAXException("ParseException thrown in user code");
291      }
292    }
293
294    // call the element specific handler now.
295    startElementHandler(nsURI, localName, qName, attrs);
296
297  }
298
299/**
300 * Element specific exit handler
301 * Subclass to do anything useful.
302 */
303  public void endElementHandler(
304                String nsURI,
305                String localName,
306                String qName,
307                StAXContentHandler handler)
308              throws SAXException
309  {
310  }
311
312/**
313 * Handles basic exit processing.
314 */
315  public void endElement(
316                String nsURI,
317                String localName,
318                String qName,
319                StAXContentHandler handler)
320              throws SAXException
321  {
322    // is this a return after delegation or really mine?
323    if (!(myLocalName.equals(localName)) )
324      return;
325
326    // last chance to clear up before exiting
327//    System.out.println("StAXFeatureHandler endElement called, localName, level: " + localName + " " + stackLevel);
328    endElementHandler(nsURI, localName, qName, handler);
329
330    // it's mine, get prepared to leave.
331    if (hasCallback) pop();
332
333    // is it time to go beddy-byes?
334    if (stackLevel == 0) {
335      // we need to cope with zero-sized elements
336      try {
337        if (!startFired) fireStartFeature();
338        if (!endFired) fireEndFeature();
339      }
340      catch (ParseException pe) {
341          throw new SAXException("ParseException thrown in user code");
342      }
343    }
344//    System.out.println("StAXFeatureHandler endElement-leaving, localName, level: " + localName + " " + stackLevel);
345  }
346}