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