| AnnotationImpl.java |
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