001package org.biojava.bio.annodb;
002
003import java.beans.XMLDecoder;
004import java.beans.XMLEncoder;
005import java.io.BufferedInputStream;
006import java.io.BufferedOutputStream;
007import java.io.BufferedReader;
008import java.io.File;
009import java.io.FileInputStream;
010import java.io.FileOutputStream;
011import java.io.FileWriter;
012import java.io.IOException;
013import java.io.PrintWriter;
014import java.io.Serializable;
015import java.lang.reflect.Method;
016import java.lang.reflect.Modifier;
017import java.util.Iterator;
018import java.util.List;
019
020import org.biojava.bio.Annotation;
021import org.biojava.bio.AnnotationTools;
022import org.biojava.bio.AnnotationType;
023import org.biojava.bio.BioException;
024import org.biojava.bio.program.indexdb.BioStore;
025import org.biojava.bio.program.indexdb.BioStoreFactory;
026import org.biojava.bio.program.indexdb.Record;
027import org.biojava.bio.program.tagvalue.AnnotationBuilder;
028import org.biojava.bio.program.tagvalue.Index2Model;
029import org.biojava.bio.program.tagvalue.Indexer2;
030import org.biojava.bio.program.tagvalue.Parser;
031import org.biojava.bio.program.tagvalue.ParserListener;
032import org.biojava.bio.program.tagvalue.TagValueListener;
033import org.biojava.bio.seq.io.filterxml.XMLAnnotationTypeHandler;
034import org.biojava.bio.seq.io.filterxml.XMLAnnotationTypeWriter;
035import org.biojava.utils.AssertionFailure;
036import org.biojava.utils.CommitFailure;
037import org.biojava.utils.ParserException;
038import org.biojava.utils.io.RandomAccessReader;
039import org.biojava.utils.stax.SAX2StAXAdaptor;
040import org.biojava.utils.xml.PrettyXMLWriter;
041import org.biojava.utils.xml.XMLWriter;
042import org.xml.sax.SAXException;
043import org.xml.sax.XMLReader;
044import org.xml.sax.helpers.XMLReaderFactory;
045
046/**
047 * <p>A database of Annotation instances backed by an indexed file set.</p>
048 *
049 * @author Matthew Pocock
050 * @since 1.3
051 */
052public class IndexedAnnotationDB
053implements AnnotationDB {
054  private final BioStore store;
055  private final AnnotationType schema;
056  private final ParserListenerFactory plFactory;
057  private final ParserListener parserListener;
058  private final AnnotationBuilder annBuilder;
059  private final Parser recordParser;
060
061  /**
062   * Create a new IndexedAnnotationDB.
063   *
064   * @param dbName
065   * @param storeLoc
066   * @param model
067   * @param toIndex
068   * @param maxKeyLen
069   * @param schema
070   * @param plFactory
071   * @throws BioException
072   * @throws CommitFailure
073   * @throws IOException
074   * @throws ParserException
075   */
076  public IndexedAnnotationDB(
077    String dbName,
078    File storeLoc,
079    Index2Model model,
080    List toIndex,
081    int maxKeyLen,
082    AnnotationType schema,
083    ParserListenerFactory plFactory
084  ) throws BioException, CommitFailure, IOException, ParserException {
085    // state
086    BioStoreFactory bsf = new BioStoreFactory();
087    bsf.setStoreName(dbName);
088    bsf.setPrimaryKey(model.getPrimaryKeyName());
089    bsf.setStoreLocation(storeLoc);
090    
091    for(Iterator i = model.getKeys().iterator(); i.hasNext(); ) {
092      String key = (String) i.next();
093      bsf.addKey(key, maxKeyLen);
094    }
095    
096    this.store = bsf.createBioStore();
097    this.schema = schema;
098    this.plFactory = plFactory;
099    this.annBuilder = new AnnotationBuilder(schema);
100    this.parserListener = plFactory.getParserListener(annBuilder);
101    this.recordParser = new Parser();
102    
103    // persistance
104    File factoryFile = new File(store.getLocation(), "ParserListenerFactory.xml");
105    XMLEncoder xmlEnc = new XMLEncoder(
106      new BufferedOutputStream(
107        new FileOutputStream(
108          factoryFile
109        )
110      )
111    );
112    xmlEnc.writeObject(plFactory);
113    xmlEnc.close();
114    
115    File schemaFile = new File(store.getLocation(), "schema.xml");
116    PrintWriter schemaPW = new PrintWriter(
117      new FileWriter(
118        schemaFile
119      )
120    );
121    XMLWriter schemaWriter = new PrettyXMLWriter(schemaPW);
122    XMLAnnotationTypeWriter schemaTW = new XMLAnnotationTypeWriter();
123    schemaTW.writeAnnotationType(schema, schemaWriter);
124    schemaPW.flush();
125    schemaPW.close();
126    
127    for(Iterator fi = toIndex.iterator(); fi.hasNext(); ) {
128      File file = (File) fi.next();
129      
130      Indexer2 ndx = new Indexer2(file, store, model);
131      ParserListener pl = plFactory.getParserListener(ndx);
132      Parser parser = new Parser();
133      while(parser.read(ndx.getReader(), pl.getParser(), pl.getListener())) {
134        ;
135      }
136    }
137    
138    store.commit();
139  }
140
141  /**
142   * Initialise the db from a store.
143   *
144   * @param store         the BioStore to initalise from
145   * @throws IOException  if there was an IO fault accessing the store
146   * @throws SAXException if the XML configuration file is corrupted
147   */
148  public IndexedAnnotationDB(BioStore store) throws IOException, SAXException {
149    this.store = store;
150    
151    File factoryFile = new File(store.getLocation(), "ParserListenerFactory.xml");
152    XMLDecoder xmlDec = new XMLDecoder(
153      new BufferedInputStream(
154        new FileInputStream(
155          factoryFile
156        )
157      )
158    );
159    this.plFactory = (ParserListenerFactory) xmlDec.readObject();
160    xmlDec.close();
161    
162    XMLReader parser = XMLReaderFactory.createXMLReader();
163    XMLAnnotationTypeHandler annTypeH = new XMLAnnotationTypeHandler();
164    parser.setContentHandler(
165      new SAX2StAXAdaptor(
166        annTypeH
167      )
168    );
169    this.schema = annTypeH.getAnnotationType();
170    
171    this.annBuilder = new AnnotationBuilder(schema);
172    this.parserListener = plFactory.getParserListener(annBuilder);
173    this.recordParser = new Parser();
174  }
175  
176  public String getName() {
177    return store.getName();
178  }
179
180  public AnnotationType getSchema() {
181    return schema;
182  }
183  
184  public Iterator iterator() {
185    return new Iterator() {
186      Iterator rli = store.getRecordList().iterator();
187      
188      public boolean hasNext() {
189        return rli.hasNext();
190      }
191      
192      public Object next() {
193        try {
194          return process((Record) rli.next());
195        } catch (Exception e) {
196          throw new RuntimeException(e);
197        }
198      }
199      
200      public void remove() {
201        throw new UnsupportedOperationException();
202      }
203    };
204  }
205  
206  public int size() {
207    return store.getRecordList().size();
208  }
209  
210  public AnnotationDB filter(AnnotationType at) {
211    AnnotationType schema = AnnotationTools.intersection(at, this.schema);
212    
213    if(schema != AnnotationType.NONE) {
214      return new LazyFilteredAnnotationDB("", this, schema);
215    } else {
216      return AnnotationDB.EMPTY;
217    }
218  }
219  
220  public AnnotationDB search(AnnotationType at) {
221    return new LazySearchedAnnotationDB("", this, at);
222  }
223
224  /**
225   * Get the ParserListenerFactory used by this IndexedAnnotationDB.
226   *
227   * @return the ParserListenerFactory
228   */
229  public ParserListenerFactory getParserListenerFactory() {
230    return plFactory;
231  }
232  
233  private Annotation process(Record rec)
234  throws IOException, ParserException {
235    RandomAccessReader rar = new RandomAccessReader(rec.getFile());
236    rar.seek(rec.getOffset());
237    BufferedReader reader = new BufferedReader(rar);
238    recordParser.read(reader, parserListener.getParser(), parserListener.getListener());
239    return annBuilder.getLast();
240  }
241
242  /**
243   * A factory for retrieving parsers and listeners.
244   *
245   * @author Matthew Pocock
246   * @since 1.3
247   */
248  public static interface ParserListenerFactory
249  extends Serializable {
250    /**
251     * Get the ParserListener for a TagValueListener.
252     *
253     * @param listener the TagValueListener to process
254     * @return the ParserListener for this
255     */
256    public ParserListener getParserListener(TagValueListener listener);
257  }
258
259  /**
260   * An implementation of ParserListenerFactory that uses a static method.
261   *
262   * @author Matthew Pocock
263   * @since 1.3
264   */
265  public static class StaticMethodRPFactory
266  implements ParserListenerFactory {
267    private final  Method method;
268
269    /**
270     * Create a new StaticMethodRPFactory for a method.
271     *
272     * @param method  a Method to use
273     * @throws IllegalArgumentException  if the Method is not statically scoped,
274     *    or does not return a ParserListener or take a single argument of type
275     *    TagValueListener
276     */
277    public StaticMethodRPFactory(Method method)
278    throws IllegalArgumentException {
279      if( (method.getModifiers() & Modifier.STATIC) != Modifier.STATIC ) {
280        throw new IllegalArgumentException("Method must be static");
281      }
282      
283      if(method.getReturnType() != ParserListener.class) {
284        throw new IllegalArgumentException("Method must return a ParserListener instance");
285      }
286      
287      if(
288        method.getParameterTypes().length != 1 ||
289        method.getParameterTypes()[0] != TagValueListener.class
290      ) {
291        throw new IllegalArgumentException("Method must accept a single TagValueListener as it's sole parameter");
292      }
293      
294      this.method = method;
295    }
296
297    /**
298     * Get the Method used.
299     *
300     * @return  the Method used.
301     */
302    public Method getMethod() {
303      return method;
304    }
305    
306    public ParserListener getParserListener(TagValueListener tvl) {
307      try {
308        return (ParserListener) method.invoke(null, new Object[] { tvl });
309      } catch (Exception e) {
310        throw new AssertionFailure("Could not invoke underlying method.", e);
311      }
312    }
313  }
314}
315