1   /*
2    *  AnnotationImpl.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   *  Valentin Tablan, Jan/00
12   *
13   *  $Id: AnnotationImpl.java,v 1.26 2002/03/07 12:32:51 kalina Exp $
14   */
15  
16  package gate.annotation;
17  
18  import java.util.*;
19  
20  import gate.*;
21  import gate.util.*;
22  import gate.corpora.*;
23  import gate.event.*;
24  
25  /** Provides an implementation for the interface gate.Annotation
26   *
27   */
28  public class AnnotationImpl extends AbstractFeatureBearer
29                              implements Annotation, FeatureBearer, Comparable {
30  
31    /** Debug flag
32     */
33    private static final boolean DEBUG = false;
34    /** Freeze the serialization UID. */
35    static final long serialVersionUID = -5658993256574857725L;
36  
37    /** Constructor. Package access - annotations have to be constructed via
38     * AnnotationSets.
39     *
40     * @param id The id of the new annotation;
41     * @param start The node from where the annotation will depart;
42     * @param end The node where trhe annotation ends;
43     * @param type The type of the new annotation;
44     * @param features The features of the annotation.
45     */
46    AnnotationImpl(
47      Integer id, Node start, Node end, String type, FeatureMap features
48    ) {
49      this.id       = id;
50      this.start    = start;
51      this.end      = end;
52      this.type     = type;
53      this.features = features;
54  
55    } // AnnotationImpl
56  
57  
58    /** The ID of the annotation.
59     */
60    public Integer getId() {
61      return id;
62    } // getId()
63  
64    /** The type of the annotation (corresponds to TIPSTER "name").
65     */
66    public String getType() {
67      return type;
68    } // getType()
69  
70    /** The start node.
71     */
72    public Node getStartNode() {
73      return start;
74    } // getStartNode()
75  
76    /** The end node.
77     */
78    public Node getEndNode() {
79      return end;
80    } // getEndNode()
81  
82    /** String representation of hte annotation
83     */
84    public String toString() {
85      return "AnnotationImpl: id=" + id + "; type=" + type +
86             "; features=" + features + "; start=" + start +
87             "; end=" + end + System.getProperty("line.separator");
88    } // toString()
89  
90    /** Ordering
91     */
92    public int compareTo(Object o) throws ClassCastException {
93      Annotation other = (Annotation) o;
94      return id.compareTo(other.getId());
95    } // compareTo
96  
97    /** When equals called on two annotations returns true, is REQUIRED that the
98      * value hashCode for each annotation to be the same. It is not required
99      * that when equals return false, the values to be different. For speed, it
100     * would be beneficial to happen that way.
101     */
102 
103   public int hashCode(){
104     int hashCodeRes = 0;
105     if (start != null && start.getOffset() != null)
106        hashCodeRes ^= start.getOffset().hashCode();
107     if (end != null && end.getOffset() != null)
108       hashCodeRes ^= end.getOffset().hashCode();
109     if(features != null)
110       hashCodeRes ^= features.hashCode();
111     return  hashCodeRes;
112   }// hashCode
113 
114   /** Returns true if two annotation are Equals.
115    *  Two Annotation are equals if their offsets, types, id and features are the
116    *  same.
117    */
118   public boolean equals(Object obj){
119     if(obj == null)
120       return false;
121     Annotation other;
122     if(obj instanceof AnnotationImpl){
123       other = (Annotation) obj;
124     }else return false;
125 
126     // If their types are not equals then return false
127     if((type == null) ^ (other.getType() == null))
128       return false;
129     if(type != null && (!type.equals(other.getType())))
130       return false;
131 
132     // If their types are not equals then return false
133     if((id == null) ^ (other.getId() == null))
134       return false;
135     if((id != null )&& (!id.equals(other.getId())))
136       return false;
137 
138     // If their start offset is not the same then return false
139     if((start == null) ^ (other.getStartNode() == null))
140       return false;
141     if(start != null){
142       if((start.getOffset() == null) ^
143          (other.getStartNode().getOffset() == null))
144         return false;
145       if(start.getOffset() != null &&
146         (!start.getOffset().equals(other.getStartNode().getOffset())))
147         return false;
148     }
149 
150     // If their end offset is not the same then return false
151     if((end == null) ^ (other.getEndNode() == null))
152       return false;
153     if(end != null){
154       if((end.getOffset() == null) ^
155          (other.getEndNode().getOffset() == null))
156         return false;
157       if(end.getOffset() != null &&
158         (!end.getOffset().equals(other.getEndNode().getOffset())))
159         return false;
160     }
161 
162     // If their featureMaps are not equals then return false
163     if((features == null) ^ (other.getFeatures() == null))
164       return false;
165     if(features != null && (!features.equals(other.getFeatures())))
166       return false;
167     return true;
168   }// equals
169 
170   /** Set the feature set. Overriden from the implementation in
171    *  AbstractFeatureBearer because it needs to fire events
172    */
173   public void setFeatures(FeatureMap features) {
174     //I need to remove first the old features listener if any
175     if (eventHandler != null)
176       this.features.removeFeatureMapListener(eventHandler);
177 
178     this.features = features;
179 
180     //if someone cares about the annotation changes, then we need to
181     //track the events from the new feature
182     if (annotationListeners != null && ! annotationListeners.isEmpty())
183       this.features.addFeatureMapListener(eventHandler);
184 
185     //finally say that the annotation features have been updated
186     fireAnnotationUpdated(new AnnotationEvent(
187                             this,
188                             AnnotationEvent.FEATURES_UPDATED));
189 
190 
191   }
192 
193 
194   /** This verifies if <b>this</b> annotation is compatible with another one.
195     * Compatible means that they hit the same possition and the FeatureMap of
196     * <b>this</b> is incuded into aAnnot FeatureMap.
197     * @param anAnnot a gate Annotation. If anAnnotation is null then false is
198     * returned.
199     * @return <code>true</code> if aAnnot is compatible with <b>this</> and
200     * <code>false</code> otherwise.
201     */
202   public boolean isCompatible(Annotation anAnnot){
203     if (anAnnot == null) return false;
204     if (coextensive(anAnnot)){
205       if (anAnnot.getFeatures() == null) return true;
206       if (anAnnot.getFeatures().subsumes(this.getFeatures()))
207         return true;
208     }// End if
209     return false;
210   }//isCompatible
211 
212   /** This verifies if <b>this</b> annotation is compatible with another one,
213     * given a set with certain keys.
214     * In this case, compatible means that they hit the same possition
215     * and those keys from <b>this</b>'s FeatureMap intersected with
216     * aFeatureNamesSet are incuded together with their values into the aAnnot's
217     * FeatureMap.
218     * @param anAnnot a gate Annotation. If param is null, it will return false.
219     * @param aFeatureNamesSet is a set containing certian key that will be
220     * intersected with <b>this</b>'s FeatureMap's keys.If param is null then
221     * isCompatible(Annotation) will be called.
222     * @return <code>true</code> if aAnnot is compatible with <b>this</> and
223     * <code>false</code> otherwise.
224     */
225   public boolean isCompatible(Annotation anAnnot, Set aFeatureNamesSet){
226     // If the set is null then isCompatible(Annotation) will decide.
227     if (aFeatureNamesSet == null) return isCompatible(anAnnot);
228     if (anAnnot == null) return false;
229     if (coextensive(anAnnot)){
230       if (anAnnot.getFeatures() == null) return true;
231       if (anAnnot.getFeatures().subsumes(this.getFeatures(),aFeatureNamesSet))
232         return true;
233     }// End if
234     return false;
235   }//isCompatible()
236 
237   /** This method verifies if two annotation and are partially compatible.
238     * Partially compatible means that they overlap and the FeatureMap of
239     * <b>this</b> is incuded into FeatureMap of aAnnot.
240     * @param aAnnot a gate Annotation.
241     * @return <code>true</code> if <b>this</b> is partially compatible with
242     * aAnnot and <code>false</code> otherwise.
243     */
244   public boolean isPartiallyCompatible(Annotation anAnnot){
245     if (anAnnot == null) return false;
246     if (overlaps(anAnnot)){
247       if (anAnnot.getFeatures() == null) return true;
248       if (anAnnot.getFeatures().subsumes(this.getFeatures()))
249         return true;
250     }// End if
251     return false;
252   }//isPartiallyCompatible
253 
254   /** This method verifies if two annotation and are partially compatible,
255     * given a set with certain keys.
256     * In this case, partially compatible means that they overlap
257     * and those keys from <b>this</b>'s FeatureMap intersected with
258     * aFeatureNamesSet are incuded together with their values into the aAnnot's
259     * FeatureMap.
260     * @param anAnnot a gate Annotation. If param is null, the method will return
261     * false.
262     * @param aFeatureNamesSet is a set containing certian key that will be
263     * intersected with <b>this</b>'s FeatureMap's keys.If param is null then
264     * isPartiallyCompatible(Annotation) will be called.
265     * @return <code>true</code> if <b>this</b> is partially compatible with
266     * aAnnot and <code>false</code> otherwise.
267     */
268   public boolean isPartiallyCompatible(Annotation anAnnot,Set aFeatureNamesSet){
269     if (aFeatureNamesSet == null) return isPartiallyCompatible(anAnnot);
270     if (anAnnot == null) return false;
271     if (overlaps(anAnnot)){
272       if (anAnnot.getFeatures() == null) return true;
273       if (anAnnot.getFeatures().subsumes(this.getFeatures(),aFeatureNamesSet))
274         return true;
275     }// End if
276     return false;
277   }//isPartiallyCompatible()
278 
279   /**
280     *  Two Annotation are coextensive if their offsets are the
281     *  same.
282     *  @param anAnnot A Gate annotation.
283     *  @return <code>true</code> if two annotation hit the same possition and
284     *  <code>false</code> otherwise
285     */
286   public boolean coextensive(Annotation anAnnot){
287     // If their start offset is not the same then return false
288     if((anAnnot.getStartNode() == null) ^ (this.getStartNode() == null))
289       return false;
290 
291     if(anAnnot.getStartNode() != null){
292       if((anAnnot.getStartNode().getOffset() == null) ^
293          (this.getStartNode().getOffset() == null))
294         return false;
295       if(anAnnot.getStartNode().getOffset() != null &&
296         (!anAnnot.getStartNode().getOffset().equals(
297                             this.getStartNode().getOffset())))
298         return false;
299     }// End if
300 
301     // If their end offset is not the same then return false
302     if((anAnnot.getEndNode() == null) ^ (this.getEndNode() == null))
303       return false;
304 
305     if(anAnnot.getEndNode() != null){
306       if((anAnnot.getEndNode().getOffset() == null) ^
307          (this.getEndNode().getOffset() == null))
308         return false;
309       if(anAnnot.getEndNode().getOffset() != null &&
310         (!anAnnot.getEndNode().getOffset().equals(
311               this.getEndNode().getOffset())))
312         return false;
313     }// End if
314 
315     // If we are here, then the annotations hit the same position.
316     return true;
317   }//coextensive
318 
319   /** This method tells if <b>this</b> overlaps aAnnot.
320     * @param aAnnot a gate Annotation.
321     * @return <code>true</code> if they overlap and <code>false</code> false if
322     * they don't.
323     */
324   public boolean overlaps(Annotation aAnnot){
325     if (aAnnot == null) return false;
326     if (aAnnot.getStartNode() == null ||
327         aAnnot.getEndNode() == null ||
328         aAnnot.getStartNode().getOffset() == null ||
329         aAnnot.getEndNode().getOffset() == null) return false;
330 
331 //    if ( (aAnnot.getEndNode().getOffset().longValue() ==
332 //          aAnnot.getStartNode().getOffset().longValue()) &&
333 //          this.getStartNode().getOffset().longValue() <=
334 //          aAnnot.getStartNode().getOffset().longValue() &&
335 //          aAnnot.getEndNode().getOffset().longValue() <=
336 //          this.getEndNode().getOffset().longValue()
337 //       ) return true;
338 
339 
340     if ( aAnnot.getEndNode().getOffset().longValue() <=
341          this.getStartNode().getOffset().longValue())
342       return false;
343 
344     if ( aAnnot.getStartNode().getOffset().longValue() >=
345          this.getEndNode().getOffset().longValue())
346       return false;
347 
348     return true;
349   }//overlaps
350 
351 //////////////////THE EVENT HANDLING CODE/////////////////////
352 //Needed so an annotation set can listen to its annotations//
353 //and update correctly the database/////////////////////////
354 
355   /**
356    * The set of listeners of the annotation update events. At present there
357    * are two event types supported:
358    * <UL>
359    *   <LI> ANNOTATION_UPDATED event
360    *   <LI> FEATURES_UPDATED event
361    * </UL>
362    */
363   private transient Vector annotationListeners;
364   /**
365    * The listener for the events coming from the features.
366    */
367   protected EventsHandler eventHandler;
368 
369 
370   /**
371    *
372    * Removes an annotation listener
373    */
374   public synchronized void removeAnnotationListener(AnnotationListener l) {
375     if (annotationListeners != null && annotationListeners.contains(l)) {
376       Vector v = (Vector) annotationListeners.clone();
377       v.removeElement(l);
378       annotationListeners = v;
379     }
380   }
381   /**
382    *
383    * Adds an annotation listener
384    */
385   public synchronized void addAnnotationListener(AnnotationListener l) {
386     Vector v = annotationListeners == null ? new Vector(2) : (Vector) annotationListeners.clone();
387 
388     //now check and if this is the first listener added,
389     //start listening to all features, so their changes can
390     //also be propagated
391     if (v.isEmpty()) {
392       FeatureMap features = getFeatures();
393       if (eventHandler == null)
394         eventHandler = new EventsHandler();
395       features.addFeatureMapListener(eventHandler);
396     }
397 
398     if (!v.contains(l)) {
399       v.addElement(l);
400       annotationListeners = v;
401     }
402   }
403   /**
404    *
405    * @param e
406    */
407   protected void fireAnnotationUpdated(AnnotationEvent e) {
408     if (annotationListeners != null) {
409       Vector listeners = annotationListeners;
410       int count = listeners.size();
411       for (int i = 0; i < count; i++) {
412         ((AnnotationListener) listeners.elementAt(i)).annotationUpdated(e);
413       }
414     }
415   }//fireAnnotationUpdated
416 
417 
418   /**
419    * The id of this annotation (for persitency resons)
420    *
421    */
422   Integer id;
423   /**
424    * The type of the annotation
425    *
426    */
427   String type;
428   /**
429    * The features of the annotation are inherited from Abstract feature bearer
430    * so no need to define here
431    */
432 
433   /**
434    * The start node
435    */
436   Node start;
437 
438   /**
439    *  The end node
440    */
441   Node end;
442 
443   /** @link dependency */
444   /*#AnnotationImpl lnkAnnotationImpl;*/
445 
446   /**
447    * All the events from the features are handled by
448    * this inner class.
449    */
450   class EventsHandler implements gate.event.FeatureMapListener {
451     public void featureMapUpdated(){
452       //tell the annotation listeners that my features have been updated
453       fireAnnotationUpdated(new AnnotationEvent(
454                                   AnnotationImpl.this,
455                                   AnnotationEvent.FEATURES_UPDATED));
456     }
457   }//inner class EventsHandler
458 
459 
460 } // class AnnotationImpl
461