/* * Constraint.java - transducer class * * Copyright (c) 1998-2001, The University of Sheffield. * * This file is part of GATE (see http://gate.ac.uk/), and is free * software, licenced under the GNU Library General Public License, * Version 2, June 1991 (in the distribution as file licence.html, * and also available at http://gate.ac.uk/gate/licence.html). * * Hamish Cunningham, 24/07/98 * * Modifications by Luc Plamondon, Universit� de Montr�al, 20/11/03: * - Migrated original file to the ca.umontreal.iro.rali.gate.jape package * - The attributes/values are stored in a list of JdmAttribute objects, * instead of a FeatureMap. This allows us to store a comparison operator * other than "equal" in addition to a feature/value pair in each * JdmAttribute object. * - Removed a few constructors that were not used elsewhere. * - Added a series of subsume methods (initially in gate.FeatureMap, but due * to cross-package restrictions on attributes/methods and due to speed * considerations, I did not extend a new class from SimpleFeatureMapImpl * and did not overload the subsume method). * * $Id$ */ package ca.umontreal.iro.rali.gate.jape; import java.net.*; import java.util.*; import java.util.regex.*; import gate.annotation.*; import gate.creole.ontology.*; import gate.util.*; import gate.*; /** * An individual annotation/attributes/values/operator expression. * It doesn't extend PatternElement, even though it has to "match", because a * set of Constraint must be applied together in order to avoid doing separate * selectAnnotations calls for each one. */ public class Constraint implements JapeConstants, gate.creole.ANNIEConstants, java.io.Serializable, Cloneable { /** Debug flag */ private static final boolean DEBUG = false; /** Construction from annot type string */ public Constraint(String annotType) { this.annotType = annotType; attributeList = new LinkedList(); } // Construction from annot type /** The type of annnotation we're looking for. */ private String annotType; /** Are we negated? */ private boolean negated = false; /** Set negation. */ public void negate() { negated = true; } /** Access to negation flag. */ public boolean isNegated() { return negated; } /** Change the sign of the negation flag. */ public void changeSign() { negated = !negated; } /** Get the type of annnotation we're looking for. */ public String getAnnotType() { return annotType; } /** The list of attributes that must match the annotation features. Attributes are JdmAttribute objects. */ private LinkedList attributeList; /** Get the list of attributes that must match the annotation features. Attributes are JdmAttribute objects. */ public LinkedList getAttributeSeq() { return attributeList; } /** Get the attributes that must be present on the matched annotation. */ public JdmAttribute[] getAttributeArray() { return (JdmAttribute[]) attributeList.toArray(); } /** Add an attribute. */ public void addAttribute(JdmAttribute attr) { attributeList.add(attr); } // addAttribute /** Need cloning for processing of macro references. See comments on * <CODE>PatternElement.clone()</CODE> */ public Object clone() { Constraint newC = null; try { newC = (Constraint) super.clone(); } catch(CloneNotSupportedException e) { throw(new InternalError(e.toString())); } newC.annotType = annotType; // Shallow clone. Is it ok? newC.attributeList = (LinkedList) attributeList.clone(); return newC; } // clone /** Finish: replace dynamic data structures with Java arrays; called * after parsing. */ public void finish() { /* if(attrs1 == null || attrs1.size() == 0) { attrs2 = new JdmAttribute[0]; attrs1 = null; return; } int attrsLen = attrs1.size(); attrs2 = new JdmAttribute[attrsLen]; int i = 0; //for(Enumeration e = attrs1.getElements(); e.hasMoreElements(); i++) { // attrs2[i] = (JdmAttribute) e.nextElement(); //} Iterator iter = attrs1.keySet().iterator(); while(iter.hasNext()) { String name = (String) iter.next(); Object value = attrs1.get(name); attrs2[i++] = new JdmAttribute(name, value); } attrs1 = null; */ } // finish /** Create a string representation of the object. */ public String toString() { return toString(""); } /** Create a string representation of the object. */ public String toString(String pad) { String newline = Strings.getNl(); StringBuffer buf = new StringBuffer( pad + "Constraint: annotType(" + annotType + "); attrs(" + newline + pad ); buf.append(attributeList.toString()); buf.append(newline + pad + ") Constraint." + newline); return buf.toString(); } // toString public String shortDesc() { String res = annotType + "("; res += attributeList.toString(); res += ")"; return res; } // shortDesc /** Test if an annotation complies with at least one feature and comparison operators of this constraint's attributes. @param annot an annotation @return <code>true</code> if the annotation tests successfully with the values and operators of at least one attribute of this constraint and <code>false</code> if not. */ public boolean subsumesOne(SimpleFeatureMapImpl annot) { return subsumesOneOrAll(annot, false); } /** Test if an annotation complies with all features and comparison operators of this constraint's attributes. @param annot an annotation @return <code>true</code> if the annotation tests successfully with the values and operators from every attribute of this constraint and <code>false</code> if not. */ public boolean subsumes(SimpleFeatureMapImpl annot) { return subsumesOneOrAll(annot, true); } /** Test if an annotation complies with the features and comparison operators of this constraint's attributes. Each feature is tested and then a big AND can be done if <code>anded=true</code>, or a big OR if <code>anded=false</code>. @param annot an annotation @param anded <code>true</code>: all features must match. <code>false</code>: at least one feature must match. @return <code>true</code> if the annotation tests successfully with the values and operators from the attribute of this constraint and <code>false</code> if not. */ protected boolean subsumesOneOrAll(SimpleFeatureMapImpl annot, boolean anded){ boolean ored = !anded; // null is included in everything if (attributeList == null) return true; /* General algorithm: we will now iterate over all the features to test. If we are ANDing them, return false as soon as a test fails. If we are ORing them, return true as soon as a test succeeds. */ JdmAttribute attribute; for (ListIterator i = attributeList.listIterator(); i.hasNext(); ) { attribute = (JdmAttribute) i.next(); String attributeName = attribute.getName(); Object attributeValue = attribute.getValue(); int attributeOp = attribute.getOperator(); Object annotValue = annot.get(attributeName); if (attributeName == null || attributeValue == null) continue; /* if an attribute pertains to a class from the ontology, perform ontology-aware subsume */ if (attributeName.equals(LOOKUP_CLASS_FEATURE_NAME) && annotValue != null) { //find the ontology url amongst the other attributes of the constraint Object constraintOntoObj = null; JdmAttribute currentAttribute = null; for (ListIterator j = attributeList.listIterator(); j.hasNext(); ) { currentAttribute = (JdmAttribute) j.next(); if (currentAttribute.getName().equals(LOOKUP_ONTOLOGY_FEATURE_NAME)) { constraintOntoObj = currentAttribute.getValue(); continue; } } /* continue only if ont. url from constraint and annot. are the same, otherwise ignore this attribute (default with true) */ Object thisOntoObj = annot.get(LOOKUP_ONTOLOGY_FEATURE_NAME); if (constraintOntoObj != null && thisOntoObj != null) { if (constraintOntoObj.equals(thisOntoObj)) { boolean doSubsume = ontologySubsume( constraintOntoObj.toString(), attributeValue.toString(), annotValue.toString()); if (attributeOp == JapeConstants.EQUAL) { if (!doSubsume && anded) return false; if (doSubsume && ored) return true; } if (attributeOp == JapeConstants.NOT_EQUAL) { if (doSubsume && anded) return false; if (!doSubsume && ored) return true; } // else: accept (ignore) } // if ontologies are with the same url } //if not null objects } /* process without ontology awareness */ try { // EQUAL if (attributeOp == JapeConstants.EQUAL) { boolean successful = equalSubsume(attributeName, attributeValue, annotValue); if (!successful && anded) return false; if (successful && ored) return true; } //if (attributeOp == JapeConstants.EQUAL) // NOT_EQUAL if (attributeOp == JapeConstants.NOT_EQUAL) { boolean successful = equalSubsume(attributeName, attributeValue, annotValue); if (successful && anded) return false; if (!successful && ored) return true; } //if (attributeOp == JapeConstants.NOT_EQUAL) // REGEXP if (attributeOp == JapeConstants.REGEXP) { // create a default value if annotation value is null if (annotValue == null) annotValue = new String(); boolean successful = regexpSubsume(attributeName, attributeValue, annotValue); if (!successful && anded) return false; if (successful && ored) return true; } //if (attributeOp == JapeConstants.REGEXP) // NOT_REGEXP if (attributeOp == JapeConstants.NOT_REGEXP) { // create a default value if annotation value is null if (annotValue == null) annotValue = new String(); boolean successful = regexpSubsume(attributeName, attributeValue, annotValue); if (successful && anded) return false; if (!successful && ored) return true; } //if (attributeOp == JapeConstants.NOT_REGEXP) // GREATER if (attributeOp == JapeConstants.GREATER) { boolean successful = greaterSubsume(attributeName, attributeValue, annotValue); if (!successful && anded) return false; if (successful && ored) return true; } //if (attributeOp == JapeConstants.GREATER) // LESSER_OR_EQUAL if (attributeOp == JapeConstants.LESSER_OR_EQUAL) { boolean successful = greaterSubsume(attributeName, attributeValue, annotValue); if (successful && anded) return false; if (!successful && ored) return true; } //if (attributeOp == JapeConstants.GREATER) // LESSER if (attributeOp == JapeConstants.LESSER) { boolean successful = lesserSubsume(attributeName, attributeValue, annotValue); if (!successful && anded) return false; if (successful && ored) return true; } //if (attributeOp == JapeConstants.GREATER) // GREATER_OR_EQUAL if (attributeOp == JapeConstants.GREATER_OR_EQUAL) { boolean successful = lesserSubsume(attributeName, attributeValue, annotValue); if (successful && anded) return false; if (!successful && ored) return true; } //if (attributeOp == JapeConstants.GREATER) } catch (JapeException je) { gate.util.Err.println(je.getMessage()); if (anded) return false; } } // checked all attributes /* If all features had to match (ANDed), then we can return true because no test has failed so far. If at least one feature had to match (ORed), then we have to return false because no test has succeeded so far. */ if (anded) return true; else // if (ored) return false; } //subsumes() /** For a given attribute, test whether the values from the annotation and from the constraint are equal. They must have the same type. The method use a.equals(b) to test for equality. @param attributeName the name of the attribute, in this constraint @param attributeValue the value of the attribute, in this constraint @param annotValue the value of the attribute, in the annotation @return true if both values are equal, false otherwise or if the values have different type or if annotValue is null. @throws JapeException if the attribute in the constraint has an unexpected type (other than String/Long/Double/Boolean). This probably means that the grammar parser (ParseCpsl.jj) has been changed to allow that. */ private static boolean equalSubsume(Object attributeName, Object attributeValue, Object annotValue) throws JapeException { if (annotValue == null) return false; if (attributeValue.equals(annotValue)) return true; /* The constraint's attribute can be a String/Long/Double/Boolean. The annotation's attribute must be of the same type, otherwise equals will return false. In that case, let's suppose the annot's attrib. is a String and let's try to convert it to the same type as the constraint. */ if (annotValue instanceof String) { String annotValueString = (String) annotValue; if (attributeValue instanceof String) return false; // the comparison has already been made try { // the constraint's attribute is a Long if (attributeValue instanceof Long) { if (attributeValue.equals(Long.valueOf(annotValueString))) return true; else return false; } // the constraint's attribute is a Double if (attributeValue instanceof Double){ if (attributeValue.equals(Double.valueOf(annotValueString))) return true; else return false; } // the constraint's attribute is a Boolean if (attributeValue instanceof Boolean) { if (attributeValue.equals(Boolean.valueOf(annotValueString))) return true; else return false; } // if we reach that point, it means constraint has an unexpected type! throw new JapeException("Warning! Attribute \""+attributeName+"\" from a grammar rule has a value that is not a String/Long/Double/Boolean. This should not be allowed."); } catch (NumberFormatException otherType) { // annot is a String and cannot be converted to Long/Double/Boolean, // cannot be equal return false; } } //if String return false; } /** For a given attribute, test whether the value from the annotation matches the regular expression stated in the constraint. @param attributeName the name of the attribute, in this constraint @param attributeValue the value of the attribute, in this constraint (that is, the regular expression) @param annotValue the value of the attribute, in the annotation @return true if annotValue matches the attributeValue regexp, false otherwise @throws JapeException if the attribute in the annotation is not a String (pattern matching is irrelevant in that case) */ private static boolean regexpSubsume(Object attributeName, Object attributeValue, Object annotValue) throws JapeException { String annotValueString; Pattern constraintPattern; if (annotValue instanceof String) { annotValueString = (String) annotValue; if (annotValueString == null) annotValueString = new String(); constraintPattern = (Pattern) attributeValue; if (constraintPattern.matcher(annotValueString).matches()) { return true; } else { return false; } } else { throw new JapeException("Cannot do pattern matching on attribute \""+attributeName.toString()+"\". Are you sure the value is a string?"); } } /** For a given attribute, test whether the value from the annotation is greater than the value from this constraint. This method uses the compareTo method from the java.lang.Comparable interface to perform the test. If any of the values cannot be cast to Comparable (Boolean, for example, are not Comparable), this method returns false. <p> Because sometimes numerical values are stored in annotations as strings (Token.length for example), this method tries to convert the annotation's value to a Double or a Long when the compareTo test fails, and then performs the compareTo test again. @param attributeName the name of the attribute, in this constraint @param attributeValue the value of the attribute, in this constraint @param annotValue the value of the attribute, in the annotation @return true if annotValue is greater than attributeValue, false otherwise or if annotValue cannot be cast to a Long/Double, or if annotValue cannot be parsed to a Long/Double. @throws JapeException if the attribute in the annotation cannot be compared or parsed to an object of the same type as the attribute in the constraint, or if the attribute in the annotation is null. */ private static boolean greaterSubsume(Object attributeName, Object attributeValue, Object annotValue) throws JapeException { if (annotValue == null) throw new JapeException("Cannot compare attribute \"" + attributeName.toString() + "\" with <, >, <= or >= because annotation value is null."); /* attributeValue can be a String/Long/Double/Boolean. First assume that annotValue is of the same type as attributeValue and call compareTo, except for Boolean. If it fails, assume that annotValue is a string then convert it to the same type as attributeValue and test. */ // first check if we can call the compareTo method if (attributeValue instanceof Comparable) { Comparable attributeComparable = (Comparable) attributeValue; // then assume attribute and annot are of the same type and compare try { if (attributeComparable.compareTo(annotValue) < 0) return true; else return false; } // attribute and annot do not have same type catch (ClassCastException notSameType) { // if annotValue is a String, there is still hope to parse it if (annotValue instanceof String) { String annotValueString = (String) annotValue; if (attributeValue instanceof String) return false; // the comparison has already been made try { // if attribute is a Long, parse annot as a Long if (attributeComparable instanceof Long) { if (attributeComparable.compareTo( Long.valueOf(annotValueString)) < 0) return true; else return false; } // if attribute is a Double if (attributeComparable instanceof Double) { if (attributeComparable.compareTo( Double.valueOf(annotValueString)) < 0) return true; else return false; } // annotValue is of another type, so cannot compare return false; } //try catch (NumberFormatException nfe) { // attribute is a Long/Double, but annot is not: cannot compare throw new JapeException("Cannot compare values for attribute \"" + attributeName.toString() + "\" because \"" + attributeValue.toString() + "\" and/or \"" + annotValue.toString() + "\" do not have comparable types."); } } // if instanceof String } //catch notSameType } //if instanceof Comparable else { // cannot compare this type, so throw an exception throw new JapeException("Cannot compare values for attribute \"" + attributeName.toString() + "\" because \"" + attributeValue.toString() + "\" and/or \"" + annotValue.toString() + "\" do not have comparable types."); } return true; } /** For a given attribute, test whether the value from the annotation is lesser than the value from this constraint. This method uses the compareTo method from the java.lang.Comparable interface to perform the test. If any of the values cannot be cast to Comparable (Boolean, for example, are not Comparable), this method returns false. <p> Because sometimes numerical values are stored in annotations as strings (Token.length for example), this method tries to convert the annotation's value to a Double or a Long when the compareTo test fails, and then performs the compareTo test again. @param constraintName the name of the attribute, in the constraint @param attributeValue the value of the attribute, in the constraint @param annotValue the value of the attribute, in the annotation @return true if annotValue is lesser than attributeValue, false otherwise or if annotValue cannot be cast to a Long/Double, or if annotValue cannot be parsed to a Long/Double. @throws JapeException if the attribute in the annotation cannot be compared or parsed to an object of the same type as the attribute in the constraint, or if the attribute in the annotation is null. */ private static boolean lesserSubsume(Object attributeName, Object attributeValue, Object annotValue) throws JapeException { if (annotValue == null) throw new JapeException("Cannot compare attribute \"" + attributeName.toString() + "\" with <, >, <= or >= because annotation value is null."); /* attributeValue can be a String/Long/Double/Boolean. First assume that annotValue is of the same type as attributeValue and call compareTo, except for Boolean. If it fails, assume that annotValue is a string then convert it to the same type as attributeValue and test. */ // first check if we can call the compareTo method if (attributeValue instanceof Comparable) { Comparable attributeComparable = (Comparable) attributeValue; // then assume attribute and annot are of the same type and compare try { if (attributeComparable.compareTo(annotValue) > 0) return true; else return false; } // attribute and annot do not have same type catch (ClassCastException notSameType) { // if annotValue is a String, there is still hope to parse it if (annotValue instanceof String) { String annotValueString = (String) annotValue; if (attributeValue instanceof String) return false; // the comparison has already been made try { // if attribute is a Long, parse annot as a Long if (attributeComparable instanceof Long) { if (attributeComparable.compareTo( Long.valueOf(annotValueString)) > 0) return true; else return false; } // if attribute is a Double if (attributeComparable instanceof Double) { if (attributeComparable.compareTo( Double.valueOf(annotValueString)) > 0) return true; else return false; } // annotValue is of another type, so cannot compare return false; } //try catch (NumberFormatException nfe) { // attribute is a Long/Double, but annot is not: cannot compare throw new JapeException("Cannot compare values for attribute \"" + attributeName.toString() + "\" because \"" + attributeValue.toString() + "\" and/or \"" + annotValue.toString() + "\" do not have comparable types."); } } // if instanceof String } //catch notSameType } //if instanceof Comparable else { // cannot compare this type, so throw an exception throw new JapeException("Cannot compare values for attribute \"" + attributeName.toString() + "\" because \"" + attributeValue.toString() + "\" and/or \"" + annotValue.toString() + "\" do not have comparable types."); } return true; } /**ontology enhanced subsume * @param ontoUrl the url of the ontology to be used * @return true if value1 subsumes value2 in the specified ontology */ private static boolean ontologySubsume(String ontoUrl,String value1,String value2) { // this method is a copy of gate.util.SimpleFeatureMapImpl.ontologySubsume boolean result = false; try { URL url; try { url = new URL(ontoUrl); } catch (MalformedURLException e){ throw new RuntimeException( "\nin SimpleFeatureMapImpl on ontologySubsume()\n" +e.getMessage()+"\n"); } /* GET ONTOLOGY BY URL : a bit tricky reference since the behaviour behind the getOntology method is certainly static. : should be temporary */ Ontology o = OntologyUtilities.getOntology(url); OClass c1 = (OClass) o.getOResourceByName(value1); OClass c2 = (OClass) o.getOResourceByName(value2); if (null!= c1 && null!= c2) { if (c1.equals(c2)) { result = true; } else { Set subs1; subs1 = c1.getSubClasses(OConstants.TRANSITIVE_CLOSURE); if (subs1.contains(c2)) result = true; } // else } // if not null classes } catch (gate.creole.ResourceInstantiationException x) { x.printStackTrace(Err.getPrintWriter()); } return result; } // ontologySubsume } // class Constraint