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