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