SimpleFeatureMapImpl.java
001 /*
002  *  SimpleFeatureMapImpl.java
003  *
004  *  Copyright (c) 1995-2012, 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  *  Hamish Cunningham, 7/Feb/2000
013  *  borislav popov, 1/May/2002
014  *
015  *  $Id: SimpleFeatureMapImpl.java 17546 2014-03-05 20:54:08Z markagreenwood $
016  */
017 
018 package gate.util;
019 
020 import gate.FeatureMap;
021 import gate.creole.ontology.OClass;
022 import gate.creole.ontology.OConstants;
023 import gate.creole.ontology.Ontology;
024 import gate.event.FeatureMapListener;
025 
026 import java.util.Set;
027 import java.util.Vector;
028 
029 /** Simple case of features. */
030 public class SimpleFeatureMapImpl
031     extends SimpleMapImpl
032     implements FeatureMap, java.io.Serializable, java.lang.Cloneable,
033     gate.creole.ANNIEConstants
034 {
035   /** Debug flag */
036   private static final boolean DEBUG = false;
037 
038 
039  /** Freeze the serialization UID. */
040   static final long serialVersionUID = -2747241616127229116L;
041 
042   /**
043    * Test if <b>this</b> featureMap includes all features from aFeatureMap
044    *
045    * However, if aFeatureMap contains a feature whose value is equal to
046    * gate.creole.ANNIEConstants.LOOKUP_CLASS_FEATURE_NAME (which is normally
047    * "class"), then GATE will attempt to match that feature using an ontology
048    * which it will try to retreive from a feature in both the feature map
049    * through which this method is called and in aFeatureMap. If these do not return
050    * identical ontologies, or if
051    * either feature map does not contain an ontology, then
052    * matching will fail, and this method will return false. In summary,
053    * this method will not work normally when aFeatureMap contains a feature
054    * with the name "class".
055    *
056     @param aFeatureMap object which will be included or not in
057     <b>this</b> FeatureMap obj.If this param is null then it will return true.
058     @return <code>true</code> if aFeatureMap is incuded in <b>this</b> obj.
059     * and <code>false</code> if not.
060     */
061   @Override
062   public boolean subsumes(FeatureMap aFeatureMap){
063     // null is included in everything
064     if (aFeatureMap == nullreturn true;
065 
066     if (this.size() < aFeatureMap.size()) return false;
067 
068     SimpleFeatureMapImpl sfm = (SimpleFeatureMapImpl)aFeatureMap;
069 
070     Object key;
071     Object keyValueFromAFeatureMap;
072     Object keyValueFromThis;
073 
074     for (int i = 0; i < sfm.count; i++) {
075       key = sfm.theKeys[i];
076       keyValueFromAFeatureMap = sfm.theValues[i];
077       int v = super.getSubsumeKey(key);
078       if (v < 0return false;
079       keyValueFromThis = theValues[v];//was: get(key);
080 
081       if  ( (keyValueFromThis == null && keyValueFromAFeatureMap != null||
082             (keyValueFromThis != null && keyValueFromAFeatureMap == null)
083           return false;
084 
085       /*ontology aware subsume implementation
086       ontotext.bp*/
087       if ((keyValueFromThis != null&& (keyValueFromAFeatureMap != null)) {
088 // commented out as ontology subsumes is now explicitly called if
089 // an ontology is provided. <valyt>       
090 //        if ( key.equals(LOOKUP_CLASS_FEATURE_NAME) ) {
091 //          /* ontology aware processing */
092 //          Object sfmOntoObj = sfm.get(LOOKUP_ONTOLOGY_FEATURE_NAME);
093 //          Object thisOntoObj = this.get(LOOKUP_ONTOLOGY_FEATURE_NAME);
094 //          if (null!=sfmOntoObj && null!= thisOntoObj) {
095 //            if (sfmOntoObj.equals(thisOntoObj)) {
096 //              boolean doSubsume = ontologySubsume(
097 //                          sfmOntoObj.toString(),
098 //                          keyValueFromAFeatureMap.toString(),
099 //                          keyValueFromThis.toString());
100 //              if (!doSubsume ) {
101 //                return false;
102 //              }
103 //            } // if ontologies are with the same url
104 //          } //if not null objects
105 //          else {
106 //            // incomplete feature set: missing ontology feature
107 //            return false;
108 //          }
109 //        } else {
110           /* processing without ontology awareness */
111           if (!keyValueFromThis.equals(keyValueFromAFeatureMap)) return false;
112 //        }  // else
113       // if
114     // for
115 
116     return true;
117   }//subsumes()
118 
119    /** Tests if <b>this</b> featureMap object includes aFeatureMap features. <br>
120    * If the feature map contains <code>class</code> and (optionally) <code>ontology</code> features:<br>
121    * then the ontologyLR is used to provide ontology based subsume with respect to the subClassOf relations.
122    @param ontologyLR an ontology to be used for the subsume
123    @param aFeatureMap object which will be included  or not in  <b>this</b>
124    * FeatureMap obj.
125    @return <code>true</code> if <b>this</b> includes aFeatureMap
126    * and <code>false</code> if not.
127    */
128   @Override
129   public boolean subsumes(Ontology ontologyLR, FeatureMap aFeatureMap) {
130 
131     if (ontologyLR == null) {
132       return this.subsumes(aFeatureMap);
133     }
134 
135     if (aFeatureMap == null)
136       return true;
137 
138     if (this.size() < aFeatureMap.size())
139       return false;
140 
141     SimpleFeatureMapImpl sfm = (SimpleFeatureMapImplaFeatureMap;
142 
143     Object key;
144     Object keyValueFromAFeatureMap;
145     Object keyValueFromThis;
146 
147     for (int i = 0; i < sfm.count; i++) {
148       key = sfm.theKeys[i];
149       keyValueFromAFeatureMap = sfm.theValues[i];
150       int v = super.getSubsumeKey(key);
151       if (v < 0)
152         return false;
153       keyValueFromThis = theValues[v]//was: get(key);
154 
155       if ( (keyValueFromThis == null && keyValueFromAFeatureMap != null||
156           (keyValueFromThis != null && keyValueFromAFeatureMap == null)
157           )
158         return false;
159 
160       /*ontology aware subsume implementation based on the ontology LR
161           ontotext.bp*/
162       if ( (keyValueFromThis != null&& (keyValueFromAFeatureMap != null)) {
163 
164         if (key.equals(LOOKUP_CLASS_FEATURE_NAME)) {
165           // ontology aware processing
166 
167           try {
168             OClass superClass = getClassForURIOrName(ontologyLR, keyValueFromAFeatureMap.toString());
169             OClass subClass = getClassForURIOrName(ontologyLR, keyValueFromThis.toString());
170 
171             if(superClass == null || subClass == nullreturn false;
172             if (DEBUG) {
173               Out.prln("\nClass in rule: " + keyValueFromAFeatureMap.toString());
174               Out.prln("\nClass in annotation: " + keyValueFromThis.toString());
175               Out.prln("\nisSubClassOf: " + subClass.isSubClassOf(superClass, OConstants.Closure.TRANSITIVE_CLOSURE));
176             }
177 
178             return subClass.equals(superClass|| 
179                 subClass.isSubClassOf(superClass, 
180                         OConstants.Closure.TRANSITIVE_CLOSURE);
181           catch (Exception ex) {
182             throw new gate.util.GateRuntimeException(ex);
183           }
184         }
185         else {
186           /* processing without ontology awareness */
187           if (!keyValueFromThis.equals(keyValueFromAFeatureMap))
188             return false;
189         // else
190 
191       // if
192     // for
193 
194     return true;
195   //subsumes(ontology)
196   
197   /**
198    * Look up the given name in the given ontology.  First we try
199    * treating the name as a complete URI and attempt to find the
200    * matching OClass.  If this fails (either the name is not a URI
201    * or there is no class by that URI) then we try again, treating
202    * the name as local to the default namespace of the ontology.
203    @param ontologyLR the ontology
204    @param name the URI or local resource name to look up
205    @return the corresponding OClass, or <code>null</code> if no
206    * suitable class could be found.
207    */
208   protected OClass getClassForURIOrName(Ontology ontologyLR, String name) {
209     OClass cls = null;
210     try {
211       cls = ontologyLR.getOClass(ontologyLR.createOURI(name));
212     }
213     catch(Exception e) {
214       // do nothing, but leave cls == null
215     }
216     if(cls == null) {
217       try {
218         cls = ontologyLR.getOClass(ontologyLR.createOURIForName(name));
219       }
220       catch(Exception e) {
221         // do nothing, but leave cls == null
222       }
223     }
224     return cls;
225   }
226 
227 
228   /** Tests if <b>this</b> featureMap object includes aFeatureMap but only
229     * for the those features present in the aFeatureNamesSet.
230     *
231     * However, if aFeatureMap contains a feature whose value is equal to
232    * gate.creole.ANNIEConstants.LOOKUP_CLASS_FEATURE_NAME (which is normally
233    * "class"), then GATE will attempt to match that feature using an ontology
234    * which it will try to retreive from a feature in both the feature map
235    * through which this method is called and in aFeatureMap. If these do not return
236    * identical ontologies, or if
237    * either feature map does not contain an ontology, then
238    * matching will fail, and this method will return false. In summary,
239    * this method will not work normally when aFeatureMap contains a feature
240    * with the name "class" if that feature is also in aFeatureNamesSet.
241     *
242     @param aFeatureMap which will be included or not in <b>this</b>
243     * FeatureMap obj.If this param is null then it will return true.
244     @param aFeatureNamesSet is a set of strings representing the names of the
245     * features that would be considered for subsumes. If aFeatureNamesSet is
246     <b>null</b> then subsumes(FeatureMap) will be called.
247     @return <code>true</code> if all features present in the aFeaturesNameSet
248     * from aFeatureMap are included in <b>this</b> obj, or <code>false</code>
249     * otherwise.
250     */
251   @Override
252   public boolean subsumes(FeatureMap aFeatureMap, Set<? extends Object> aFeatureNamesSet){
253     // This means that all features are taken into consideration.
254     if (aFeatureNamesSet == nullreturn this.subsumes(aFeatureMap);
255     // null is included in everything
256     if (aFeatureMap == nullreturn true;
257     // This means that subsumes is supressed.
258     if (aFeatureNamesSet.isEmpty()) return true;
259 
260     SimpleFeatureMapImpl sfm = (SimpleFeatureMapImpl)aFeatureMap;
261 
262     Object key;
263     Object keyValueFromAFeatureMap;
264     Object keyValueFromThis;
265 
266     for (int i = 0; i < sfm.count; i++) {
267       key = sfm.theKeys[i];
268 
269       if (!aFeatureNamesSet.contains(key))
270         continue;
271 
272       keyValueFromAFeatureMap = sfm.theValues[i];
273         keyValueFromThis = get(key);
274 
275       if  ( (keyValueFromThis == null && keyValueFromAFeatureMap != null||
276             (keyValueFromThis != null && keyValueFromAFeatureMap == null)
277           return false;
278 
279       if ((keyValueFromThis != null&& (keyValueFromAFeatureMap != null)) {
280 // Commented out as ontology subsumes is now explicitly called when an ontology
281 // is provided. <valyt>
282 //        if ( key.equals(LOOKUP_CLASS_FEATURE_NAME) ) {
283 //          /* ontology aware processing */
284 //          if (!aFeatureNamesSet.contains(LOOKUP_ONTOLOGY_FEATURE_NAME))
285 //            continue;
286 //
287 //          Object sfmOntoObj = sfm.get(LOOKUP_ONTOLOGY_FEATURE_NAME);
288 //          Object thisOntoObj = this.get(LOOKUP_ONTOLOGY_FEATURE_NAME);
289 //          if (null!=sfmOntoObj && null!= thisOntoObj) {
290 //            if (sfmOntoObj.equals(thisOntoObj)) {
291 //              if (! ontologySubsume(
292 //                          sfmOntoObj.toString(),
293 //                          keyValueFromAFeatureMap.toString(),
294 //                          keyValueFromThis.toString()))
295 //                return false;
296 //            } // if ontologies are with the same url
297 //          } //if not null objects
298 //          else {
299 //            // incomplete feature set: missing ontology feature
300 //            return false;
301 //          }
302 //        } else {
303           /*processing without ontology awareness*/
304           if (!keyValueFromThis.equals(keyValueFromAFeatureMap)) return false;
305 //        } //else
306       // if values not null
307     // for
308 
309     return true;
310   }// subsumes()
311 
312 
313   /**
314    * Overriden to fire events, so that the persistent objects
315    *  can keep track of what's updated
316    */
317   @Override
318   public Object put(Object key, Object value) {
319     Object result = super.put(key, value);
320     this.fireMapUpdatedEvent();
321     return result;
322   // put
323 
324   /**
325    * Overriden to fire events, so that the persistent objects
326    *  can keep track of what's updated
327    */
328   @Override
329   public Object remove(Object key) {
330     Object result = super.remove(key);
331     this.fireMapUpdatedEvent();
332     return result;
333   // remove
334 
335   @Override
336   public void clear() {
337     super.clear();
338     //tell the world if they're listening
339     this.fireMapUpdatedEvent();
340   // clear
341 
342   // Views
343   @Override
344   public Object clone() {
345     return super.clone();
346   //clone
347 
348   @Override
349   public boolean equals(Object o) {
350     return super.equals(o);
351   // equals
352 
353 //////////////////THE EVENT HANDLING CODE//////////////
354 //Needed so an annotation can listen to its features//
355 //and update correctly the database//////////////////
356   private transient Vector<FeatureMapListener> mapListeners;
357   /**
358    * Removes a gate listener
359    */
360   @Override
361   public synchronized void removeFeatureMapListener(FeatureMapListener l) {
362     if (mapListeners != null && mapListeners.contains(l)) {
363       @SuppressWarnings("unchecked")
364       Vector<FeatureMapListener> v = (Vector<FeatureMapListener>mapListeners.clone();
365       v.removeElement(l);
366       mapListeners = v;
367     }
368   //removeFeatureMapListener
369   /**
370    * Adds a gate listener
371    */
372   @Override
373   public synchronized void addFeatureMapListener(FeatureMapListener l) {
374     @SuppressWarnings("unchecked")
375     Vector<FeatureMapListener> v = mapListeners == null new Vector<FeatureMapListener>(2(Vector<FeatureMapListener>)mapListeners.clone();
376     if (!v.contains(l)) {
377       v.addElement(l);
378       mapListeners = v;
379     }
380   //addFeatureMapListener
381 
382   /**
383    *
384    */
385   protected void fireMapUpdatedEvent () {
386     if (mapListeners != null) {
387       Vector<FeatureMapListener> listeners = mapListeners;
388       int count = listeners.size();
389       if (count == 0return;
390       for (int i = 0; i < count; i++)
391         listeners.elementAt(i).featureMapUpdated();
392     }
393   }//fireMapUpdatedEvent
394 
395 //Commented out as ontology subsumes is now explicitly called when an ontology
396 //is provided. <valyt>
397 //  /**ontology enhanced subsume
398 //   * @param ontoUrl the url of the ontology to be used
399 //   * @return true if value1 subsumes value2 in the specified ontology */
400 //  protected boolean ontologySubsume(String ontoUrl,String value1,String value2) {
401 //    boolean result = false;
402 //    try {
403 //      URL url;
404 //      try {
405 //        url = new URL(ontoUrl);
406 //      } catch (MalformedURLException e){
407 //        throw new RuntimeException(
408 //        "\nin SimpleFeatureMapImpl on ontologySubsume()\n"
409 //        +e.getMessage()+"\n");
410 //      }
411 //
412 //      /* GET ONTOLOGY BY URL : a bit tricky reference
413 //      since the behaviour behind the getOntology method is
414 //      certainly static.
415 //      : should be temporary */
416 //      Ontology o = OntologyUtilities.getOntology(url);
417 //      OClass superClass = (OClass) o.getOResourceByName(value1);
418 //      OClass subClass = (OClass) o.getOResourceByName(value2);
419 //      if (subClass.equals(superClass))
420 //        return true;
421 //
422 //      if (subClass.isSubClassOf(superClass, OConstants.TRANSITIVE_CLOSURE))
423 //        return true;
424 //
425 //      //check for equivalency
426 //      Set<OClass> equiv = superClass.getEquivalentClasses();
427 //      result = equiv.contains(subClass);
428 //
429 //    } catch  (gate.creole.ResourceInstantiationException x) {
430 //      x.printStackTrace(Err.getPrintWriter());
431 //    }
432 //    return result;
433 //  } // ontologySubsume
434 
435 // class SimpleFeatureMapImpl