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 o) throws ClassCastException {
096 Annotation other = (Annotation) o;
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) ? 0 : type.hashCode());
112 hashCodeRes = 31*hashCodeRes
113 + ((id == null) ? 0 : id.hashCode());
114 hashCodeRes = 31*hashCodeRes
115 + ((start == null || start.getOffset() == null)
116 ? 0 : start.getOffset().hashCode());
117 hashCodeRes = 31*hashCodeRes
118 + ((end == null || end.getOffset() == null)
119 ? 0 : 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 = (Annotation) obj;
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 == null) return false;
214 if (coextensive(anAnnot)){
215 if (anAnnot.getFeatures() == null) return 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 == null) return isCompatible(anAnnot);
238 if (anAnnot == null) return false;
239 if (coextensive(anAnnot)){
240 if (anAnnot.getFeatures() == null) return 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 == null) return false;
256 if (overlaps(anAnnot)){
257 if (anAnnot.getFeatures() == null) return 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 == null) return isPartiallyCompatible(anAnnot);
280 if (anAnnot == null) return false;
281 if (overlaps(anAnnot)){
282 if (anAnnot.getFeatures() == null) return 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 == null) return false;
331 if (aAnnot.getStartNode() == null ||
332 aAnnot.getEndNode() == null ||
333 aAnnot.getStartNode().getOffset() == null ||
334 aAnnot.getEndNode().getOffset() == null) return 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 == null) return false;
366 if (aAnnot.getStartNode() == null ||
367 aAnnot.getEndNode() == null ||
368 aAnnot.getStartNode().getOffset() == null ||
369 aAnnot.getEndNode().getOffset() == null) return 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 = (Vector) annotationListeners.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) : (Vector) annotationListeners.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 ((AnnotationListener) listeners.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
|