1   /*
2    *  SimpleFeatureMapImpl.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   *  Hamish Cunningham, 7/Feb/2000
12   *  borislav popov, 1/May/2002
13   *
14   *  $Id: SimpleFeatureMapImpl.java,v 1.37 2005/09/19 14:07:53 valyt Exp $
15   */
16  
17  package gate.util;
18  
19  import java.net.MalformedURLException;
20  import java.net.URL;
21  import java.util.Set;
22  import java.util.Vector;
23  
24  import com.ontotext.gate.ontology.OntologyImpl;
25  
26  import gate.FeatureMap;
27  import gate.creole.ontology.Taxonomy;
28  import gate.event.FeatureMapListener;
29  
30  /** Simple case of features. */
31  //>>> DAM: was (derived from HashMap)
32  /*
33  public class SimpleFeatureMapImpl  extends HashMap implements FeatureMap
34  */
35  //=== DAM: FeatArray optimization, now derived from SimpleMapImpl
36  public class SimpleFeatureMapImpl
37      extends SimpleMapImpl
38  //    extends HashMap
39      implements FeatureMap, java.io.Serializable, java.lang.Cloneable,
40      gate.creole.ANNIEConstants
41  //>>> DAM: end
42  {
43    /** Debug flag */
44    private static final boolean DEBUG = false;
45  
46  
47   /** Freeze the serialization UID. */
48    static final long serialVersionUID = -2747241616127229116L;
49  
50    /** 
51     * Test if <b>this</b> featureMap includes all features from aFeatureMap
52     * 
53     * However, if aFeatureMap contains a feature whose value is equal to 
54     * gate.creole.ANNIEConstants.LOOKUP_CLASS_FEATURE_NAME (which is normally 
55     * "class"), then GATE will attempt to match that feature using an ontology
56     * which it will try to retreive from a feature in both the feature map 
57     * through which this method is called and in aFeatureMap. If these do not return 
58     * identical ontologies, or if
59     * either feature map does not contain an ontology, then 
60     * matching will fail, and this method will return false. In summary, 
61     * this method will not work normally when aFeatureMap contains a feature 
62     * with the name "class".
63     * 
64      * @param aFeatureMap object which will be included or not in
65      * <b>this</b> FeatureMap obj.If this param is null then it will return true.
66      * @return <code>true</code> if aFeatureMap is incuded in <b>this</b> obj.
67      * and <code>false</code> if not.
68      */
69    public boolean subsumes(FeatureMap aFeatureMap){
70      // null is included in everything
71      if (aFeatureMap == null) return true;
72  
73      if (this.size() < aFeatureMap.size()) return false;
74  
75      SimpleFeatureMapImpl sfm = (SimpleFeatureMapImpl)aFeatureMap;
76  
77      Object key;
78      Object keyValueFromAFeatureMap;
79      Object keyValueFromThis;
80  
81      for (int i = 0; i < sfm.count; i++) {
82        key = sfm.theKeys[i];
83        keyValueFromAFeatureMap = sfm.theValues[i];
84        int v = super.getSubsumeKey(key);
85        if (v < 0) return false;
86        keyValueFromThis = theValues[v];//was: get(key);
87  
88        if  ( (keyValueFromThis == null && keyValueFromAFeatureMap != null) ||
89              (keyValueFromThis != null && keyValueFromAFeatureMap == null)
90            ) return false;
91  
92        /*ontology aware subsume implementation
93        ontotext.bp*/
94        if ((keyValueFromThis != null) && (keyValueFromAFeatureMap != null)) {
95  
96          if ( key.equals(LOOKUP_CLASS_FEATURE_NAME) ) {
97            /* ontology aware processing */
98            Object sfmOntoObj = sfm.get(LOOKUP_ONTOLOGY_FEATURE_NAME);
99            Object thisOntoObj = this.get(LOOKUP_ONTOLOGY_FEATURE_NAME);
100           if (null!=sfmOntoObj && null!= thisOntoObj) {
101             if (sfmOntoObj.equals(thisOntoObj)) {
102               boolean doSubsume = ontologySubsume(
103                           sfmOntoObj.toString(),
104                           keyValueFromAFeatureMap.toString(),
105                           keyValueFromThis.toString());
106               if (!doSubsume ) {
107                 return false;
108               }
109             } // if ontologies are with the same url
110           } //if not null objects
111           else {
112             // incomplete feature set: missing ontology feature
113             return false;
114           }
115         } else {
116           /* processing without ontology awareness */
117           if (!keyValueFromThis.equals(keyValueFromAFeatureMap)) return false;
118         }  // else
119 
120       } // if
121     } // for
122 
123     return true;
124   }//subsumes()
125 
126    /** Tests if <b>this</b> featureMap object includes aFeatureMap features. <br>
127    * If the feature map contains <code>class</code> and (optionally) <code>ontology</code> features:<br>
128    * then the ontologyLR is used to provide ontology based subsume with respect to the subClassOf relations.
129    * @param ontologyLR an ontology to be used for the subsume
130    * @param aFeatureMap object which will be included  or not in  <b>this</b>
131    * FeatureMap obj.
132    * @return <code>true</code> if <b>this</b> includes aFeatureMap
133    * and <code>false</code> if not.
134    */
135   public boolean subsumes(Taxonomy ontologyLR, FeatureMap aFeatureMap) {
136 
137     if (ontologyLR == null) {
138       return this.subsumes(aFeatureMap);
139     }
140 
141     if (aFeatureMap == null)
142       return true;
143 
144     if (this.size() < aFeatureMap.size())
145       return false;
146 
147     SimpleFeatureMapImpl sfm = (SimpleFeatureMapImpl) aFeatureMap;
148 
149     Object key;
150     Object keyValueFromAFeatureMap;
151     Object keyValueFromThis;
152 
153     for (int i = 0; i < sfm.count; i++) {
154       key = sfm.theKeys[i];
155       keyValueFromAFeatureMap = sfm.theValues[i];
156       int v = super.getSubsumeKey(key);
157       if (v < 0)
158         return false;
159       keyValueFromThis = theValues[v]; //was: get(key);
160 
161       if ( (keyValueFromThis == null && keyValueFromAFeatureMap != null) ||
162           (keyValueFromThis != null && keyValueFromAFeatureMap == null)
163           )
164         return false;
165 
166       /*ontology aware subsume implementation based on the ontology LR
167           ontotext.bp*/
168       if ( (keyValueFromThis != null) && (keyValueFromAFeatureMap != null)) {
169 
170         if (key.equals(LOOKUP_CLASS_FEATURE_NAME)) {
171           // ontology aware processing
172 
173           try {
174 
175             if (DEBUG) {
176               Out.prln("\nClass in rule: " + keyValueFromAFeatureMap.toString());
177               Out.prln("\nClass in annotation: " + keyValueFromThis.toString());
178               Out.prln("\nisSubClassOf: " +
179                        ontologyLR.isSubClassOf(keyValueFromAFeatureMap.toString(),
180                                                keyValueFromThis.toString()));
181             }
182 
183             return ontologyLR.isSubClassOf(keyValueFromAFeatureMap.toString(),
184                                            keyValueFromThis.toString());
185           } catch (Exception ex) {
186             throw new gate.util.GateRuntimeException(ex);
187           }
188         }
189         else {
190           /* processing without ontology awareness */
191           if (!keyValueFromThis.equals(keyValueFromAFeatureMap))
192             return false;
193         } // else
194 
195       } // if
196     } // for
197 
198     return true;
199   } //subsumes(ontology)
200 
201 
202   /** Tests if <b>this</b> featureMap object includes aFeatureMap but only
203     * for the those features present in the aFeatureNamesSet.
204     * 
205     * However, if aFeatureMap contains a feature whose value is equal to 
206    * gate.creole.ANNIEConstants.LOOKUP_CLASS_FEATURE_NAME (which is normally 
207    * "class"), then GATE will attempt to match that feature using an ontology
208    * which it will try to retreive from a feature in both the feature map 
209    * through which this method is called and in aFeatureMap. If these do not return 
210    * identical ontologies, or if
211    * either feature map does not contain an ontology, then 
212    * matching will fail, and this method will return false. In summary, 
213    * this method will not work normally when aFeatureMap contains a feature 
214    * with the name "class" if that feature is also in aFeatureNamesSet.
215     * 
216     * @param aFeatureMap which will be included or not in <b>this</b>
217     * FeatureMap obj.If this param is null then it will return true.
218     * @param aFeatureNamesSet is a set of strings representing the names of the
219     * features that would be considered for subsumes. If aFeatureNamesSet is
220     * <b>null</b> then subsumes(FeatureMap) will be called.
221     * @return <code>true</code> if all features present in the aFeaturesNameSet
222     * from aFeatureMap are included in <b>this</b> obj, or <code>false</code>
223     * otherwise.
224     */
225   public boolean subsumes(FeatureMap aFeatureMap, Set aFeatureNamesSet){
226     // This means that all features are taken into consideration.
227     if (aFeatureNamesSet == null) return this.subsumes(aFeatureMap);
228     // null is included in everything
229     if (aFeatureMap == null) return true;
230     // This means that subsumes is supressed.
231     if (aFeatureNamesSet.isEmpty()) return true;
232 
233     SimpleFeatureMapImpl sfm = (SimpleFeatureMapImpl)aFeatureMap;
234 
235     Object key;
236     Object keyValueFromAFeatureMap;
237     Object keyValueFromThis;
238 
239     for (int i = 0; i < sfm.count; i++) {
240       key = sfm.theKeys[i];
241 
242       if (!aFeatureNamesSet.contains(key))
243         continue;
244 
245       keyValueFromAFeatureMap = sfm.theValues[i];
246         keyValueFromThis = get(key);
247 
248       if  ( (keyValueFromThis == null && keyValueFromAFeatureMap != null) ||
249             (keyValueFromThis != null && keyValueFromAFeatureMap == null)
250           ) return false;
251 
252       if ((keyValueFromThis != null) && (keyValueFromAFeatureMap != null)) {
253         if ( key.equals(LOOKUP_CLASS_FEATURE_NAME) ) {
254           /* ontology aware processing */
255           if (!aFeatureNamesSet.contains(LOOKUP_ONTOLOGY_FEATURE_NAME))
256             continue;
257 
258           Object sfmOntoObj = sfm.get(LOOKUP_ONTOLOGY_FEATURE_NAME);
259           Object thisOntoObj = this.get(LOOKUP_ONTOLOGY_FEATURE_NAME);
260           if (null!=sfmOntoObj && null!= thisOntoObj) {
261             if (sfmOntoObj.equals(thisOntoObj)) {
262               if (! ontologySubsume(
263                           sfmOntoObj.toString(),
264                           keyValueFromAFeatureMap.toString(),
265                           keyValueFromThis.toString()))
266                 return false;
267             } // if ontologies are with the same url
268           } //if not null objects
269           else {
270             // incomplete feature set: missing ontology feature
271             return false;
272           }
273         } else {
274           /*processing without ontology awareness*/
275           if (!keyValueFromThis.equals(keyValueFromAFeatureMap)) return false;
276         } //else
277       } // if values not null
278     } // for
279 
280     return true;
281   }// subsumes()
282 
283 
284   /**
285    * Overriden to fire events, so that the persistent objects
286    *  can keep track of what's updated
287    */
288   public Object put(Object key, Object value) {
289     Object result = super.put(key, value);
290     this.fireMapUpdatedEvent();
291     return result;
292   } // put
293 
294   /**
295    * Overriden to fire events, so that the persistent objects
296    *  can keep track of what's updated
297    */
298   public Object remove(Object key) {
299     Object result = super.remove(key);
300     this.fireMapUpdatedEvent();
301     return result;
302   } // remove
303 
304   public void clear() {
305     super.clear();
306     //tell the world if they're listening
307     this.fireMapUpdatedEvent();
308   } // clear
309 
310   // Views
311   public Object clone() {
312     return super.clone();
313   } //clone
314 
315   public boolean equals(Object o) {
316     return super.equals(o);
317   } // equals
318 
319 //////////////////THE EVENT HANDLING CODE//////////////
320 //Needed so an annotation can listen to its features//
321 //and update correctly the database//////////////////
322   private transient Vector mapListeners;
323   /**
324    * Removes a gate listener
325    */
326   public synchronized void removeFeatureMapListener(FeatureMapListener l) {
327     if (mapListeners != null && mapListeners.contains(l)) {
328       Vector v = (Vector) mapListeners.clone();
329       v.removeElement(l);
330       mapListeners = v;
331     }
332   } //removeFeatureMapListener
333   /**
334    * Adds a gate listener
335    */
336   public synchronized void addFeatureMapListener(FeatureMapListener l) {
337     Vector v = mapListeners == null ? new Vector(2) : (Vector)mapListeners.clone();
338     if (!v.contains(l)) {
339       v.addElement(l);
340       mapListeners = v;
341     }
342   } //addFeatureMapListener
343 
344   /**
345    *
346    */
347   protected void fireMapUpdatedEvent () {
348     if (mapListeners != null) {
349       Vector listeners = mapListeners;
350       int count = listeners.size();
351       if (count == 0) return;
352       for (int i = 0; i < count; i++)
353         ((FeatureMapListener) listeners.elementAt(i)).featureMapUpdated();
354     }
355   }//fireMapUpdatedEvent
356 
357 
358   /**ontology enhanced subsume
359    * @param ontoUrl the url of the ontology to be used
360    * @return true if value1 subsumes value2 in the specified ontology */
361   protected boolean ontologySubsume(String ontoUrl,String value1,String value2) {
362     boolean result = false;
363     try {
364       URL url;
365       try {
366         url = new URL(ontoUrl);
367       } catch (MalformedURLException e){
368         throw new RuntimeException(
369         "\nin SimpleFeatureMapImpl on ontologySubsume()\n"
370         +e.getMessage()+"\n");
371       }
372 
373       /* GET ONTOLOGY BY URL : a bit tricky reference
374       since the behaviour behind the getOntology method is
375       certainly static.
376       : should be temporary */
377       Taxonomy o = new OntologyImpl().getOntology(url);
378 
379       result = o.isSubClassOf(value1, value2);
380 
381     } catch  (gate.creole.ResourceInstantiationException x) {
382       x.printStackTrace(Err.getPrintWriter());
383     }
384     return result;
385   } // ontologySubsume
386 
387 } // class SimpleFeatureMapImpl
388 
389