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 == null) ontFeatureMap = 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 ? 0 : 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(negated) buf.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 == null) return 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(negated) break;
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 == null) return 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 && !negated) return false;
361 if(successful && negated) return 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
|