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