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.tagvalue;
023
024import java.io.BufferedReader;
025import java.io.IOException;
026import java.util.ArrayList;
027import java.util.Iterator;
028import java.util.List;
029
030import org.biojava.utils.ParserException;
031
032/**
033 * <p>
034 * Encapsulate the parsing of lines from a buffered reader into tag-value
035 * events.
036 * </p>
037 *
038 * <p>
039 * Scripts will usually construct a parser object, a BufferedReader, a
040 * TagValueParser and TagValueListener, and then set up a loop that processes
041 * each record in the reader by calling Parser.read() until it returns false.
042 * </p>
043 *
044 * @since 1.2
045 * @author Matthew Pocock
046 */
047public class Parser {
048  public boolean read(
049    BufferedReader reader,
050    TagValueParser parser,
051    TagValueListener listener
052  ) throws
053    IOException,
054    ParserException
055  {
056    final List stack = new ArrayList();
057    push(stack, new Frame(parser, listener, null));
058    
059    Context ctxt = new Context();
060    
061    listener.startRecord();
062    
063    for(
064      Object line = reader.readLine();
065      line != null;
066      line = reader.readLine()
067    ) {
068      // Find the deepest stack-frame with the same key.
069      // If this is not the deepest one, unwind the stack to that point
070      Frame frame = null;
071      TagValue tv = null;
072      for(Iterator fi = stack.iterator(); fi.hasNext(); ) {
073        frame = (Frame) fi.next();
074        
075        tv = frame.parser.parse(line);
076        
077        // end of record. Is it the last in the file?
078        if(tv == null) {
079          // scan for eof after whitespace
080          boolean eof = false;
081          while(true) {
082            reader.mark(1);
083            int c = reader.read();
084            if(c == -1) {
085              eof = true;
086              break;
087            }
088            
089            if(Character.isWhitespace((char) c)) {
090              continue;
091            }
092            
093            reader.reset();
094            break;
095          }
096          
097          // now unwind stack
098          do {
099            Frame top = (Frame) pop(stack);
100            top.listener.endTag();
101            top.listener.endRecord();
102          } while(!stack.isEmpty());
103          return !eof;
104        }
105        
106        // not a continuation of a previous tag - unwrap stack
107        if(
108          tv.isNewTag() ||
109          (tv.getTag() != null && !tv.getTag().equals(frame.tag))
110        ) {
111          // remove all stack frames which have been obsoleted by this tag
112          Frame top;
113          for(top = (Frame) pop(stack); top != frame; top = (Frame) pop(stack)) {
114            top.listener.endTag();
115            top.listener.endRecord();
116          }
117          if(top.tag != null) {
118            top.listener.endTag();
119          }
120          
121          // handle current stack frame by starting a tag
122          push(stack, new Frame(top.parser, top.listener, tv.getTag()));
123          top.listener.startTag(tv.getTag());
124          break;
125        }
126        
127        line = tv.getValue();
128      }
129      
130      // process a value and handle potentially pushing a new stack frame
131      while(true) {
132        // pass in value and see if it requests a new stack frame
133        ctxt.flush();
134        frame.listener.value(ctxt, tv.getValue());
135        if(!ctxt.isDirty()) {
136          break;
137        }
138
139        // push a new stack frame
140        tv = ctxt.parser.parse(tv.getValue());
141        frame = new Frame(ctxt.parser, ctxt.listener, tv.getTag());
142        push(stack, frame);
143        ctxt.listener.startRecord();
144        ctxt.listener.startTag(tv.getTag());
145        
146        // we must loop arround in case the new frame wants to immediately push a
147        // new stack frame
148      }
149    }
150
151    throw new IOException("Premature end of stream or missing end tag");
152  }
153  
154  private void push(List stack, Object o) {
155    stack.add(o);
156  }
157  
158  private Object pop(List stack) {
159    return stack.remove(stack.size() - 1);
160  }
161  
162  private static class Frame {
163    public final TagValueParser parser;
164    public final TagValueListener listener;
165    public final Object tag;
166    
167    public Frame(
168      TagValueParser parser,
169      TagValueListener listener,
170      Object tag
171    ) {
172      this.parser = parser;
173      this.listener = listener;
174      this.tag = tag;
175    }
176  }
177  
178  private static class Context
179    implements
180      TagValueContext
181  {
182    public TagValueParser parser;
183    public TagValueListener listener;
184    
185    public void pushParser(
186      TagValueParser subParser,
187      TagValueListener listener
188    ) {
189      this.parser = subParser;
190      this.listener = listener;
191    }
192    
193    public void flush() {
194      this.parser = null;
195      this.listener = null;
196    }
197    
198    public boolean isDirty() {
199      return parser != null;
200    }
201  }
202}