1   /*
2    *  DatabaseCorpusImpl.java
3    *
4    *  Copyright (c) 1998-2001, The University of Sheffield.
5    *
6    *  This file is part of GATE (see http://gate.ac.uk/), and is free
7    *  software, licenced under the GNU Library General Public License,
8    *  Version 2, June 1991 (in the distribution as file licence.html,
9    *  and also available at http://gate.ac.uk/gate/licence.html).
10   *
11   *  Marin Dimitrov, 05/Nov/2001
12   *
13   *  $Id: DatabaseCorpusImpl.java,v 1.10 2002/03/07 19:36:46 marin Exp $
14   */
15  
16  package gate.corpora;
17  
18  import java.util.*;
19  
20  import junit.framework.*;
21  
22  import gate.*;
23  import gate.persist.*;
24  import gate.annotation.*;
25  import gate.creole.*;
26  import gate.event.*;
27  import gate.util.*;
28  
29  
30  public class DatabaseCorpusImpl extends CorpusImpl
31                                  implements DatastoreListener,
32                                             EventAwareCorpus {
33  
34    /** Debug flag */
35    private static final boolean DEBUG = false;
36  
37    private boolean featuresChanged;
38    private boolean nameChanged;
39    /**
40     * The listener for the events coming from the features.
41     */
42    protected EventsHandler eventHandler;
43    protected List documentData;
44    protected List removedDocuments;
45  
46    public DatabaseCorpusImpl() {
47      super();
48    }
49  
50  
51    public DatabaseCorpusImpl(String _name,
52                              DatabaseDataStore _ds,
53                              Long _persistenceID,
54                              FeatureMap _features,
55                              Vector _dbDocs) {
56  
57      super();
58  
59      this.name = _name;
60      this.dataStore = _ds;
61      this.lrPersistentId = _persistenceID;
62      this.features = _features;
63  //    this.supportList = _dbDocs;
64      this.documentData =  _dbDocs;
65      this.supportList = new ArrayList(this.documentData.size());
66      this.removedDocuments = new ArrayList();
67  
68      //init the document list
69      for (int i=0; i< this.documentData.size(); i++) {
70        this.supportList.add(null);
71      }
72  
73      this.featuresChanged = false;
74      this.nameChanged = false;
75  
76      //3. add the listeners for the features
77      if (eventHandler == null)
78        eventHandler = new EventsHandler();
79      this.features.addFeatureMapListener(eventHandler);
80  
81  
82      //4. add self as listener for the data store, so that we'll know when the DS is
83      //synced and we'll clear the isXXXChanged flags
84      this.dataStore.addDatastoreListener(this);
85    }
86  
87  
88    public boolean add(Object o){
89  
90      Assert.assertNotNull(o);
91      boolean result = false;
92  
93      //accept only documents
94      if (false == o instanceof Document) {
95        throw new IllegalArgumentException();
96      }
97  
98      Document doc = (Document)o;
99  
100     //assert docs are either transient or from the same datastore
101     if (isValidForAdoption(doc)) {
102       result = super.add(doc);
103     }
104 
105     //add to doc data too
106     DocumentData newDocData = new DocumentData(doc.getName(),null);
107     this.documentData.add(newDocData);
108 
109     if (result) {
110       fireDocumentAdded(new CorpusEvent(this,
111                                         doc,
112                                         this.supportList.size()-1,
113                                         CorpusEvent.DOCUMENT_ADDED));
114     }
115 
116     return result;
117   }
118 
119 
120   public void add(int index, Object element){
121 
122     Assert.assertNotNull(element);
123     Assert.assertTrue(index >= 0);
124 
125     long    collInitialSize = this.supportList.size();
126 
127     //accept only documents
128     if (false == element instanceof Document) {
129       throw new IllegalArgumentException();
130     }
131 
132     Document doc = (Document)element;
133 
134     //assert docs are either transient or from the same datastore
135     if (isValidForAdoption(doc)) {
136       super.add(index,doc);
137 
138       //add to doc data too
139       DocumentData newDocData = new DocumentData(doc.getName(),null);
140       this.documentData.add(index,newDocData);
141 
142       //if added then fire event
143       if (this.supportList.size() > collInitialSize) {
144         fireDocumentAdded(new CorpusEvent(this,
145                                           doc,
146                                           index,
147                                           CorpusEvent.DOCUMENT_ADDED));
148       }
149     }
150   }
151 
152 
153 
154   public boolean addAll(Collection c){
155 
156     boolean collectionChanged = false;
157 
158     Iterator it = c.iterator();
159     while (it.hasNext()) {
160       Document doc = (Document)it.next();
161       if (isValidForAdoption(doc)) {
162         collectionChanged |= add(doc);
163       }
164     }
165 
166     return collectionChanged;
167   }
168 
169 
170   public boolean addAll(int index, Collection c){
171 
172     Assert.assertTrue(index >=0);
173 
174     //funny enough add(index,element) returns void and not boolean
175     //so we can't use it
176     boolean collectionChanged = false;
177     int collInitialSize = this.supportList.size();
178     int currIndex = index;
179 
180     Iterator it = c.iterator();
181     while (it.hasNext()) {
182       Document doc = (Document)it.next();
183       if (isValidForAdoption(doc)) {
184         add(currIndex++,doc);
185       }
186     }
187 
188     return (this.supportList.size() > collInitialSize);
189   }
190 
191 
192   private boolean isValidForAdoption(LanguageResource lr) {
193 
194     Long lrID = (Long)lr.getLRPersistenceId();
195 
196     if (null == lrID ||
197         (this.getDataStore() != null && lr.getDataStore().equals(this.getDataStore()))) {
198       return true;
199     }
200     else {
201       return false;
202     }
203   }
204 
205   public void resourceAdopted(DatastoreEvent evt){
206   }
207 
208   public void resourceDeleted(DatastoreEvent evt){
209 
210     Assert.assertNotNull(evt);
211     Long  deletedID = (Long)evt.getResourceID();
212     Assert.assertNotNull(deletedID);
213 
214     //unregister self as listener from the DataStore
215     if (deletedID.equals(this.getLRPersistenceId())) {
216       //someone deleted this corpus
217       this.supportList.clear();
218       getDataStore().removeDatastoreListener(this);
219     }
220 
221     //check if the ID is of a document the corpus contains
222     Iterator it = this.supportList.iterator();
223     while (it.hasNext()) {
224       Document doc = (Document)it.next();
225       if (doc.getLRPersistenceId().equals(deletedID)) {
226         this.supportList.remove(doc);
227         break;
228       }
229     }
230   }
231 
232   public void resourceWritten(DatastoreEvent evt){
233     Assert.assertNotNull(evt);
234     Assert.assertNotNull(evt.getResourceID());
235 
236     //is the event for us?
237     if (evt.getResourceID().equals(this.getLRPersistenceId())) {
238       //wow, the event is for me
239       //clear all flags, the content is synced with the DB
240       this.featuresChanged =
241         this.nameChanged = false;
242 
243       this.removedDocuments.clear();
244     }
245   }
246 
247   public boolean isResourceChanged(int changeType) {
248 
249     switch(changeType) {
250 
251       case EventAwareLanguageResource.RES_FEATURES:
252         return this.featuresChanged;
253       case EventAwareLanguageResource.RES_NAME:
254         return this.nameChanged;
255       default:
256         throw new IllegalArgumentException();
257     }
258   }
259 
260   /**
261    * Returns true of an LR has been modified since the last sync.
262    * Always returns false for transient LRs.
263    */
264   public boolean isModified() {
265     return this.isResourceChanged(EventAwareLanguageResource.RES_FEATURES) ||
266             this.isResourceChanged(EventAwareLanguageResource.RES_NAME);
267   }
268 
269 
270 
271   /** Sets the name of this resource*/
272   public void setName(String name){
273     super.setName(name);
274 
275     this.nameChanged = true;
276   }
277 
278 
279   /** Set the feature set */
280   public void setFeatures(FeatureMap features) {
281     //1. save them first, so we can remove the listener
282     FeatureMap oldFeatures = this.features;
283 
284     super.setFeatures(features);
285 
286     this.featuresChanged = true;
287 
288     //4. sort out the listeners
289     if (eventHandler != null)
290       oldFeatures.removeFeatureMapListener(eventHandler);
291     else
292       eventHandler = new EventsHandler();
293     this.features.addFeatureMapListener(eventHandler);
294   }
295 
296 
297   /**
298    * All the events from the features are handled by
299    * this inner class.
300    */
301   class EventsHandler implements gate.event.FeatureMapListener {
302     public void featureMapUpdated(){
303       //tell the document that its features have been updated
304       featuresChanged = true;
305     }
306   }
307 
308   /**
309    * Overriden to remove the features listener, when the document is closed.
310    */
311   public void cleanup() {
312     super.cleanup();
313     if (eventHandler != null)
314       this.features.removeFeatureMapListener(eventHandler);
315   }///inner class EventsHandler
316 
317 
318 
319   public void setInitData__$$__(Object data) {
320 
321     HashMap initData = (HashMap)data;
322 
323     this.name = (String)initData.get("CORP_NAME");
324     this.dataStore = (DatabaseDataStore)initData.get("DS");
325     this.lrPersistentId = (Long)initData.get("LR_ID");
326     this.features = (FeatureMap)initData.get("CORP_FEATURES");
327     this.supportList = (List)initData.get("CORP_SUPPORT_LIST");
328 
329     this.documentData = new ArrayList(this.supportList.size());
330     this.removedDocuments = new ArrayList();
331 
332     //init the documentData list
333     for (int i=0; i< this.supportList.size(); i++) {
334       Document dbDoc = (Document)this.supportList.get(i);
335       DocumentData dd = new DocumentData(dbDoc.getName(),dbDoc.getLRPersistenceId());
336       this.documentData.add(dd);
337     }
338 
339     this.featuresChanged = false;
340     this.nameChanged = false;
341 
342      //3. add the listeners for the features
343     if (eventHandler == null)
344       eventHandler = new EventsHandler();
345     this.features.addFeatureMapListener(eventHandler);
346 
347 
348     //4. add self as listener for the data store, so that we'll know when the DS is
349     //synced and we'll clear the isXXXChanged flags
350     this.dataStore.addDatastoreListener(this);
351   }
352 
353   public Object getInitData__$$__(Object initData) {
354     return null;
355   }
356 
357   /**
358    * Gets the names of the documents in this corpus.
359    * @return a {@link List} of Strings representing the names of the documents
360    * in this corpus.
361    */
362   public List getDocumentNames(){
363 
364     List docsNames = new ArrayList();
365 
366     if(this.documentData == null)
367       return docsNames;
368 
369     Iterator iter = this.documentData.iterator();
370     while (iter.hasNext()) {
371       DocumentData data = (DocumentData)iter.next();
372       docsNames.add(data.getDocumentName());
373     }
374 
375     return docsNames;
376   }
377 
378 
379   /**
380    * Gets the name of a document in this corpus.
381    * @param index the index of the document
382    * @return a String value representing the name of the document at
383    * <tt>index</tt> in this corpus.<P>
384    */
385   public String getDocumentName(int index){
386 
387     if (index >= this.documentData.size()) return "No such document";
388 
389     return ((DocumentData)this.documentData.get(index)).getDocumentName();
390   }
391 
392   /**
393    * returns a document in the coprus by index
394    * @param index the index of the document
395    * @return an Object value representing DatabaseDocumentImpl
396    */
397   public Object get(int index){
398 
399     //0. preconditions
400     Assert.assertTrue(index >= 0);
401     Assert.assertTrue(index < this.documentData.size());
402     Assert.assertTrue(index < this.supportList.size());
403 
404     if (index >= this.documentData.size())
405       return null;
406 
407     Object res = this.supportList.get(index);
408 
409     //if the document is null, then I must get it from the database
410     if (null == res) {
411       Long currLRID = (Long)((DocumentData)this.documentData.get(index)).getPersistentID();
412       FeatureMap params = Factory.newFeatureMap();
413       params.put(DataStore.DATASTORE_FEATURE_NAME, this.getDataStore());
414       params.put(DataStore.LR_ID_FEATURE_NAME, currLRID);
415 
416       try {
417         Document dbDoc = (Document)Factory.createResource(DBHelper.DOCUMENT_CLASS, params);
418 
419         if (DEBUG) {
420           Out.prln("Loaded document :" + dbDoc.getName());
421         }
422 
423         //change the result to the newly loaded doc
424         res = dbDoc;
425 
426         //finally replace the doc with the instantiated version
427         Assert.assertNull(this.supportList.get(index));
428         this.supportList.set(index, dbDoc);
429       }
430       catch (ResourceInstantiationException ex) {
431         Err.prln("Error reading document inside a serialised corpus.");
432         throw new GateRuntimeException(ex.getMessage());
433       }
434     }
435 
436     return res;
437   }
438 
439   public Object remove(int index){
440 
441     //1. get the persistent id and add it to the removed list
442     DocumentData docData = (DocumentData)this.documentData.get(index);
443     Long removedID = (Long)docData.getPersistentID();
444 //    Assert.assertTrue(null != removedID);
445     //removedID may be NULL if the doc is still transient
446 
447     //2. add to the list of removed documents
448     if (null != removedID) {
449       this.removedDocuments.add(removedID);
450     }
451 
452     //3. delete
453     this.documentData.remove(index);
454     Document res = (Document)this.supportList.remove(index);
455 
456     //4, fire events
457     fireDocumentRemoved(new CorpusEvent(DatabaseCorpusImpl.this,
458                                         res,
459                                         index,
460                                         CorpusEvent.DOCUMENT_REMOVED));
461     return res;
462 
463   }
464 
465 
466   public boolean remove(Object obj){
467 
468     //0. preconditions
469     Assert.assertNotNull(obj);
470     Assert.assertTrue(obj instanceof DatabaseDocumentImpl);
471 
472     if (false == obj instanceof Document) {
473       return false;
474     }
475 
476     Document doc = (Document) obj;
477 
478     //see if we can find it first. If not, then judt return
479     int index = findDocument(doc);
480     if (index == -1) {
481       return false;
482     }
483 
484     if(index < this.documentData.size()) {
485       //we found it, so remove it
486 
487       //1. get the persistent id and add it to the removed list
488       DocumentData docData = (DocumentData)this.documentData.get(index);
489       Long removedID = (Long)docData.getPersistentID();
490       //Assert.assertTrue(null != removedID);
491       //removed ID may be null - doc is still transient
492 
493       //2. add to the list of removed documents
494       if (null != removedID) {
495         this.removedDocuments.add(removedID);
496       }
497 
498       //3. delete
499       this.documentData.remove(index);
500       Document oldDoc = (Document) this.supportList.remove(index);
501 
502       fireDocumentRemoved(new CorpusEvent(DatabaseCorpusImpl.this,
503                                           oldDoc,
504                                           index,
505                                           CorpusEvent.DOCUMENT_REMOVED));
506     }
507 
508     return true;
509   }
510 
511 
512   public int findDocument(Document doc) {
513 
514     boolean found = false;
515     DocumentData docData = null;
516 
517     //first try finding the document in memory
518     int index = this.supportList.indexOf(doc);
519 
520     if (index > -1 && index < this.documentData.size()) {
521       return index;
522     }
523 
524     //else try finding a document with the same name and persistent ID
525     Iterator iter = this.documentData.iterator();
526 
527     for (index = 0;  iter.hasNext(); index++) {
528       docData = (DocumentData) iter.next();
529       if (docData.getDocumentName().equals(doc.getName()) &&
530           docData.getPersistentID().equals(doc.getLRPersistenceId())) {
531         found = true;
532         break;
533       }
534     }
535 
536     if (found && index < this.documentData.size()) {
537       return index;
538     }
539     else {
540       return -1;
541     }
542   }//findDocument
543 
544 
545   public boolean contains(Object o){
546     //return true if:
547     // - the document data list contains a document with such a name
548     //   and persistent id
549 
550     if(false == o instanceof Document)
551       return false;
552 
553     int index = findDocument((Document) o);
554 
555     if (index < 0) {
556       return false;
557     }
558     else {
559       return true;
560     }
561   }
562 
563   public Iterator iterator(){
564     return new DatabaseCorpusIterator(this.documentData);
565   }
566 
567   public List getLoadedDocuments() {
568     return new ArrayList(this.supportList);
569   }
570 
571   public List getRemovedDocuments() {
572     return new ArrayList(this.removedDocuments);
573   }
574 
575   private class DatabaseCorpusIterator implements Iterator {
576 
577       private Iterator docDataIter;
578       private List docDataList;
579 
580       public DatabaseCorpusIterator(List docDataList) {
581         this.docDataList = docDataList;
582         this.docDataIter = this.docDataList.iterator();
583       }
584 
585       public boolean hasNext() {
586         return docDataIter.hasNext();
587       }
588 
589       public Object next(){
590 
591         //try finding a document with the same name and persistent ID
592         DocumentData docData = (DocumentData)docDataIter.next();
593         int index = this.docDataList.indexOf(docData);
594         return DatabaseCorpusImpl.this.get(index);
595       }
596 
597       public void remove() {
598         throw new UnsupportedOperationException("DatabaseCorpusImpl does not " +
599                     "support remove in the iterators");
600       }
601   }
602 }