1   /*
2    *  BasicPatternElement.java - transducer class
3    *
4    *  Copyright (c) 1998-2005, 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, 24/07/98
12   *
13   *  $Id: BasicPatternElement.java,v 1.16 2005/07/12 09:37:04 valyt Exp $
14   */
15  
16  
17  package gate.jape;
18  
19  import java.util.*;
20  
21  import gate.*;
22  import gate.annotation.AnnotationSetImpl;
23  import gate.util.Out;
24  import gate.util.Strings;
25  
26  
27  /**
28    * A pattern element within curly braces. Has a set of Constraint,
29    * which all must be satisfied at whatever position the element is being
30    * matched at.
31    */
32  public class BasicPatternElement
33  extends PatternElement implements JapeConstants, java.io.Serializable
34  {
35    /** Debug flag */
36    private static final boolean DEBUG = false;
37  
38    /** A set of Constraint. Used during parsing. */
39    private ArrayList constraints1;
40  
41    /** A set of Constraint. Used during matching. */
42    private Constraint[] constraints2;
43  
44    /** A map of constraint annot type to constraint. Used during parsing. */
45    private HashMap constraintsMap;
46  
47    /** Cache of the last position we failed at (-1 when none). */
48    private int lastFailurePoint = -1;
49  
50    /** The position of the next available annotation of the type required
51      * by the first constraint.
52      */
53    //private MutableInteger nextAvailable = new MutableInteger();
54  
55    /** The set of annotations we have matched. */
56    private AnnotationSet matchedAnnots;
57  
58    /** Access to the annotations that have been matched. */
59    public AnnotationSet getMatchedAnnots() { return matchedAnnots; }
60  
61    /** Construction. */
62    public BasicPatternElement() {
63      constraintsMap = new HashMap();
64      constraints1 = new ArrayList();
65      lastFailurePoint = -1;
66      //nextAvailable = new MutableInteger();
67      matchedAnnots = new AnnotationSetImpl((Document) null);
68    } // construction
69  
70    /** Need cloning for processing of macro references. See comments on
71      * <CODE>PatternElement.clone()</CODE>
72      */
73    public Object clone() {
74      BasicPatternElement newPE = (BasicPatternElement) super.clone();
75      newPE.constraintsMap = (HashMap) constraintsMap.clone();
76      newPE.constraints1 = new ArrayList();
77      int consLen = constraints1.size();
78      for(int i = 0; i < consLen; i++)
79        newPE.constraints1.add(
80          ((Constraint) constraints1.get(i)).clone()
81        );
82  //    newPE.matchedAnnots = new AnnotationSetImpl((Document) null);
83  //    newPE.matchedAnnots.addAll(matchedAnnots);
84      return newPE;
85    } // clone
86  
87    /** Add a constraint. Ensures that only one constraint of any given
88      * annotation type exists.
89      */
90    public void addConstraint(Constraint newConstraint) {
91      /* if the constraint is already mapped, put it's attributes on the
92       * existing constraint, else add it
93       */
94      String annotType = newConstraint.getAnnotType();
95      Constraint existingConstraint = (Constraint) constraintsMap.get(annotType);
96      if(existingConstraint == null) {
97        constraintsMap.put(annotType, newConstraint);
98        constraints1.add(newConstraint);
99      }
100     else {
101       FeatureMap newAttrs = newConstraint.getAttributeSeq();
102       FeatureMap existingAttrs =
103         existingConstraint.getAttributeSeq();
104         existingAttrs.putAll(newAttrs);
105       if(newConstraint.isNegated())
106         existingConstraint.negate();
107     }
108   } // addConstraint
109 
110 
111   /**
112    * Indicates whether this constraint deals with only one type of annotation or
113    * multiple types.
114    */
115   public boolean isMultiType() {
116       return constraints2 != null ? constraints2.length > 1 :
117              constraints1 != null ? constraints1.size() > 1 :
118              false;
119   }
120 
121   /** Finish: replace dynamic data structures with Java arrays; called
122     * after parsing.
123     */
124   public void finish() {
125     int j=0;
126     constraints2 = new Constraint[constraints1.size()];
127     for(Iterator i=constraints1.iterator(); i.hasNext(); ) {
128       constraints2[j] = (Constraint) i.next();
129       constraints2[j++].finish();
130     }
131     constraints1 = null;
132   } // finish
133 
134   /** Reset: clear last failure point and matched annotations list. */
135   public void reset() {
136     super.reset();
137     lastFailurePoint = -1;
138     //nextAvailable.value = -1;
139     matchedAnnots = new AnnotationSetImpl((Document) null);
140   } // reset
141 
142   /** Multilevel rollback of the annotation cache. */
143   public void rollback(int arity) {
144     //Debug.pr(this, "BPE rollback(" + arity + "), matchHistory.size() = " +
145     //          matchHistory.size());
146     //Debug.nl(this);
147 
148     for(int i=0; i<arity; i++) {
149       matchedAnnots.removeAll((AnnotationSet) matchHistory.pop());
150     }
151   } // rollback
152 
153   /** Does this element match the document at this position? */
154   public boolean matches (
155     Document doc, int position, MutableInteger newPosition
156   ) {
157     final int startingCacheSize = matchedAnnots.size();
158     AnnotationSet addedAnnots = new AnnotationSetImpl((Document) null);
159 
160     //Debug.pr(this, "BPE.matches: trying at position " + position);
161     //Debug.nl(this);
162     int rightmostEnd = -1;
163     int end = doc.getContent().size().intValue();
164     MutableInteger nextAvailable = new MutableInteger();
165     int nextAvailOfFirstConstraint = -1;
166 
167     for(int len = constraints2.length, i = 0; i < len; i++) {
168       Constraint constraint = constraints2[i];
169       String annotType = constraint.getAnnotType();
170       JdmAttribute[] constraintAttrs = constraint.getAttributeArray();
171       MutableBoolean moreToTry = new MutableBoolean();
172 
173       if(DEBUG) {
174         Out.println(
175           "BPE.matches: selectAnn on lFP = " + lastFailurePoint +
176           "; max(pos,lfp) = " + Math.max(position, lastFailurePoint) +
177           "; annotType = " + annotType + "; attrs = " +
178           constraintAttrs.toString() + Strings.getNl()
179         );
180         for(int j=0; j<constraintAttrs.length; j++)
181           Out.println(
182             "BPE.matches attr: " + constraintAttrs[j].toString()
183           );
184       }
185       FeatureMap features = Factory.newFeatureMap();
186       for(int j = constraintAttrs.length - 1; j >= 0; j--)
187         features.put(constraintAttrs[j].getName(), constraintAttrs[j].getValue());
188       AnnotationSet match = doc.getAnnotations().get(
189         // this loses "April 2" on the frozen tests:
190         // Math.max(nextAvailable.value, Math.max(position, lastFailurePoint)),
191         annotType,
192         features,
193         new Long(Math.max(position, lastFailurePoint))  /*,
194         nextAvailable,
195         moreToTry */
196       );
197       if(DEBUG) Out.println(
198         "BPE.matches: selectAnn returned " + match + ".... moreToTry = " +
199         moreToTry.value + "    nextAvailable = " + nextAvailable.value
200       );
201 
202       // store first constraint's next available
203       if(nextAvailOfFirstConstraint == -1)
204         nextAvailOfFirstConstraint = nextAvailable.value;
205 
206       // if there are no more annotations of this type, then we can
207       // say that we failed this BPE and that we tried the whole document
208       if(! moreToTry.value) {
209         if(match != null)
210           throw(new RuntimeException("BPE: no more annots but found one!"));
211         lastFailurePoint = end;
212         newPosition.value = end;
213       }
214 
215       // selectNextAnnotation ensures that annotations matched will
216       // all start >= position. we also need to ensure that second and
217       // subsequent matches start <= to the rightmost end. otherwise
218       // BPEs can match non-contiguous annotations, which is not the
219       // intent. so we record the rightmostEnd, and reject annotations
220       // whose leftmostStart is > this value.
221       int matchEnd = -1;
222       if(match != null) {
223         matchEnd = match.lastNode().getOffset().intValue();
224         if(rightmostEnd == -1) { // first time through
225           rightmostEnd = matchEnd;
226         }
227         else if(match.firstNode().getOffset().intValue() >= rightmostEnd) {
228           // reject
229           lastFailurePoint = matchEnd;
230           match = null;
231         }
232         else { // this one is ok; reset rightmostEnd
233           if(rightmostEnd < matchEnd)
234             rightmostEnd = matchEnd;
235         }
236       } // match != null
237 
238       // negation
239       if(constraint.isNegated()) {
240         if(match == null) {
241           //Debug.pr(
242           //  this, "BPE.matches: negating failed constraint" + Debug.getNl()
243           //);
244           continue;
245         }
246         else {
247           // Debug.pr(
248           //  this, "BPE.matches: negating successful constraint, match = " +
249           //  match.toString() + Debug.getNl()
250           //);
251           lastFailurePoint = matchEnd;
252           match = null;
253         }
254       } // constraint is negated
255 
256       if(match == null) { // clean up
257         //Debug.pr(this, "BPE.matches: selectNextAnnotation returned null");
258         //Debug.nl(this);
259 
260         newPosition.value = Math.max(position + 1, nextAvailOfFirstConstraint);
261         lastFailurePoint = nextAvailable.value;
262 
263         // we clear cached annots added this time, not all: maybe we were
264         // applied under *, for example, and failure doesn't mean we should
265         // purge the whole cache
266         //for(int j = matchedAnnots.size() - 1; j >= startingCacheSize; j--)
267         //  matchedAnnots.removeNth(j);
268         matchedAnnots.removeAll(addedAnnots);
269 
270         //Debug.pr(
271         //  this, "BPE.matches: false, newPosition.value(" +
272         //  newPosition.value + ")" + Debug.getNl()
273         //);
274         return false;
275       } else {
276 
277         //Debug.pr(this,"BPE.matches: match= "+match.toString()+Debug.getNl());
278         matchedAnnots.addAll(match);
279         addedAnnots.addAll(match);
280         newPosition.value = Math.max(newPosition.value, matchEnd);
281       }
282 
283     } // for each constraint
284 
285     // success: store the annots added this time
286     matchHistory.push(addedAnnots);
287 
288     //Debug.pr(this, "BPE.matches: returning true" + Debug.getNl());
289     // under negation we may not have advanced...
290     if(newPosition.value == position)
291       newPosition.value++;
292 
293     return true;
294   } // matches
295 
296   /** Create a string representation of the object. */
297   public String toString() {
298     StringBuffer result = new StringBuffer("{");
299     Constraint[] constraints = getConstraints();
300     for(int i = 0; i<constraints.length; i++){
301       result.append(constraints[i].shortDesc() + ",");
302     }
303     result.setCharAt(result.length() -1, '}');
304     return result.toString();
305   }
306 
307   /** Create a string representation of the object. */
308   public String toString(String pad) {
309     String newline = Strings.getNl();
310     String newPad = Strings.addPadding(pad, INDENT_PADDING);
311 
312     StringBuffer buf = new StringBuffer(pad +
313       "BPE: lastFailurePoint(" + lastFailurePoint + "); constraints("
314     );
315 
316     // constraints
317     if(constraints1 != null) {
318       for(int len = constraints1.size(), i = 0; i < len; i++)
319         buf.append(
320           newline + ((Constraint) constraints1.get(i)).toString(newPad)
321         );
322     } else {
323       for(int len = constraints2.length, i = 0; i < len; i++)
324         buf.append(newline + constraints2[i].toString(newPad));
325     }
326 
327     // matched annots
328     buf.append(
329       newline + pad + "matchedAnnots: " + matchedAnnots +
330       newline + pad + ") BPE."
331     );
332 
333     return buf.toString();
334   } // toString
335 
336   /**
337     * Returns a short description.
338     */
339   public String shortDesc() {
340     String res = "";
341     if(constraints1 != null) {
342       for(int len = constraints1.size(), i = 0; i < len; i++)
343         res += ((Constraint) constraints1.get(i)).toString();
344     } else {
345       for(int len = constraints2.length, i = 0; i < len; i++)
346         res += constraints2[i].shortDesc();
347     }
348     return res;
349   }
350 
351   public Constraint[] getConstraints(){
352     return constraints2;
353   }
354 } // class BasicPatternElement
355 
356