AnnotationImpl.java
001 /*
002  *  AnnotationImpl.java
003  *
004  *  Copyright (c) 1995-2010, The University of Sheffield. See the file
005  *  COPYRIGHT.txt in the software or at http://gate.ac.uk/gate/COPYRIGHT.txt
006  *
007  *  This file is part of GATE (see http://gate.ac.uk/), and is free
008  *  software, licenced under the GNU Library General Public License,
009  *  Version 2, June 1991 (in the distribution as file licence.html,
010  *  and also available at http://gate.ac.uk/gate/licence.html).
011  *
012  *  Valentin Tablan, Jan/00
013  *
014  *  $Id: AnnotationImpl.java 13231 2010-11-16 20:16:52Z johann_p $
015  */
016 
017 package gate.annotation;
018 
019 import java.io.Serializable;
020 import java.util.Set;
021 import java.util.Vector;
022 
023 import gate.*;
024 import gate.event.AnnotationEvent;
025 import gate.event.AnnotationListener;
026 import gate.util.AbstractFeatureBearer;
027 import gate.util.FeatureBearer;
028 
029 /** Provides an implementation for the interface gate.Annotation
030  *
031  */
032 public class AnnotationImpl extends AbstractFeatureBearer
033                             implements Annotation, FeatureBearer, Comparable {
034 
035   /** Debug flag
036    */
037   private static final boolean DEBUG = false;
038   /** Freeze the serialization UID. */
039   static final long serialVersionUID = -5658993256574857725L;
040 
041   /** Constructor. Package access - annotations have to be constructed via
042    * AnnotationSets.
043    *
044    @param id The id of the new annotation;
045    @param start The node from where the annotation will depart;
046    @param end The node where trhe annotation ends;
047    @param type The type of the new annotation;
048    @param features The features of the annotation.
049    */
050   protected AnnotationImpl(
051     Integer id, Node start, Node end, String type, FeatureMap features
052   ) {
053     this.id       = id;
054     this.start    = start;
055     this.end      = end;
056     this.type     = type;
057     this.features = features;
058 
059   // AnnotationImpl
060 
061   /** The ID of the annotation.
062    */
063   public Integer getId() {
064     return id;
065   // getId()
066 
067   /** The type of the annotation (corresponds to TIPSTER "name").
068    */
069   public String getType() {
070     return type;
071   // getType()
072 
073   /** The start node.
074    */
075   public Node getStartNode() {
076     return start;
077   // getStartNode()
078 
079   /** The end node.
080    */
081   public Node getEndNode() {
082     return end;
083   // getEndNode()
084 
085   /** String representation of hte annotation
086    */
087   public String toString() {
088     return "AnnotationImpl: id=" + id + "; type=" + type +
089            "; features=" + features + "; start=" + start +
090            "; end=" + end + System.getProperty("line.separator");
091   // toString()
092 
093   /** Ordering
094    */
095   public int compareTo(Object othrows ClassCastException {
096     Annotation other = (Annotationo;
097     return id.compareTo(other.getId());
098   // compareTo
099 
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     // hash code based on type, id, start and end offsets (which should never
108     // change once the annotation has been created).
109     int hashCodeRes = 17;
110     hashCodeRes = 31*hashCodeRes
111         ((type == null: type.hashCode());
112     hashCodeRes = 31*hashCodeRes
113         ((id == null: id.hashCode());
114     hashCodeRes = 31*hashCodeRes
115         ((start == null || start.getOffset() == null)
116             : start.getOffset().hashCode());
117     hashCodeRes = 31*hashCodeRes
118         ((end == null || end.getOffset() == null)
119             : end.getOffset().hashCode());
120 
121     return  hashCodeRes;
122   }// hashCode
123 
124   /** Returns true if two annotation are Equals.
125    *  Two Annotation are equals if their offsets, types, id and features are the
126    *  same.
127    */
128   public boolean equals(Object obj){
129     if(obj == null)
130       return false;
131     Annotation other;
132     if(obj instanceof AnnotationImpl){
133       other = (Annotationobj;
134     }else return false;
135 
136     // If their types are not equals then return false
137     if((type == null(other.getType() == null))
138       return false;
139     if(type != null && (!type.equals(other.getType())))
140       return false;
141 
142     // If their types are not equals then return false
143     if((id == null(other.getId() == null))
144       return false;
145     if((id != null )&& (!id.equals(other.getId())))
146       return false;
147 
148     // If their start offset is not the same then return false
149     if((start == null(other.getStartNode() == null))
150       return false;
151     if(start != null){
152       if((start.getOffset() == null^
153          (other.getStartNode().getOffset() == null))
154         return false;
155       if(start.getOffset() != null &&
156         (!start.getOffset().equals(other.getStartNode().getOffset())))
157         return false;
158     }
159 
160     // If their end offset is not the same then return false
161     if((end == null(other.getEndNode() == null))
162       return false;
163     if(end != null){
164       if((end.getOffset() == null^
165          (other.getEndNode().getOffset() == null))
166         return false;
167       if(end.getOffset() != null &&
168         (!end.getOffset().equals(other.getEndNode().getOffset())))
169         return false;
170     }
171 
172     // If their featureMaps are not equals then return false
173     if((features == null(other.getFeatures() == null))
174       return false;
175     if(features != null && (!features.equals(other.getFeatures())))
176       return false;
177     return true;
178   }// equals
179 
180   /** Set the feature set. Overriden from the implementation in
181    *  AbstractFeatureBearer because it needs to fire events
182    */
183   public void setFeatures(FeatureMap features) {
184     //I need to remove first the old features listener if any
185     if (eventHandler != null)
186       this.features.removeFeatureMapListener(eventHandler);
187 
188     this.features = features;
189 
190     //if someone cares about the annotation changes, then we need to
191     //track the events from the new feature
192     if (annotationListeners != null && ! annotationListeners.isEmpty())
193       this.features.addFeatureMapListener(eventHandler);
194 
195     //finally say that the annotation features have been updated
196     fireAnnotationUpdated(new AnnotationEvent(
197                             this,
198                             AnnotationEvent.FEATURES_UPDATED));
199 
200 
201   }
202 
203 
204   /** This verifies if <b>this</b> annotation is compatible with another one.
205     * Compatible means that they hit the same possition and the FeatureMap of
206     <b>this</b> is incuded into aAnnot FeatureMap.
207     @param anAnnot a gate Annotation. If anAnnotation is null then false is
208     * returned.
209     @return <code>true</code> if aAnnot is compatible with <b>this</b> and
210     <code>false</code> otherwise.
211     */
212   public boolean isCompatible(Annotation anAnnot){
213     if (anAnnot == nullreturn false;
214     if (coextensive(anAnnot)){
215       if (anAnnot.getFeatures() == nullreturn true;
216       if (anAnnot.getFeatures().subsumes(this.getFeatures()))
217         return true;
218     }// End if
219     return false;
220   }//isCompatible
221 
222   /** This verifies if <b>this</b> annotation is compatible with another one,
223     * given a set with certain keys.
224     * In this case, compatible means that they hit the same possition
225     * and those keys from <b>this</b>'s FeatureMap intersected with
226     * aFeatureNamesSet are incuded together with their values into the aAnnot's
227     * FeatureMap.
228     @param anAnnot a gate Annotation. If param is null, it will return false.
229     @param aFeatureNamesSet is a set containing certian key that will be
230     * intersected with <b>this</b>'s FeatureMap's keys.If param is null then
231     * isCompatible(Annotation) will be called.
232     @return <code>true</code> if aAnnot is compatible with <b>this</b> and
233     <code>false</code> otherwise.
234     */
235   public boolean isCompatible(Annotation anAnnot, Set aFeatureNamesSet){
236     // If the set is null then isCompatible(Annotation) will decide.
237     if (aFeatureNamesSet == nullreturn isCompatible(anAnnot);
238     if (anAnnot == nullreturn false;
239     if (coextensive(anAnnot)){
240       if (anAnnot.getFeatures() == nullreturn true;
241       if (anAnnot.getFeatures().subsumes(this.getFeatures(),aFeatureNamesSet))
242         return true;
243     }// End if
244     return false;
245   }//isCompatible()
246 
247   /** This method verifies if two annotation and are partially compatible.
248     * Partially compatible means that they overlap and the FeatureMap of
249     <b>this</b> is incuded into FeatureMap of aAnnot.
250     @param anAnnot a gate Annotation.
251     @return <code>true</code> if <b>this</b> is partially compatible with
252     * anAnnot and <code>false</code> otherwise.
253     */
254   public boolean isPartiallyCompatible(Annotation anAnnot){
255     if (anAnnot == nullreturn false;
256     if (overlaps(anAnnot)){
257       if (anAnnot.getFeatures() == nullreturn true;
258       if (anAnnot.getFeatures().subsumes(this.getFeatures()))
259         return true;
260     }// End if
261     return false;
262   }//isPartiallyCompatible
263 
264   /** This method verifies if two annotation and are partially compatible,
265     * given a set with certain keys.
266     * In this case, partially compatible means that they overlap
267     * and those keys from <b>this</b>'s FeatureMap intersected with
268     * aFeatureNamesSet are incuded together with their values into the aAnnot's
269     * FeatureMap.
270     @param anAnnot a gate Annotation. If param is null, the method will return
271     * false.
272     @param aFeatureNamesSet is a set containing certian key that will be
273     * intersected with <b>this</b>'s FeatureMap's keys.If param is null then
274     * isPartiallyCompatible(Annotation) will be called.
275     @return <code>true</code> if <b>this</b> is partially compatible with
276     * aAnnot and <code>false</code> otherwise.
277     */
278   public boolean isPartiallyCompatible(Annotation anAnnot,Set aFeatureNamesSet){
279     if (aFeatureNamesSet == nullreturn isPartiallyCompatible(anAnnot);
280     if (anAnnot == nullreturn false;
281     if (overlaps(anAnnot)){
282       if (anAnnot.getFeatures() == nullreturn true;
283       if (anAnnot.getFeatures().subsumes(this.getFeatures(),aFeatureNamesSet))
284         return true;
285     }// End if
286     return false;
287   }//isPartiallyCompatible()
288 
289   /**
290     *  Two Annotation are coextensive if their offsets are the
291     *  same.
292     *  @param anAnnot A Gate annotation.
293     *  @return <code>true</code> if two annotation hit the same possition and
294     *  <code>false</code> otherwise
295     */
296   public boolean coextensive(Annotation anAnnot){
297     // If their start offset is not the same then return false
298     if((anAnnot.getStartNode() == null(this.getStartNode() == null))
299       return false;
300 
301     if(anAnnot.getStartNode() != null){
302       if((anAnnot.getStartNode().getOffset() == null^
303          (this.getStartNode().getOffset() == null))
304         return false;
305       if(anAnnot.getStartNode().getOffset() != null &&
306         (!anAnnot.getStartNode().getOffset().equals(
307                             this.getStartNode().getOffset())))
308         return false;
309     }// End if
310 
311     // If their end offset is not the same then return false
312     if((anAnnot.getEndNode() == null(this.getEndNode() == null))
313       return false;
314 
315     if(anAnnot.getEndNode() != null){
316       if((anAnnot.getEndNode().getOffset() == null^
317          (this.getEndNode().getOffset() == null))
318         return false;
319       if(anAnnot.getEndNode().getOffset() != null &&
320         (!anAnnot.getEndNode().getOffset().equals(
321               this.getEndNode().getOffset())))
322         return false;
323     }// End if
324 
325     // If we are here, then the annotations hit the same position.
326     return true;
327   }//coextensive
328 
329   public boolean overlaps(Annotation aAnnot){
330     if (aAnnot == nullreturn false;
331     if (aAnnot.getStartNode() == null ||
332         aAnnot.getEndNode() == null ||
333         aAnnot.getStartNode().getOffset() == null ||
334         aAnnot.getEndNode().getOffset() == nullreturn false;
335 
336 //    if ( (aAnnot.getEndNode().getOffset().longValue() ==
337 //          aAnnot.getStartNode().getOffset().longValue()) &&
338 //          this.getStartNode().getOffset().longValue() <=
339 //          aAnnot.getStartNode().getOffset().longValue() &&
340 //          aAnnot.getEndNode().getOffset().longValue() <=
341 //          this.getEndNode().getOffset().longValue()
342 //       ) return true;
343 
344 
345     if aAnnot.getEndNode().getOffset().longValue() <=
346          this.getStartNode().getOffset().longValue())
347       return false;
348 
349     if aAnnot.getStartNode().getOffset().longValue() >=
350          this.getEndNode().getOffset().longValue())
351       return false;
352 
353     return true;
354   }//overlaps
355 
356   
357   /** This method tells if <b>this</b> annotation's text range is 
358    * fully contained within the text annotated by <code>aAnnot</code>'s
359    * annotation. 
360    @param aAnnot a gate Annotation.
361    @return <code>true</code> if this annotation is fully contained in the 
362    * other one.
363    */
364  public boolean withinSpanOf(Annotation aAnnot){
365    if (aAnnot == nullreturn false;
366    if (aAnnot.getStartNode() == null ||
367        aAnnot.getEndNode() == null ||
368        aAnnot.getStartNode().getOffset() == null ||
369        aAnnot.getEndNode().getOffset() == nullreturn false;
370 
371    if ( ( aAnnot.getEndNode().getOffset().longValue() >=
372           this.getEndNode().getOffset().longValue() ) &&
373         aAnnot.getStartNode().getOffset().longValue() <= 
374           this.getStartNode().getOffset().longValue() ) )
375      return true;
376    else 
377      return false;
378  }//withinSpanOf
379 
380   
381 //////////////////THE EVENT HANDLING CODE/////////////////////
382 //Needed so an annotation set can listen to its annotations//
383 //and update correctly the database/////////////////////////
384 
385   /**
386    * The set of listeners of the annotation update events. At present there
387    * are two event types supported:
388    <UL>
389    *   <LI> ANNOTATION_UPDATED event
390    *   <LI> FEATURES_UPDATED event
391    </UL>
392    */
393   private transient Vector annotationListeners;
394   /**
395    * The listener for the events coming from the features.
396    */
397   protected EventsHandler eventHandler;
398 
399 
400   /**
401    *
402    * Removes an annotation listener
403    */
404   public synchronized void removeAnnotationListener(AnnotationListener l) {
405     if (annotationListeners != null && annotationListeners.contains(l)) {
406       Vector v = (VectorannotationListeners.clone();
407       v.removeElement(l);
408       annotationListeners = v;
409     }
410   }
411   /**
412    *
413    * Adds an annotation listener
414    */
415   public synchronized void addAnnotationListener(AnnotationListener l) {
416     Vector v = annotationListeners == null new Vector(2(VectorannotationListeners.clone();
417 
418     //now check and if this is the first listener added,
419     //start listening to all features, so their changes can
420     //also be propagated
421     if (v.isEmpty()) {
422       FeatureMap features = getFeatures();
423       if (eventHandler == null)
424         eventHandler = new EventsHandler();
425       features.addFeatureMapListener(eventHandler);
426     }
427 
428     if (!v.contains(l)) {
429       v.addElement(l);
430       annotationListeners = v;
431     }
432   }
433   /**
434    *
435    @param e
436    */
437   protected void fireAnnotationUpdated(AnnotationEvent e) {
438     if (annotationListeners != null) {
439       Vector listeners = annotationListeners;
440       int count = listeners.size();
441       for (int i = 0; i < count; i++) {
442         ((AnnotationListenerlisteners.elementAt(i)).annotationUpdated(e);
443       }
444     }
445   }//fireAnnotationUpdated
446 
447 
448   /**
449    * The id of this annotation (for persitency resons)
450    *
451    */
452   Integer id;
453   /**
454    * The type of the annotation
455    *
456    */
457   String type;
458   /**
459    * The features of the annotation are inherited from Abstract feature bearer
460    * so no need to define here
461    */
462 
463   /**
464    * The start node
465    */
466   protected Node start;
467 
468   /**
469    *  The end node
470    */
471   protected Node end;
472   
473   /** @link dependency */
474   /*#AnnotationImpl lnkAnnotationImpl;*/
475 
476   /**
477    * All the events from the features are handled by
478    * this inner class.
479    */
480   class EventsHandler implements gate.event.FeatureMapListener, Serializable {
481     public void featureMapUpdated(){
482       //tell the annotation listeners that my features have been updated
483       fireAnnotationUpdated(new AnnotationEvent(
484                                   AnnotationImpl.this,
485                                   AnnotationEvent.FEATURES_UPDATED));
486     }
487     static final long serialVersionUID = 2608156420244752907L;
488     
489   }//inner class EventsHandler
490 
491 
492 // class AnnotationImpl