SerialCorpusImpl.java
0001 /*
0002  *  SerialCorpusImpl.java
0003  *
0004  *  Copyright (c) 1995-2012, The University of Sheffield. See the file
0005  *  COPYRIGHT.txt in the software or at http://gate.ac.uk/gate/COPYRIGHT.txt
0006  *
0007  *  This file is part of GATE (see http://gate.ac.uk/), and is free
0008  *  software, licenced under the GNU Library General Public License,
0009  *  Version 2, June 1991 (in the distribution as file licence.html,
0010  *  and also available at http://gate.ac.uk/gate/licence.html).
0011  *
0012  *  Kalina Bontcheva, 19/Oct/2001
0013  *
0014  *  $Id: SerialCorpusImpl.java 17841 2014-04-16 12:35:41Z markagreenwood $
0015  */
0016 
0017 package gate.corpora;
0018 
0019 import gate.Corpus;
0020 import gate.DataStore;
0021 import gate.Document;
0022 import gate.Factory;
0023 import gate.FeatureMap;
0024 import gate.Gate;
0025 import gate.GateConstants;
0026 import gate.Resource;
0027 import gate.creole.AbstractLanguageResource;
0028 import gate.creole.CustomDuplication;
0029 import gate.creole.ResourceInstantiationException;
0030 import gate.creole.ir.IREngine;
0031 import gate.creole.ir.IndexDefinition;
0032 import gate.creole.ir.IndexException;
0033 import gate.creole.ir.IndexManager;
0034 import gate.creole.ir.IndexStatistics;
0035 import gate.creole.ir.IndexedCorpus;
0036 import gate.creole.metadata.CreoleResource;
0037 import gate.event.CorpusEvent;
0038 import gate.event.CorpusListener;
0039 import gate.event.CreoleEvent;
0040 import gate.event.CreoleListener;
0041 import gate.event.DatastoreEvent;
0042 import gate.event.DatastoreListener;
0043 import gate.persist.PersistenceException;
0044 import gate.util.Err;
0045 import gate.util.GateRuntimeException;
0046 import gate.util.MethodNotImplementedException;
0047 import gate.util.Out;
0048 
0049 import java.io.FileFilter;
0050 import java.io.IOException;
0051 import java.io.ObjectInputStream;
0052 import java.net.URL;
0053 import java.util.ArrayList;
0054 import java.util.Collection;
0055 import java.util.Iterator;
0056 import java.util.List;
0057 import java.util.ListIterator;
0058 import java.util.Vector;
0059 
0060 // The initial design was to implement this on the basis of a WeakValueHashMap.
0061 // However this creates problems, because the user might e.g., add a transient
0062 // document to the corpus and then if the Document variable goes out of scope
0063 // before sync() is called, nothing will be saved of the new document. Bad!
0064 // Instead, to cope with the unloading for memory saving use, I implemented
0065 // a documentUnload() method, which sets the in-memory copy to null but can
0066 // always restore the doc, because it has its persistence ID.
0067 
0068 @CreoleResource(name = "GATE Serial Corpus", isPrivate = true, comment = "GATE persistent corpus (serialisation)", icon = "corpus", helpURL = "http://gate.ac.uk/userguide/sec:developer:datastores")
0069 public class SerialCorpusImpl extends AbstractLanguageResource 
0070     implements Corpus, CreoleListener, DatastoreListener, IndexedCorpus,
0071     CustomDuplication {
0072 
0073   /** Debug flag */
0074   private static final boolean DEBUG = false;
0075 
0076   static final long serialVersionUID = 3632609241787241616L;
0077 
0078   protected transient Vector<CorpusListener> corpusListeners;
0079 
0080   protected List<DocumentData> docDataList = null;
0081 
0082   // here I keep document index as key (same as the index in docDataList
0083   // which defines the document order) and Documents as value
0084   protected transient List<Document> documents = null;
0085 
0086   protected transient IndexManager indexManager = null;
0087 
0088   protected transient List<Document> addedDocs = null;
0089 
0090   protected transient List<String> removedDocIDs = null;
0091 
0092   protected transient List<Document> changedDocs = null;
0093 
0094   public SerialCorpusImpl() {
0095   }
0096 
0097   /**
0098    * Constructor to create a SerialCorpus from a transient one. This is
0099    * called by adopt() to store the transient corpus and re-route the
0100    * methods calls to it, until the corpus is sync-ed on disk. After
0101    * that, the transientCorpus will always be null, so the new
0102    * functionality will be used instead.
0103    */
0104   protected SerialCorpusImpl(Corpus tCorpus) {
0105     // copy the corpus name and features from the one in memory
0106     this.setName(tCorpus.getName());
0107     this.setFeatures(tCorpus.getFeatures());
0108 
0109     docDataList = new ArrayList<DocumentData>();
0110     // now cache the names of all docs for future use
0111     List<String> docNames = tCorpus.getDocumentNames();
0112     for(int i = 0; i < docNames.size(); i++) {
0113       Document doc = tCorpus.get(i);
0114       docDataList.add(new DocumentData(docNames.get(i), null, doc
0115               .getClass().getName()));
0116     }
0117 
0118     // copy all the documents from the transient corpus
0119     documents = new ArrayList<Document>();
0120     documents.addAll(tCorpus);
0121 
0122     // make sure we fire events when docs are added/removed/etc
0123     Gate.getCreoleRegister().addCreoleListener(this);
0124   }
0125 
0126   /**
0127    * Gets the names of the documents in this corpus.
0128    
0129    @return {@link List} of Strings representing the names of the
0130    *         documents in this corpus.
0131    */
0132   @Override
0133   public List<String> getDocumentNames() {
0134     List<String> docsNames = new ArrayList<String>();
0135     if(docDataList == nullreturn docsNames;
0136     for(Object aDocDataList : docDataList) {
0137       DocumentData data = (DocumentData)aDocDataList;
0138       docsNames.add(data.getDocumentName());
0139     }
0140     return docsNames;
0141   }
0142 
0143   /**
0144    * Gets the persistent IDs of the documents in this corpus.
0145    
0146    @return {@link List} of Objects representing the persistent IDs
0147    *         of the documents in this corpus.
0148    */
0149   public List<Object> getDocumentPersistentIDs() {
0150     List<Object> docsIDs = new ArrayList<Object>();
0151     if(docDataList == nullreturn docsIDs;
0152     Iterator<DocumentData> iter = docDataList.iterator();
0153     while(iter.hasNext()) {
0154       DocumentData data = iter.next();
0155       docsIDs.add(data.getPersistentID());
0156     }
0157     return docsIDs;
0158   }
0159 
0160   /**
0161    * Gets the persistent IDs of the documents in this corpus.
0162    
0163    @return {@link List} of Objects representing the persistent IDs
0164    *         of the documents in this corpus.
0165    */
0166   public List<String> getDocumentClassTypes() {
0167     List<String> docsIDs = new ArrayList<String>();
0168     if(docDataList == nullreturn docsIDs;
0169     Iterator<DocumentData> iter = docDataList.iterator();
0170     while(iter.hasNext()) {
0171       DocumentData data = iter.next();
0172       docsIDs.add(data.getClassType());
0173     }
0174     return docsIDs;
0175   }
0176 
0177   /**
0178    * This method should only be used by the Serial Datastore to set
0179    */
0180   public void setDocumentPersistentID(int index, Object persID) {
0181     if(index >= docDataList.size()) return;
0182     docDataList.get(index).setPersistentID(persID);
0183     if(DEBUGOut.prln("IDs are now: " + docDataList);
0184   }
0185 
0186   /**
0187    * Gets the name of a document in this corpus.
0188    
0189    @param index the index of the document
0190    @return a String value representing the name of the document at
0191    *         <tt>index</tt> in this corpus.
0192    *         <P>
0193    */
0194   @Override
0195   public String getDocumentName(int index) {
0196     if(index >= docDataList.size()) return "No such document";
0197 
0198     return docDataList.get(index).getDocumentName();
0199   }
0200 
0201   /**
0202    * Gets the persistent ID of a document in this corpus.
0203    
0204    @param index the index of the document
0205    @return a value representing the persistent ID of the document at
0206    *         <tt>index</tt> in this corpus.
0207    *         <P>
0208    */
0209   public Object getDocumentPersistentID(int index) {
0210     if(index >= docDataList.size()) return null;
0211     return docDataList.get(index).getPersistentID();
0212   }
0213 
0214   public String getDocumentClassType(int index) {
0215     if(index >= docDataList.size()) return null;
0216     return docDataList.get(index).getClassType();
0217   }
0218 
0219   /**
0220    * Unloads a document from memory.
0221    
0222    @param index the index of the document to be unloaded.
0223    @param sync should the document be sync'ed (i.e. saved) before
0224    *          unloading.
0225    */
0226   public void unloadDocument(int index, boolean sync) {
0227     // 1. check whether its been loaded and is a persistent one
0228     // if a persistent doc is not loaded, there's nothing we need to do
0229     if((!isDocumentLoaded(index)) && isPersistentDocument(index)) return;
0230     // 2. If requested, sync the document before releasing it from
0231     // memory,
0232     // because the creole register garbage collects all LRs which are
0233     // not used
0234     // any more
0235     if(sync) {
0236       Document doc = documents.get(index);
0237       try {
0238         // if the document is not already adopted, we need to do that
0239         // first
0240         if(doc.getLRPersistenceId() == null) {
0241           doc = (Document)this.getDataStore().adopt(doc);
0242           this.getDataStore().sync(doc);
0243           this.setDocumentPersistentID(index, doc.getLRPersistenceId());
0244         }
0245         else // if it is adopted, just sync it
0246         this.getDataStore().sync(doc);
0247       }
0248       catch(PersistenceException ex) {
0249         throw new GateRuntimeException("Error unloading document from corpus"
0250                 "because document sync failed: " + ex.getMessage(), ex);
0251       }
0252     }
0253     // 3. remove the document from the memory
0254     // do this, only if the saving has succeeded
0255     documents.set(index, null);
0256   }
0257 
0258   /**
0259    * Unloads a document from memory
0260    
0261    @param doc the document to be unloaded
0262    @param sync should the document be sync'ed (i.e. saved) before
0263    *          unloading.
0264    */
0265   public void unloadDocument(Document doc, boolean sync) {
0266     if(DEBUGOut.prln("Document to be unloaded :" + doc.getName());
0267     // 1. determine the index of the document; if not there, do nothing
0268     int index = findDocument(doc);
0269     if(index == -1return;
0270     if(DEBUGOut.prln("Index of doc: " + index);
0271     if(DEBUGOut.prln("Size of corpus: " + documents.size());
0272     unloadDocument(index, sync);
0273     // documents.remove(new Integer(index));
0274   }
0275 
0276   /**
0277    * Unloads a document from memory, calling sync() first, to store the
0278    * changes.
0279    
0280    @param doc the document to be unloaded.
0281    */
0282   @Override
0283   public void unloadDocument(Document doc) {
0284     unloadDocument(doc, true);
0285   }
0286 
0287   /**
0288    * Unloads the document from memory, calling sync() first, to store
0289    * the changes.
0290    
0291    @param index the index of the document to be unloaded.
0292    */
0293   public void unloadDocument(int index) {
0294     unloadDocument(index, true);
0295   }
0296 
0297   /**
0298    * This method returns true when the document is already loaded in
0299    * memory
0300    */
0301   @Override
0302   public boolean isDocumentLoaded(int index) {
0303     if(documents == null || documents.isEmpty()) return false;
0304     return documents.get(index!= null;
0305   }
0306 
0307   /**
0308    * This method returns true when the document is already stored on
0309    * disk i.e., is not transient
0310    */
0311   public boolean isPersistentDocument(int index) {
0312     if(documents == null || documents.isEmpty()) return false;
0313     return (docDataList.get(index).getPersistentID() != null);
0314   }
0315 
0316   /**
0317    * Every LR that is a CreoleListener (and other Listeners too) must
0318    * override this method and make sure it removes itself from the
0319    * objects which it has been listening to. Otherwise, the object will
0320    * not be released from memory (memory leak!).
0321    */
0322   @Override
0323   public void cleanup() {
0324     if(DEBUGOut.prln("serial corpus cleanup called");
0325     if(corpusListeners != nullcorpusListeners = null;
0326     if(documents != nulldocuments.clear();
0327     docDataList.clear();
0328     Gate.getCreoleRegister().removeCreoleListener(this);
0329     if(this.dataStore != null) {
0330       this.dataStore.removeDatastoreListener(this);
0331     }
0332   }
0333 
0334   /**
0335    * Fills this corpus with documents created from files in a directory.
0336    
0337    @param filter the file filter used to select files from the target
0338    *          directory. If the filter is <tt>null</tt> all the files
0339    *          will be accepted.
0340    @param directory the directory from which the files will be picked.
0341    *          This parameter is an URL for uniformity. It needs to be a
0342    *          URL of type file otherwise an InvalidArgumentException
0343    *          will be thrown. An implementation for this method is
0344    *          provided as a static method at
0345    *          {@link gate.corpora.CorpusImpl#populate(Corpus, URL, FileFilter, String, boolean)}
0346    *          .
0347    @param encoding the encoding to be used for reading the documents
0348    @param recurseDirectories should the directory be parsed
0349    *          recursively?. If <tt>true</tt> all the files from the
0350    *          provided directory and all its children directories (on as
0351    *          many levels as necessary) will be picked if accepted by
0352    *          the filter otherwise the children directories will be
0353    *          ignored.
0354    */
0355   @Override
0356   public void populate(URL directory, FileFilter filter, String encoding,
0357           boolean recurseDirectoriesthrows IOException,
0358           ResourceInstantiationException {
0359     CorpusImpl.populate(this, directory, filter, encoding, recurseDirectories);
0360   }
0361 
0362   /**
0363    * Fills this corpus with documents created from files in a directory.
0364    
0365    @param filter the file filter used to select files from the target
0366    *          directory. If the filter is <tt>null</tt> all the files
0367    *          will be accepted.
0368    @param directory the directory from which the files will be picked.
0369    *          This parameter is an URL for uniformity. It needs to be a
0370    *          URL of type file otherwise an InvalidArgumentException
0371    *          will be thrown. An implementation for this method is
0372    *          provided as a static method at
0373    *          {@link gate.corpora.CorpusImpl#populate(Corpus, URL, FileFilter, String, boolean)}
0374    *          .
0375    @param encoding the encoding to be used for reading the documents
0376    @param recurseDirectories should the directory be parsed
0377    *          recursively?. If <tt>true</tt> all the files from the
0378    *          provided directory and all its children directories (on as
0379    *          many levels as necessary) will be picked if accepted by
0380    *          the filter otherwise the children directories will be
0381    *          ignored.
0382    */
0383   @Override
0384   public void populate(URL directory, FileFilter filter, String encoding,
0385           String mimeType, boolean recurseDirectoriesthrows IOException,
0386           ResourceInstantiationException {
0387     CorpusImpl.populate(this, directory, filter, encoding, mimeType,
0388             recurseDirectories);
0389   }
0390 
0391   /**
0392    * Fills the provided corpus with documents extracted from the
0393    * provided single concatenated file.
0394    
0395    @param singleConcatenatedFile the single concatenated file.
0396    @param documentRootElement content between the start and end of
0397    *          this element is considered for documents.
0398    @param encoding the encoding of the trec file.
0399    @param numberOfFilesToExtract indicates the number of files to
0400    *          extract from the trecweb file.
0401    @param documentNamePrefix the prefix to use for document names when
0402    *          creating from
0403    @param mimeType the mime type which determines how the document is handled
0404    @return total length of populated documents in the corpus in number
0405    *         of bytes
0406    */  
0407   @Override
0408   public long populate(URL singleConcatenatedFile, String documentRootElement,
0409           String encoding, int numberOfFilesToExtract,
0410           String documentNamePrefix, String mimeType, boolean includeRootElementthrows IOException,
0411           ResourceInstantiationException {
0412     return CorpusImpl.populate(this, singleConcatenatedFile,
0413             documentRootElement, encoding, numberOfFilesToExtract,
0414             documentNamePrefix, mimeType, includeRootElement);
0415   }
0416 
0417   @Override
0418   public synchronized void removeCorpusListener(CorpusListener l) {
0419     if(corpusListeners != null && corpusListeners.contains(l)) {
0420       @SuppressWarnings("unchecked")
0421       Vector<CorpusListener> v = (Vector<CorpusListener>)corpusListeners.clone();
0422       v.removeElement(l);
0423       corpusListeners = v;
0424     }
0425   }
0426 
0427   @Override
0428   public synchronized void addCorpusListener(CorpusListener l) {
0429     @SuppressWarnings("unchecked")
0430     Vector<CorpusListener> v = corpusListeners == null
0431             new Vector<CorpusListener>(2)
0432             (Vector<CorpusListener>)corpusListeners.clone();
0433     if(!v.contains(l)) {
0434       v.addElement(l);
0435       corpusListeners = v;
0436     }
0437   }
0438 
0439   protected void fireDocumentAdded(CorpusEvent e) {
0440     if(corpusListeners != null) {
0441       Vector<CorpusListener> listeners = corpusListeners;
0442       int count = listeners.size();
0443       for(int i = 0; i < count; i++) {
0444         listeners.elementAt(i).documentAdded(e);
0445       }
0446     }
0447   }
0448 
0449   protected void fireDocumentRemoved(CorpusEvent e) {
0450     if(corpusListeners != null) {
0451       Vector<CorpusListener> listeners = corpusListeners;
0452       int count = listeners.size();
0453       for(int i = 0; i < count; i++) {
0454         listeners.elementAt(i).documentRemoved(e);
0455       }
0456     }
0457   }
0458 
0459   @Override
0460   public void resourceLoaded(CreoleEvent e) {
0461   }
0462 
0463   @Override
0464   public void resourceRenamed(Resource resource, String oldName, String newName) {
0465   }
0466 
0467   @Override
0468   public void resourceUnloaded(CreoleEvent e) {
0469     Resource res = e.getResource();
0470     if(res instanceof Document) {
0471       Document doc = (Document)res;
0472       if(DEBUGOut.prln("resource Unloaded called ");
0473       // remove from the corpus too, if a transient one
0474       if(doc.getDataStore() != this.getDataStore()) {
0475         this.remove(doc);
0476       }
0477       else {
0478         // unload all occurences
0479         int index = indexOf(res);
0480         if(index < 0return;
0481         documents.set(index, null);
0482         if(DEBUG)
0483           Out.prln("corpus: document " + index + " unloaded and set to null");
0484       // if
0485     }
0486   }
0487 
0488   @Override
0489   public void datastoreOpened(CreoleEvent e) {
0490   }
0491 
0492   @Override
0493   public void datastoreCreated(CreoleEvent e) {
0494   }
0495 
0496   @Override
0497   public void datastoreClosed(CreoleEvent e) {
0498     if(!e.getDatastore().equals(this.getDataStore())) return;
0499     if(this.getDataStore() != null)
0500       this.getDataStore().removeDatastoreListener(this);
0501     // close this corpus, since it cannot stay open when the DS it comes
0502     // from
0503     // is closed
0504     Factory.deleteResource(this);
0505   }
0506 
0507   /**
0508    * Called by a datastore when a new resource has been adopted
0509    */
0510   @Override
0511   public void resourceAdopted(DatastoreEvent evt) {
0512   }
0513 
0514   /**
0515    * Called by a datastore when a resource has been deleted
0516    */
0517   @Override
0518   public void resourceDeleted(DatastoreEvent evt) {
0519     DataStore ds = (DataStore)evt.getSource();
0520     // 1. check whether this datastore fired the event. If not, return.
0521     if(!ds.equals(this.dataStore)) return;
0522 
0523     Object docID = evt.getResourceID();
0524     if(docID == nullreturn;
0525 
0526     if(DEBUGOut.prln("Resource deleted called for: " + docID);
0527     // first check if it is this corpus that's been deleted, it must be
0528     // unloaded immediately
0529     if(docID.equals(this.getLRPersistenceId())) {
0530       Factory.deleteResource(this);
0531       return;
0532     }// if
0533 
0534     boolean isDirty = false;
0535     // the problem here is that I only have the doc persistent ID
0536     // and nothing else, so I need to determine the index of the doc
0537     // first
0538     for(int i = 0; i < docDataList.size(); i++) {
0539       DocumentData docData = docDataList.get(i);
0540       // we've found the correct document
0541       // don't break the loop, because it might appear more than once
0542       if(docID.equals(docData.getPersistentID())) {
0543         if(evt.getResource() == null) {
0544           // instead of calling remove() which tries to load the
0545           // document
0546           // remove it from the documents and docDataList
0547           documentRemoved(docDataList.get(i).persistentID
0548                   .toString());
0549           docDataList.remove(i);
0550           documents.remove(i);
0551           isDirty = true;
0552           i--;
0553           continue;
0554         }
0555 
0556         remove(i);
0557         isDirty = true;
0558       }// if
0559     }// for loop through the doc data
0560 
0561     if(isDirtytry {
0562       this.dataStore.sync(this);
0563     }
0564     catch(PersistenceException ex) {
0565       throw new GateRuntimeException("SerialCorpusImpl: " + ex.getMessage());
0566     }
0567     catch(SecurityException sex) {
0568       throw new GateRuntimeException("SerialCorpusImpl: " + sex.getMessage());
0569     }
0570   }// resourceDeleted
0571 
0572   /**
0573    * Called by a datastore when a resource has been wrote into the
0574    * datastore
0575    */
0576   @Override
0577   public void resourceWritten(DatastoreEvent evt) {
0578     if(evt.getResourceID().equals(this.getLRPersistenceId())) {
0579       thisResourceWritten();
0580     }
0581   }
0582 
0583   // List methods
0584   // java docs will be automatically copied from the List interface.
0585 
0586   @Override
0587   public int size() {
0588     return docDataList.size();
0589   }
0590 
0591   @Override
0592   public boolean isEmpty() {
0593     return docDataList.isEmpty();
0594   }
0595 
0596   @Override
0597   public boolean contains(Object o) {
0598     // return true if:
0599     // - the document data list contains a document with such a name
0600     // and persistent id
0601 
0602     if(!(instanceof Document)) return false;
0603 
0604     int index = findDocument((Document)o);
0605     if(index < 0)
0606       return false;
0607     else return true;
0608   }
0609 
0610   @Override
0611   public Iterator<Document> iterator() {
0612     return new Iterator<Document>() {
0613       Iterator<DocumentData> docDataIter = docDataList.iterator();
0614 
0615       @Override
0616       public boolean hasNext() {
0617         return docDataIter.hasNext();
0618       }
0619 
0620       @Override
0621       public Document next() {
0622 
0623         // try finding a document with the same name and persistent ID
0624         DocumentData docData = docDataIter.next();
0625         int index = docDataList.indexOf(docData);
0626         return SerialCorpusImpl.this.get(index);
0627       }
0628 
0629       @Override
0630       public void remove() {
0631         throw new UnsupportedOperationException("SerialCorpusImpl does not "
0632                 "support remove in the iterators");
0633       }
0634     }// return
0635 
0636   }// iterator
0637 
0638   @Override
0639   public String toString() {
0640     return "document data " + docDataList.toString() " documents "
0641             + documents;
0642   }
0643 
0644   @Override
0645   public Object[] toArray() {
0646     // there is a problem here, because some docs might not be
0647     // instantiated
0648     throw new MethodNotImplementedException(
0649             "toArray() is not implemented for SerialCorpusImpl");
0650   }
0651 
0652   @Override
0653   public <T> T[] toArray(T[] a) {
0654     // there is a problem here, because some docs might not be
0655     // instantiated
0656     throw new MethodNotImplementedException(
0657             "toArray(Object[] a) is not implemented for SerialCorpusImpl");
0658   }
0659 
0660   @Override
0661   public boolean add(Document o) {
0662     if(o == nullreturn false;
0663     Document doc = o;
0664 
0665     // make it accept only docs from its own datastore
0666     if(doc.getDataStore() != null && !this.dataStore.equals(doc.getDataStore())) {
0667       Err.prln("Error: Persistent corpus can only accept documents "
0668               "from its own datastore!");
0669       return false;
0670     }// if
0671 
0672     // add the document with its index in the docDataList
0673     // in this case, since it's going to be added to the end
0674     // the index will be the size of the docDataList before
0675     // the addition
0676     DocumentData docData = new DocumentData(doc.getName(), doc
0677             .getLRPersistenceId(), doc.getClass().getName());
0678     boolean result = docDataList.add(docData);
0679     documents.add(doc);
0680     documentAdded(doc);
0681     fireDocumentAdded(new CorpusEvent(SerialCorpusImpl.this, doc, docDataList
0682             .size() 1, doc.getLRPersistenceId(), CorpusEvent.DOCUMENT_ADDED));
0683 
0684     return result;
0685   }
0686 
0687   @Override
0688   public boolean remove(Object o) {
0689     if(DEBUGOut.prln("SerialCorpus:Remove object called");
0690     if(!(instanceof Document)) return false;
0691     Document doc = (Document)o;
0692 
0693     // see if we can find it first. If not, then judt return
0694     int index = findDocument(doc);
0695     if(index == -1return false;
0696 
0697     if(index < docDataList.size()) { // we found it, so remove it
0698       // by Andrey Shafirin: this part of code can produce an exception
0699       // if
0700       // document wasn't loaded
0701       String docName = docDataList.get(index).getDocumentName();
0702       Object docPersistentID = getDocumentPersistentID(index);
0703       docDataList.remove(index);
0704       // Document oldDoc = (Document) documents.remove(index);
0705       documents.remove(index);
0706       // if (DEBUG) Out.prln("documents after remove of " +
0707       // oldDoc.getName()
0708       // + " are " + documents);
0709       if(DEBUG)
0710         Out.prln("documents after remove of " + docName + " are " + documents);
0711       // documentRemoved(oldDoc.getLRPersistenceId().toString());
0712       if(docPersistentID != nulldocumentRemoved(docPersistentID.toString());
0713       // fireDocumentRemoved(new CorpusEvent(SerialCorpusImpl.this,
0714       // oldDoc,
0715       // index,
0716       // CorpusEvent.DOCUMENT_REMOVED));
0717       fireDocumentRemoved(new CorpusEvent(SerialCorpusImpl.this, (Document)o,
0718               index, docPersistentID, CorpusEvent.DOCUMENT_REMOVED));
0719     }
0720 
0721     return true;
0722   }
0723 
0724   public int findDocument(Document doc) {
0725     boolean found = false;
0726     DocumentData docData = null;
0727 
0728     // first try finding the document in memory
0729     int index = documents.indexOf(doc);
0730     if(index > -&& index < docDataList.size()) return index;
0731 
0732     // else try finding a document with the same name and persistent ID
0733     Iterator<DocumentData> iter = docDataList.iterator();
0734     for(index = 0; iter.hasNext(); index++) {
0735       docData = iter.next();
0736       if(docData.getDocumentName().equals(doc.getName())
0737               && docData.getPersistentID().equals(doc.getLRPersistenceId())
0738               && docData.getClassType().equals(doc.getClass().getName())) {
0739         found = true;
0740         break;
0741       }
0742     }
0743     if(found && index < docDataList.size())
0744       return index;
0745     else return -1;
0746   }// findDocument
0747 
0748   @Override
0749   public boolean containsAll(Collection<?> c) {
0750     Iterator<?> iter = c.iterator();
0751     while(iter.hasNext()) {
0752       if(!contains(iter.next())) return false;
0753     }
0754     return true;
0755   }
0756 
0757   @Override
0758   public boolean addAll(Collection<? extends Document> c) {
0759     boolean allAdded = true;
0760     Iterator<? extends Document> iter = c.iterator();
0761     while(iter.hasNext()) {
0762       if(!add(iter.next())) allAdded = false;
0763     }
0764     return allAdded;
0765   }
0766 
0767   @Override
0768   public boolean addAll(int index, Collection<? extends Document> c) {
0769     throw new UnsupportedOperationException();
0770   }
0771 
0772   @Override
0773   public boolean removeAll(Collection<?> c) {
0774     boolean allRemoved = true;
0775     Iterator<?> iter = c.iterator();
0776     while(iter.hasNext()) {
0777       if(!remove(iter.next())) allRemoved = false;
0778     }
0779     return allRemoved;
0780 
0781   }
0782 
0783   @Override
0784   public boolean retainAll(Collection<?> c) {
0785     throw new UnsupportedOperationException();
0786   }
0787 
0788   @Override
0789   public void clear() {
0790     documents.clear();
0791     docDataList.clear();
0792   }
0793 
0794   @Override
0795   public boolean equals(Object o) {
0796     if(o == nullreturn false;
0797     if(!(instanceof SerialCorpusImpl)) return false;
0798     SerialCorpusImpl oCorpus = (SerialCorpusImpl)o;    
0799     if(oCorpus == thisreturn true;
0800     if((oCorpus.lrPersistentId == this.lrPersistentId || (this.lrPersistentId != null && this.lrPersistentId
0801             .equals(oCorpus.lrPersistentId)))
0802             && oCorpus.name.equals(this.name)
0803             && (oCorpus.dataStore == this.dataStore || oCorpus.dataStore
0804                     .equals(this.dataStore))
0805             && oCorpus.docDataList.equals(docDataList)) return true;
0806     return false;
0807   }
0808 
0809   @Override
0810   public int hashCode() {
0811     return docDataList.hashCode();
0812   }
0813 
0814   @Override
0815   public Document get(int index) {
0816     if(index >= docDataList.size()) return null;
0817 
0818     Document res = documents.get(index);
0819 
0820     if(DEBUG)
0821       Out.prln("SerialCorpusImpl: get(): index " + index + "result: " + res);
0822 
0823     // if the document is null, then I must get it from the DS
0824     if(res == null) {
0825       FeatureMap parameters = Factory.newFeatureMap();
0826       parameters.put(DataStore.DATASTORE_FEATURE_NAME, this.dataStore);
0827       try {
0828         parameters.put(DataStore.LR_ID_FEATURE_NAME, docDataList
0829                 .get(index).getPersistentID());
0830         Document lr = (DocumentFactory.createResource(docDataList
0831                 .get(index).getClassType(), parameters);
0832         if(DEBUGOut.prln("Loaded document :" + lr.getName());
0833         // change the result to the newly loaded doc
0834         res = lr;
0835 
0836         // finally replace the doc with the instantiated version
0837         documents.set(index, lr);
0838       }
0839       catch(ResourceInstantiationException ex) {
0840         Err.prln("Error reading document inside a serialised corpus.");
0841         throw new GateRuntimeException(ex);
0842       }
0843     }
0844 
0845     return res;
0846   }
0847 
0848   @Override
0849   public Document set(int index, Document element) {
0850     throw new gate.util.MethodNotImplementedException();
0851     // fire the 2 events
0852     /*
0853      * fireDocumentRemoved(new CorpusEvent(SerialCorpusImpl.this,
0854      * oldDoc, ((Integer) key).intValue(),
0855      * CorpusEvent.DOCUMENT_REMOVED)); fireDocumentAdded(new
0856      * CorpusEvent(SerialCorpusImpl.this, newDoc, ((Integer)
0857      * key).intValue(), CorpusEvent.DOCUMENT_ADDED));
0858      */
0859   }
0860 
0861   @Override
0862   public void add(int index, Document o) {
0863     if(o == nullreturn;
0864     Document doc = o;
0865 
0866     DocumentData docData = new DocumentData(doc.getName(), doc
0867             .getLRPersistenceId(), doc.getClass().getName());
0868     docDataList.add(index, docData);
0869 
0870     documents.add(index, doc);
0871     documentAdded(doc);
0872     fireDocumentAdded(new CorpusEvent(SerialCorpusImpl.this, doc, index, doc
0873             .getLRPersistenceId(), CorpusEvent.DOCUMENT_ADDED));
0874 
0875   }
0876 
0877   @Override
0878   public Document remove(int index) {
0879     if(DEBUGOut.prln("Remove index called");
0880     // try to get the actual document if it was loaded
0881     Document res = isDocumentLoaded(index? get(indexnull;
0882     Object docLRID = docDataList.get(index).persistentID;
0883     if(docLRID != nulldocumentRemoved(docLRID.toString());
0884     docDataList.remove(index);
0885     documents.remove(index);
0886     fireDocumentRemoved(new CorpusEvent(SerialCorpusImpl.this, res, index,
0887             docLRID, CorpusEvent.DOCUMENT_REMOVED));
0888     return res;
0889   }
0890 
0891   @Override
0892   public int indexOf(Object o) {
0893     if(instanceof Documentreturn findDocument((Document)o);
0894 
0895     return -1;
0896   }
0897 
0898   @Override
0899   public int lastIndexOf(Object o) {
0900     throw new gate.util.MethodNotImplementedException();
0901   }
0902 
0903   @Override
0904   public ListIterator<Document> listIterator() {
0905     throw new gate.util.MethodNotImplementedException();
0906   }
0907 
0908   @Override
0909   public ListIterator<Document> listIterator(int index) {
0910     throw new gate.util.MethodNotImplementedException();
0911   }
0912 
0913   /**
0914    * persistent Corpus does not support this method as all the documents
0915    * might no be in memory
0916    */
0917   @Override
0918   public List<Document> subList(int fromIndex, int toIndex) {
0919     throw new gate.util.MethodNotImplementedException();
0920   }
0921 
0922   @Override
0923   public void setDataStore(DataStore dataStore)
0924           throws gate.persist.PersistenceException {
0925     super.setDataStore(dataStore);
0926     if(this.dataStore != nullthis.dataStore.addDatastoreListener(this);
0927   }
0928 
0929   public void setTransientSource(Object source) {
0930     if(!(source instanceof Corpus)) return;
0931 
0932     // the following initialisation is only valid when we're
0933     // constructing
0934     // this object from a transient one. If it has already been stored
0935     // in
0936     // a datastore, then the initialisation is done in readObject()
0937     // since
0938     // this method is the one called by serialisation, when objects
0939     // are restored.
0940     if(this.dataStore != null && this.lrPersistentId != nullreturn;
0941 
0942     Corpus tCorpus = (Corpus)source;
0943 
0944     // copy the corpus name and features from the one in memory
0945     this.setName(tCorpus.getName());
0946     this.setFeatures(tCorpus.getFeatures());
0947 
0948     docDataList = new ArrayList<DocumentData>();
0949     // now cache the names of all docs for future use
0950     List<String> docNames = tCorpus.getDocumentNames();
0951     for(int i = 0; i < docNames.size(); i++) {
0952       Document aDoc = tCorpus.get(i);
0953       docDataList.add(new DocumentData(docNames.get(i), null, aDoc
0954               .getClass().getName()));
0955     }
0956 
0957     // copy all the documents from the transient corpus
0958     documents = new ArrayList<Document>();
0959     documents.addAll(tCorpus);
0960 
0961     this.addedDocs = new Vector<Document>();
0962     this.removedDocIDs = new Vector<String>();
0963     this.changedDocs = new Vector<Document>();
0964 
0965     // make sure we fire events when docs are added/removed/etc
0966     Gate.getCreoleRegister().addCreoleListener(this);
0967 
0968   }
0969 
0970   // we don't keep the transient source, so always return null
0971   // Sill this must be implemented, coz of the GUI and Factory
0972   public Object getTransientSource() {
0973     return null;
0974   }
0975 
0976   @Override
0977   public Resource init() throws gate.creole.ResourceInstantiationException {
0978     super.init();
0979 
0980     return this;
0981 
0982   }
0983 
0984   /**
0985    * readObject - calls the default readObject() and then initialises
0986    * the transient data
0987    
0988    @serialData Read serializable fields. No optional data read.
0989    */
0990   private void readObject(ObjectInputStream sthrows IOException,
0991           ClassNotFoundException {
0992     s.defaultReadObject();
0993     documents = new ArrayList<Document>(docDataList.size());
0994     for(int i = 0; i < docDataList.size(); i++)
0995       documents.add(null);
0996     corpusListeners = new Vector<CorpusListener>();
0997     // finally set the creole listeners if the LR is like that
0998     Gate.getCreoleRegister().addCreoleListener(this);
0999     if(this.dataStore != nullthis.dataStore.addDatastoreListener(this);
1000 
1001     // if indexed construct the manager.
1002     /*IndexDefinition definition = (IndexDefinition)this.getFeatures().get(
1003             GateConstants.CORPUS_INDEX_DEFINITION_FEATURE_KEY);
1004     if(definition != null) {
1005       String className = definition.getIrEngineClassName();
1006       try {
1007         // Class aClass = Class.forName(className);
1008         Class<?> aClass = Class.forName(className, true, Gate.getClassLoader());
1009         IREngine engine = (IREngine)aClass.newInstance();
1010         this.indexManager = engine.getIndexmanager();
1011         this.indexManager.setIndexDefinition(definition);
1012         this.indexManager.setCorpus(this);
1013       }
1014       catch(Exception e) {
1015         e.printStackTrace(Err.getPrintWriter());
1016       }
1017       // switch (definition.getIndexType()) {
1018       // case GateConstants.IR_LUCENE_INVFILE:
1019       // this.indexManager = new LuceneIndexManager();
1020       // this.indexManager.setIndexDefinition(definition);
1021       // this.indexManager.setCorpus(this);
1022       // break;
1023       // }
1024       this.addedDocs = new Vector<Document>();
1025       this.removedDocIDs = new Vector<String>();
1026       this.changedDocs = new Vector<Document>();
1027     }*/
1028   }// readObject
1029 
1030   @Override
1031   public void setIndexDefinition(IndexDefinition definition) {
1032     if(definition != null) {
1033       this.getFeatures().put(GateConstants.CORPUS_INDEX_DEFINITION_FEATURE_KEY,
1034               definition);
1035 
1036       String className = definition.getIrEngineClassName();
1037       try {
1038         // Class aClass = Class.forName(className);
1039         Class<?> aClass = Class.forName(className, true, Gate.getClassLoader());
1040         IREngine engine = (IREngine)aClass.newInstance();
1041         this.indexManager = engine.getIndexmanager();
1042         this.indexManager.setIndexDefinition(definition);
1043         this.indexManager.setCorpus(this);
1044       }
1045       catch(Exception e) {
1046         e.printStackTrace(Err.getPrintWriter());
1047       }
1048       // switch (definition.getIndexType()) {
1049       // case GateConstants.IR_LUCENE_INVFILE:
1050       // this.indexManager = new LuceneIndexManager();
1051       // this.indexManager.setIndexDefinition(definition);
1052       // this.indexManager.setCorpus(this);
1053       // break;
1054       // }
1055       this.addedDocs = new Vector<Document>();
1056       this.removedDocIDs = new Vector<String>();
1057       this.changedDocs = new Vector<Document>();
1058     }
1059   }
1060 
1061   @Override
1062   public IndexDefinition getIndexDefinition() {
1063     return (IndexDefinition)this.getFeatures().get(
1064             GateConstants.CORPUS_INDEX_DEFINITION_FEATURE_KEY);
1065   }
1066 
1067   @Override
1068   public IndexManager getIndexManager() {
1069     return this.indexManager;
1070   }
1071 
1072   @Override
1073   public IndexStatistics getIndexStatistics() {
1074     return (IndexStatistics)this.getFeatures().get(
1075             GateConstants.CORPUS_INDEX_STATISTICS_FEATURE_KEY);
1076   }
1077 
1078   private void documentAdded(Document doc) {
1079     if(indexManager != null) {
1080       addedDocs.add(doc);
1081     }
1082   }
1083 
1084   private void documentRemoved(String lrID) {
1085     if(indexManager != null) {
1086       removedDocIDs.add(lrID);
1087     }
1088   }
1089 
1090   private void thisResourceWritten() {
1091     if(indexManager != null) {
1092       try {
1093         for(int i = 0; i < documents.size(); i++) {
1094           if(documents.get(i!= null) {
1095             Document doc = documents.get(i);
1096             if(!addedDocs.contains(doc&& doc.isModified()) {
1097               changedDocs.add(doc);
1098             }
1099           }
1100         }
1101         indexManager.sync(addedDocs, removedDocIDs, changedDocs);
1102       }
1103       catch(IndexException ie) {
1104         ie.printStackTrace();
1105       }
1106     }
1107   }
1108 
1109   /**
1110    * SerialCorpusImpl does not support duplication.
1111    */
1112   @Override
1113   public Resource duplicate(Factory.DuplicationContext ctx)
1114           throws ResourceInstantiationException {
1115     throw new ResourceInstantiationException("Duplication of "
1116             this.getClass().getName() " not permitted");
1117   }
1118 
1119 }