| AnnotationImpl.java |
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.31 2004/10/06 11:48:46 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 static final long serialVersionUID = 2608156420244752907L;
461
462 }//inner class EventsHandler
463
464
465 } // class AnnotationImpl
466