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