Constraint.java
001 /*
002  *  Constraint.java - transducer class
003  *
004  *  Copyright (c) 1995-2010, 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, 24/07/98
013  *
014  *  $Id: Constraint.java 12407 2010-03-29 14:05:09Z ian_roberts $
015  */
016 
017 package gate.jape;
018 
019 import gate.*;
020 import gate.creole.ontology.Ontology;
021 import gate.jape.constraint.*;
022 import gate.util.SimpleFeatureMapImpl;
023 
024 import java.util.*;
025 
026 /**
027  * A set of predicates/expressions that apply to a single
028  {@link Annotation} type. It doesn't extend PatternElement, even
029  * though it has to "match", because a set of Constraint must be applied
030  * together in order to avoid doing separate selectAnnotations calls for
031  * each one.
032  <p>
033  <b>Matching Logic:</b>The matching function of a non-negated
034  * constraint can be defined as, "There exists an annotation for which
035  * all of the predicates are true." Thus, given a collection of
036  * annotations, only a single annotation must meet all of the predicates
037  * of the constraint in order for the match to be successful.
038  <p>
039  <b>Negation:</b> A negated constraint means strictly the opposite -
040  * "There does not exist an annotation for which any of these predicates
041  * are true." Negation does <b>not</b> mean "There exists an annotation
042  * for which all of these predicates are false." Thus, negation makes
043  * more intuitive sense when thought of as applying to a set of
044  * annotations rather than to an individual annotation.
045  */
046 public class Constraint implements JapeConstants, gate.creole.ANNIEConstants,
047                        java.io.Serializable, Cloneable {
048   /** Construction from annot type string */
049   public Constraint(String annotType) {
050     this.annotType = annotType;
051   // Construction from annot type
052 
053   /**
054    * Construction from annot type and FeatureMap. Creates
055    {@link EqualPredicate}s for each feature in the map
056    */
057   public Constraint(String annotType, FeatureMap attrs) {
058     this(annotType);
059 
060     for(Map.Entry<Object, Object> entry : attrs.entrySet()) {
061       addAttribute(entry.getKey().toString(), entry.getValue());
062     }
063   // Construction from annot type and attribute sequence
064 
065   /**
066    * Construction from annot type and array of JdmAttributes.
067    *
068    @deprecated JdmAttributes are no longer used. Use
069    *             {@link ConstraintPredicate}s instead.
070    */
071   @Deprecated
072   public Constraint(String annotType, List<JdmAttribute> attrsArray) {
073     this(annotType);
074     for(JdmAttribute attr : attrsArray) {
075       addAttribute(attr.getName(), attr.getValue());
076     }
077   }
078 
079   /** The type of annotation we're looking for. */
080   private String annotType;
081 
082   /** Are we negated? */
083   private boolean negated = false;
084 
085   /** Set negation. */
086   public void negate() {
087     negated = true;
088   }
089 
090   /** Change the sign of the negation flag. */
091   public void changeSign() {
092     negated = !negated;
093   }
094 
095   /** Access to negation flag. */
096   public boolean isNegated() {
097     return negated;
098   }
099 
100   /** Get the type of annotation we're looking for. */
101   public String getAnnotType() {
102     return annotType;
103   }
104 
105   /**
106    * The list of predicates that must match the annotation features.
107    */
108   private List<ConstraintPredicate> predicates = new ArrayList<ConstraintPredicate>();
109 
110   /** Get the attributes that must be present on the matched annotation. */
111   public List<ConstraintPredicate> getAttributeSeq() {
112     return predicates;
113   }
114 
115   /** Predicate that acts on class feature, if one is set. */
116   protected ConstraintPredicate ontLookupClassPred;
117 
118   /**
119    * FeatureMap that may contain ontology-related features and values
120    * pulled from any predicates that operate on those features
121    */
122   protected FeatureMap ontFeatureMap;
123 
124   /** Add an attribute. */
125   public void addAttribute(ConstraintPredicate attr) {
126     predicates.add(attr);
127 
128     // check for LOOKUP_CLASS_FEATURE_NAME and
129     // LOOKUP_ONTOLOGY_FEATURE_NAME
130     // predicates and store some information about them once so we don't
131     // have to look it up on every matches call.
132     if(attr.getAccessor() instanceof AnnotationFeatureAccessor) {
133       String featureName = (String)attr.getAccessor().getKey();
134       if(featureName.equals(LOOKUP_CLASS_FEATURE_NAME)) {
135         ontLookupClassPred = attr;
136         getOntFeatureMap().put(LOOKUP_CLASS_FEATURE_NAME,
137                 ontLookupClassPred.getValue());
138         if(!attr.getOperator().equals(ConstraintPredicate.EQUAL))
139           gate.util.Err
140                   .println("Warning: If an ontology is specified at runtime, "
141                          "Ontology class feature will be compared using "
142                          "ontology subsumption, not " + attr.getOperator());
143       }
144       else if(featureName.equals(LOOKUP_ONTOLOGY_FEATURE_NAME)) {
145         getOntFeatureMap().put(LOOKUP_ONTOLOGY_FEATURE_NAME, attr.getValue());
146       }
147     }
148 
149   // addAttribute
150 
151   /**
152    * Generate a FeatureMap to perform ontology-related compare.
153    *
154    @return
155    */
156   protected FeatureMap getOntFeatureMap() {
157     if(ontFeatureMap == nullontFeatureMap = new SimpleFeatureMapImpl();
158 
159     return ontFeatureMap;
160   }
161 
162   /**
163    * Legacy support for adding an attribute using a jdm attribute.
164    *
165    @deprecated
166    @param attr
167    */
168   public void addAttribute(JdmAttribute attr) {
169     addAttribute(attr.getName(), attr.getValue());
170   // addAttribute
171 
172   /** Create and add an attribute. */
173   public void addAttribute(String name, Object value) {
174     ConstraintPredicate attr = Factory.getConstraintFactory().createPredicate(
175             name, value);
176     addAttribute(attr);
177   // addAttribute
178 
179   /**
180    * Add all predicates from the given collection to this object. Does
181    * not remove or replace any existing predicates.
182    *
183    @param attrs
184    */
185   public void addAttributes(Collection<ConstraintPredicate> attrs) {
186     // don't just call addAll because we want to check for
187     // ontology-related features
188     for(ConstraintPredicate pred : attrs) {
189       addAttribute(pred);
190     }
191   }
192 
193   /**
194    * Need cloning for processing of macro references. See comments on
195    <CODE>PatternElement.clone()</CODE>
196    */
197   public Object clone() {
198     Constraint newC = null;
199     try {
200       newC = (Constraint)super.clone();
201     }
202     catch(CloneNotSupportedException e) {
203       throw (new InternalError(e.toString()));
204     }
205     newC.annotType = annotType;
206     newC.predicates = (List<ConstraintPredicate>)((ArrayList)predicates).clone();
207     return newC;
208   // clone
209 
210   /**
211    * Returns a boolean value indicating whether this Constraint is
212    * equivalent to the given Constraint. If the given object is not a
213    * Constraint, compares the two objects using
214    <CODE>Object.equals()</CODE>.
215    */
216   public boolean equals(Object other) {
217     if(!(other instanceof Constraint)) return super.equals(other);
218     Constraint o = (Constraint)other;
219 
220     return (o.negated == negated && o.annotType.equals(annotType&& o.predicates
221             .equals(predicates));
222   }
223 
224   /**
225    * Returns an integer hash code for this object.
226    */
227   public int hashCode() {
228     int hashCode = negated ? 37 17;
229     hashCode = 37 * hashCode + annotType.hashCode();
230     hashCode = 37 * hashCode + predicates.hashCode();
231     return hashCode;
232   }
233 
234   /**
235    * Finish: replace dynamic data structures with Java arrays; called
236    * after parsing.
237    */
238   public void finish() {
239     /*
240      * if(attrs1 == null || attrs1.size() == 0) { attrs2 = new
241      * JdmAttribute[0]; attrs1 = null; return; } int attrsLen =
242      * attrs1.size(); attrs2 = new JdmAttribute[attrsLen];
243      *
244      * int i = 0; //for(Enumeration e = attrs1.getElements();
245      * e.hasMoreElements(); i++) { // attrs2[i] = (JdmAttribute)
246      * e.nextElement(); //} Iterator iter = attrs1.keySet().iterator();
247      * while(iter.hasNext()) { String name = (String) iter.next();
248      * Object value = attrs1.get(name); attrs2[i++] = new
249      * JdmAttribute(name, value); } attrs1 = null;
250      */
251   // finish
252 
253   /** Create a string representation of the object. */
254   public String toString() {
255     return getDisplayString("Constraint: ");
256   }
257 
258   /** Create a string representation of the object. */
259   public String getDisplayString(String prefix) {
260     StringBuffer buf = new StringBuffer(prefix);
261     if(negatedbuf.append("!");
262     buf.append(annotType);
263     if(!predicates.isEmpty()) {
264       buf.append("(");
265       buf.append(getAttributesString());
266       buf.append(")");
267     }
268     return buf.toString();
269   // toString
270 
271   /**
272    * Returns string representation of all the attributes that is
273    * appropriate for display.
274    *
275    @return
276    */
277   public String getAttributesString() {
278     StringBuffer retVal = new StringBuffer();
279 
280     for(Iterator<ConstraintPredicate> iter = predicates.iterator(); iter
281             .hasNext();) {
282       ConstraintPredicate attr = iter.next();
283       retVal.append(attr);
284       if(iter.hasNext()) retVal.append(",");
285     }
286 
287     return retVal.toString();
288   }
289 
290   public String shortDesc() {
291     return getDisplayString("");
292   // shortDesc
293 
294   /**
295    * Invoke {@link #matches(Annotation, Ontology, Object)} on all provided
296    * annotations.
297    *
298    @param annots collection of Annotations to test
299    @param ontology optional Ontology to compare ont-specific features
300    @return Collection of annotations which matches successfully
301    *         against predicates.
302    */
303   public List<Annotation> matches(Collection<Annotation> annots,
304           Ontology ontology, AnnotationSet context) {
305     List<Annotation> retVal = new ArrayList<Annotation>();
306 
307     if(annots == nullreturn retVal;
308 
309     for(Annotation annot : annots) {
310       if(matches(annot, ontology, context)) {
311         retVal.add(annot);
312         // small optimization - if constraint is negated and there is a
313         // match,
314         // don't bother checking all the other annots since a negated
315         // constraint
316         // fails if even a single annotation matches it.
317         if(negatedbreak;
318       }
319     }
320 
321     return retVal;
322   }
323 
324   /**
325    * Test if an annotation is of the proper type for this constraint and
326    * if it complies with the {@link ConstraintPredicate}s of this
327    * constraint.
328    *
329    @param annot an Annotation
330    @param ontologyLR optional ontology to use when comparing
331    *          ont-related features
332    @return <code>true</code> if the annotation is of the proper type
333    *         and matches all predicates. If the constraint is negated,
334    *         an annotation need only match a single predicate to return
335    *         true.
336    */
337   public final boolean matches(Annotation annot, Ontology ontologyLR, 
338           AnnotationSet context) {
339     if(annot == nullreturn false;
340     if(!annot.getType().equals(getAnnotType())) return false;
341 
342     // if no predicates, then we have a match based solely on the annot
343     // type.
344     if(predicates == null || predicates.isEmpty()) return true;
345 
346     for(ConstraintPredicate predicate : predicates) {
347       boolean successful = false;
348       try {
349         // do some special checking if this predicate deals with the
350         // ontology.  Note that we assume the operator for the predicate
351         // was ==. We issue a warning when the predicate is first set
352         // if it's anything else.
353         if(predicate == ontLookupClassPred && ontologyLR != null)
354           successful = annot.getFeatures().subsumes(ontologyLR, ontFeatureMap);
355         else successful = predicate.matches(annot, context);
356       }
357       catch(JapeException je) {
358         gate.util.Err.println(je.getMessage());
359       }
360       if(!successful && !negatedreturn false;
361       if(successful && negatedreturn true;
362       // else, keep checking the rest of the predicates
363     // checked all predicates
364 
365     /*
366      * If all features had to match (ANDed), then we can return true
367      * because no test failed. If at least one feature had to match
368      * (ORed), then we have to return false because no test succeeded.
369      */
370     if(!negated)
371       return true;
372     else // if negated
373     return false;
374 
375   }
376 
377   /**
378    * Test if an annotation is of the proper type for this constraint and
379    * if it complies with the {@link ConstraintPredicate}s of this
380    * constraint.
381    *
382    @param annot a Annotation
383    @return <code>true</code> if the annotation is of the proper type
384    *         and matches all predicates. If the constraint is negated,
385    *         an annotation need only match a single predicate to return
386    *         true.
387    */
388   public boolean matches(Annotation annot, AnnotationSet context) {
389     return matches(annot, null, context);
390   }
391 
392 // class Constraint
393 
394 // $Log$
395 // Revision 1.14 2006/01/06 22:37:24 kwilliams
396 // Implement equals(Object) and hashCode() so we can usefully be put
397 // into Sets, HashMaps, etc.
398 //
399 // Revision 1.13 2006/01/06 22:03:04 kwilliams
400 // Define other constructors in terms of Constraint(String,FeatureMap)
401 //
402 // Revision 1.12 2005/07/15 15:37:32 valyt
403 // New toString() method from Ken Williams
404 //
405 // Revision 1.11 2005/01/11 13:51:36 ian
406 // Updating copyrights to 1998-2005 in preparation for v3.0
407 //
408 // Revision 1.10 2004/07/21 17:10:07 akshay
409 // Changed copyright from 1998-2001 to 1998-2004
410 //
411 // Revision 1.9 2004/03/25 13:01:14 valyt
412 // Imports optimisation throughout the Java sources
413 // (to get rid of annoying warnings in Eclipse)
414 //
415 // Revision 1.8 2001/09/13 12:09:49 kalina
416 // Removed completely the use of jgl.objectspace.Array and such.
417 // Instead all sources now use the new Collections, typically ArrayList.
418 // I ran the tests and I ran some documents and compared with keys.
419 // JAPE seems to work well (that's where it all was). If there are
420 // problems
421 // maybe look at those new structures first.
422 //
423 // Revision 1.7 2000/11/08 16:35:02 hamish
424 // formatting
425 //
426 // Revision 1.6 2000/10/26 10:45:30 oana
427 // Modified in the code style
428 //
429 // Revision 1.5 2000/10/16 16:44:33 oana
430 // Changed the comment of DEBUG variable
431 //
432 // Revision 1.4 2000/10/10 15:36:35 oana
433 // Changed System.out in Out and System.err in Err;
434 // Added the DEBUG variable seted on false;
435 // Added in the header the licence;
436 //
437 // Revision 1.3 2000/05/25 16:10:41 valyt
438 // JapeGUI is working
439 //
440 // Revision 1.2 2000/04/20 13:26:41 valyt
441 // Added the graph_drawing library.
442 // Creating of the NFSM and DFSM now works.
443 //
444 // Revision 1.1 2000/02/23 13:46:05 hamish
445 // added
446 //
447 // Revision 1.1.1.1 1999/02/03 16:23:01 hamish
448 // added gate2
449 //
450 // Revision 1.8 1998/11/05 13:36:30 kalina
451 // moved to use array of JdmAttributes for selectNextAnnotation instead
452 // of a sequence
453 //
454 // Revision 1.7 1998/11/01 22:35:56 kalina
455 // attribute seq hashtable mod
456 //
457 // Revision 1.6 1998/09/23 12:48:02 hamish
458 // negation added; noncontiguous BPEs disallowed
459 //
460 // Revision 1.5 1998/08/12 15:39:34 hamish
461 // added padding toString methods
462 //
463 // Revision 1.4 1998/07/31 13:12:14 mks
464 // done RHS stuff, not tested
465 //
466 // Revision 1.3 1998/07/30 11:05:15 mks
467 // more jape
468 //
469 // Revision 1.2 1998/07/29 11:06:55 hamish
470 // first compiling version
471 //
472 // Revision 1.1.1.1  1998/07/28 16:37:46  hamish
473 // gate2 lives