/* * SinglePhaseTransducer.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 from gate.jape to ca.umontreal.iro.rali.gate.jape * - When testing for equality between a constraint and a path (an annotation), * check whether the constraint is negated. If yes, succeed if annotation * type is NOT equal to constraint annotation type. Also compare attributes * when the negated constraint has ones. * - When testing for equality between the attributes and values, call a * "subsume" method that is aware that the comparison operator for each * attribute may be different from equality. * - Allows constraints on different types of annotation * * $Id$ */ package ca.umontreal.iro.rali.gate.jape; import java.io.*; import gate.annotation.*; import gate.util.*; import gate.*; import gate.gui.*; import gate.creole.*; import gate.event.*; import java.util.*; import ca.umontreal.iro.rali.gate.fsm.*; /** * Represents a complete CPSL grammar, with a phase name, options and * rule set (accessible by name and by sequence). * Implements a transduce method taking a Document as input. * Constructs from String or File. */ public class SinglePhaseTransducer extends Transducer implements JapeConstants, java.io.Serializable { /** Debug flag */ private static final boolean DEBUG = false; /** Construction from name. */ public SinglePhaseTransducer(String name) { this.name = name; rules = new PrioritisedRuleList(); finishedAlready = false; } // Construction from name /** Type of rule application (constants defined in JapeConstants). */ private int ruleApplicationStyle = BRILL_STYLE; /** Set the type of rule application (types defined in JapeConstants). */ public void setRuleApplicationStyle(int style) { ruleApplicationStyle = style; } /** The list of rules in this transducer. Ordered by priority and * addition sequence (which will be file position if they come from * a file). */ private PrioritisedRuleList rules; FSM fsm; public FSM getFSM(){ return fsm; } /** Add a rule. */ public void addRule(Rule rule) { rules.add(rule); } // addRule /** The values of any option settings given. */ private java.util.HashMap optionSettings = new java.util.HashMap(); /** Add an option setting. If this option is set already, the new * value overwrites the previous one. */ public void setOption(String name, String setting) { optionSettings.put(name, setting); } // setOption /** Get the value for a particular option. */ public String getOption(String name) { return (String) optionSettings.get(name); } // getOption /** Whether the finish method has been called or not. */ private boolean finishedAlready; /** Finish: replace dynamic data structures with Java arrays; called * after parsing. */ public void finish(){ // both MPT and SPT have finish called on them by the parser... if(finishedAlready) return; else finishedAlready = true; //each rule has a RHS which has a string for java code //those strings need to be compiled now Map actionClasses = new HashMap(rules.size()); for(Iterator i = rules.iterator(); i.hasNext(); ){ Rule rule = (Rule)i.next(); rule.finish(); actionClasses.put(rule.getRHS().getActionClassName(), rule.getRHS().getActionClassString()); } try{ Javac.loadClasses(actionClasses); }catch(Exception e){ Err.prln("Compile error:\n" + e.getMessage()); //e.printStackTrace(); } //build the finite state machine transition graph fsm = new FSM(this); //clear the old style data structures rules.clear(); rules = null; } // finish private void addAnnotationsByOffset(SimpleSortedSet keys, Set annotations){ Iterator annIter = annotations.iterator(); while(annIter.hasNext()){ Annotation ann = (Annotation)annIter.next(); //ignore empty annotations long offset = ann.getStartNode().getOffset().longValue(); if(offset == ann.getEndNode().getOffset().longValue()) continue; //dam: was /* // Long offset = ann.getStartNode().getOffset(); List annsAtThisOffset = null; if(keys.add(offset)){ annsAtThisOffset = new LinkedList(); map.put(offset, annsAtThisOffset); }else{ annsAtThisOffset = (List)map.get(offset); } annsAtThisOffset.add(ann); */ //dam: end keys.add(offset, ann); } }//private void addAnnotationsByOffset() /** * Transduce a document using the annotation set provided and the current * rule application style. */ public void transduce(Document doc, AnnotationSet inputAS, AnnotationSet outputAS) throws JapeException, ExecutionException { interrupted = false; fireProgressChanged(0); //the input annotations will be read from this map //maps offset to list of annotations //dam was /* Map annotationsByOffset = new HashMap(); SortedSet offsets = new TreeSet(); */ //dam: now SimpleSortedSet offsets = new SimpleSortedSet(); SimpleSortedSet annotationsByOffset = offsets; //dam: end //select only the annotations of types specified in the input list // Out.println("Input:" + input); if(input.isEmpty()) { //dam: was // addAnnotationsByOffset(annotationsByOffset, offsets, inputAS); //dam: now addAnnotationsByOffset(offsets, inputAS); //dam: end } else { Iterator typesIter = input.iterator(); AnnotationSet ofOneType = null; while(typesIter.hasNext()){ ofOneType = inputAS.get((String)typesIter.next()); if(ofOneType != null){ //dam: was // addAnnotationsByOffset(annotationsByOffset, offsets, ofOneType); //dam: now addAnnotationsByOffset(offsets, ofOneType); //dam: end } } } if(annotationsByOffset.isEmpty()){ fireProcessFinished(); return; } annotationsByOffset.sort(); //define data structures //FSM instances that haven't blocked yet java.util.ArrayList activeFSMInstances = new java.util.ArrayList(); // FSM instances that have reached a final state // This set will be sorted later by the length // of the document content covered by the matched annotations java.util.ArrayList acceptingFSMInstances = new ArrayList(); FSMInstance currentFSM; //find the first node of the document Node startNode = ((Annotation) ((ArrayList)annotationsByOffset. get(offsets.first())).get(0)). getStartNode(); //used to calculate the percentage of processing done long lastNodeOff = doc.getContent().size().longValue(); //the offset of the node where the matching currently starts //the value -1 marks no more annotations to parse long startNodeOff = startNode.getOffset().longValue(); //used to decide when to fire progress events long oldStartNodeOff = 0; //the big while for the actual parsing while(startNodeOff != -1){ //Out.prln(); //Out.pr("Start: " + startNodeOff); //while there are more annotations to parse //create initial active FSM instance starting parsing from new startNode //currentFSM = FSMInstance.getNewInstance( currentFSM = new FSMInstance( fsm, fsm.getInitialState(),//fresh start startNode,//the matching starts form the current startNode startNode,//current position in AG is the start position new java.util.HashMap(),//no bindings yet! doc ); // at this point ActiveFSMInstances should always be empty! activeFSMInstances.clear(); acceptingFSMInstances.clear(); //dam: was used LinkedList // activeFSMInstances.addLast(currentFSM); //dam: now used ArrayList activeFSMInstances.add(currentFSM); //dam: end //far each active FSM Instance, try to advance whileloop2: while(!activeFSMInstances.isEmpty()){ if(interrupted) throw new ExecutionInterruptedException( "The execution of the \"" + getName() + "\" Jape transducer has been abruptly interrupted!"); //Out.pr(" <" + acceptingFSMInstances.size() + "/" + // activeFSMInstances.size() +">"); // take the first active FSM instance currentFSM = (FSMInstance)activeFSMInstances.remove(0); // process the current FSM instance if(currentFSM.getFSMPosition().isFinal()){ //the current FSM is in a final state //dam: was LinkedList // acceptingFSMInstances.addLast(currentFSM.clone()); //dam: now acceptingFSMInstances.add(currentFSM.clone()); //dam: end // //if we are in APPELT mode clear all the accepting instances // //apart from the longest one // if(ruleApplicationStyle == APPELT_STYLE && // acceptingFSMInstances.size() > 1){ // Object longestAcceptor = acceptingFSMInstances.last(); // acceptingFSMInstances.clear(); // acceptingFSMInstances.add(longestAcceptor); // } //if we're only looking for the shortest stop here if(ruleApplicationStyle == FIRST_STYLE) break whileloop2; } // get all the annotations that start where the current FSM finishes SimpleSortedSet offsetsTailSet = offsets.tailSet( currentFSM.getAGPosition().getOffset().longValue()); ArrayList paths; long theFirst = offsetsTailSet.first(); if(theFirst <0) continue; paths = (ArrayList)annotationsByOffset.get(theFirst); //System.out.println("Paths: " + paths + "\n^localInputIndex: " + localInputIndex); if(paths.isEmpty()) continue; // get the transitions for the current state of the FSM State currentState = currentFSM.getFSMPosition(); Iterator transitionsIter = currentState.getTransitions().iterator(); // for each transition, keep the set of annotations starting at current // node (the "paths") that match each constraint of the transition transitionsWhile: while(transitionsIter.hasNext()){ Transition currentTransition = (Transition)transitionsIter.next(); Constraint[] currentConstraints = currentTransition.getConstraints().getConstraints(); // separate negated from "positive" constraints ArrayList negatedConstraints = new ArrayList(3); ArrayList positiveConstraints = new ArrayList(3); for (int i=0; i<currentConstraints.length; i++) { if (currentConstraints[i].isNegated()) negatedConstraints.add(currentConstraints[i]); else positiveConstraints.add(currentConstraints[i]); } // First test against negative constraints; skip this transition // as soon as there is an unwanted match for (Iterator pathsIter=paths.iterator(); pathsIter.hasNext(); ) { Annotation onePath = (Annotation)pathsIter.next(); for (int i=0, size=negatedConstraints.size(); i<size; i++) { Constraint theConstraint = (Constraint) negatedConstraints.get(i); if (theConstraint.getAnnotType().equals(onePath.getType())) // if type is the same, also check attributes if (theConstraint.subsumesOne((SimpleFeatureMapImpl) onePath.getFeatures())) { continue transitionsWhile; } } } // Then test against positive constraints and keep track of the // annots that match each constraint. // First create empty lists for each constraint... ArrayList matchingAnnot = new ArrayList(positiveConstraints.size()); for (int i=0, size=positiveConstraints.size(); i<size; i++) { matchingAnnot.add(new LinkedList()); } // for each annot. at the current node in the annot graph... for (Iterator pathsIter=paths.iterator(); pathsIter.hasNext(); ) { Annotation onePath = (Annotation)pathsIter.next(); // for each positive constraint... for (int i=0, size=positiveConstraints.size(); i<size; i++) { Constraint theConstraint = (Constraint) positiveConstraints.get(i); if (theConstraint.getAnnotType().equals(onePath.getType())) { // if type is the same, also check attributes if (theConstraint.subsumes((SimpleFeatureMapImpl) onePath.getFeatures())) { ((LinkedList) matchingAnnot.get(i)).add(onePath); } } } // for each positive constraint } // for each annot. // If there are only negative constraints and no positive constraints, // let the first list of matchingAnnot contain all annots at this node if (negatedConstraints.size() != 0 && matchingAnnot.size() == 0) { matchingAnnot.add(new LinkedList(paths)); } // We have a match if every constraint is met by at least one annot. // Given the sets Sx of the annotations that match constraint x, // compute all tuples (A1, A2, ..., An) where Ax comes from the // set Sx and n is the number of constraints LinkedList combinations = combine( matchingAnnot, matchingAnnot.size(), new LinkedList()); // Create a new FSM for every tuple of annot for (ListIterator tuplesIter = combinations.listIterator(); tuplesIter.hasNext(); ) { LinkedList tuple = (LinkedList) tuplesIter.next(); //we have a match //System.out.println("Match!"); //create a new FSMInstance, advance it over the current annotation //take care of the bindings and add it to ActiveFSM FSMInstance newFSMI = (FSMInstance)currentFSM.clone(); // Find rightmost node offset Node endNode = null; for (ListIterator annotsInTupleIter = tuple.listIterator(); annotsInTupleIter.hasNext(); ) { Annotation annot = (Annotation) annotsInTupleIter.next(); if (endNode == null) { // first loop: initialise endNode = annot.getEndNode(); } else { // check for a rightmost offset if (annot.getEndNode().getOffset().compareTo(endNode.getOffset()) > 0) { endNode = annot.getEndNode(); } } // else } // ListIterator annotsInTupleIter newFSMI.setAGPosition(endNode); newFSMI.setFSMPosition(currentTransition.getTarget()); //bindings java.util.Map binds = newFSMI.getBindings(); java.util.Iterator labelsIter = currentTransition.getBindings().iterator(); String oneLabel; AnnotationSet boundAnnots, newSet; while(labelsIter.hasNext()){ oneLabel = (String)labelsIter.next(); boundAnnots = (AnnotationSet)binds.get(oneLabel); if(boundAnnots != null) newSet = new AnnotationSetImpl((AnnotationSet)boundAnnots); else newSet = new AnnotationSetImpl(doc); // newSet.addAll(tuple); Ok with Gate 2.1, but buggy with 2.2!!! // So call newSet.add one at a time so that annots keep their IDs for (ListIterator annotsInTupleIter = tuple.listIterator(); annotsInTupleIter.hasNext(); ) { newSet.add((Annotation) annotsInTupleIter.next()); } binds.put(oneLabel, newSet); }//while(labelsIter.hasNext()) activeFSMInstances.add(newFSMI); //Out.pr("^(" + newFSMI.getStartAGPosition().getOffset() + // "->" + newFSMI.getAGPosition().getOffset() + ")"); } //while combinationsIter.hasNext() }//while(transitionsIter.hasNext()) }//while(!activeFSMInstances.isEmpty()) //FIRE THE RULE //dam: use long // Long lastAGPosition = null; //dam: now long lastAGPosition = -1; //dam: end if(acceptingFSMInstances.isEmpty()){ //no rule to fire, advance to the next input offset lastAGPosition = startNodeOff + 1; } else if(ruleApplicationStyle == BRILL_STYLE) { //System.out.println("Brill acceptor"); // fire the rules corresponding to all accepting FSM instances java.util.Iterator accFSMs = acceptingFSMInstances.iterator(); FSMInstance currentAcceptor; RightHandSide currentRHS; lastAGPosition = startNode.getOffset().longValue(); while(accFSMs.hasNext()){ currentAcceptor = (FSMInstance) accFSMs.next(); currentRHS = currentAcceptor.getFSMPosition().getAction(); currentRHS.transduce(doc, currentAcceptor.getBindings(), inputAS, outputAS, ontology); //dam: use long // Long currentAGPosition = currentAcceptor.getAGPosition().getOffset(); //dam: now long currentAGPosition = currentAcceptor.getAGPosition().getOffset().longValue(); //dam: end if(currentAGPosition > lastAGPosition) lastAGPosition = currentAGPosition; } } else if(ruleApplicationStyle == APPELT_STYLE || ruleApplicationStyle == FIRST_STYLE || ruleApplicationStyle == ONCE_STYLE) { //System.out.println("Appelt acceptor"); // We want the rule that matches the longest document region, so use // a reverse comparator based on the length to the region and rule // priority (implemented in the compareTo method of FSMInstance). // However, the FSM that are equal (same length and priority) must // stay in the same order, because the transitions were added in a // a specific order by FSM.convertComplexPE to implement the greediness // of "*" and "+" Kleene operators (loop transitions must be explored // before the others) Collections.sort(acceptingFSMInstances, Collections.reverseOrder()); FSMInstance currentAcceptor =(FSMInstance)acceptingFSMInstances.get(0); if(isDebugMode()){ //see if we have any conflicts Iterator accIter = acceptingFSMInstances.iterator(); FSMInstance anAcceptor; List conflicts = new ArrayList(); while(accIter.hasNext()){ anAcceptor = (FSMInstance)accIter.next(); if(anAcceptor.equals(currentAcceptor)){ conflicts.add(anAcceptor); }else{ break; } } if(conflicts.size() > 1){ Out.prln("\nConflicts found during matching:" + "\n================================"); accIter = conflicts.iterator(); int i = 0; while(accIter.hasNext()){ Out.prln(i++ + ") " + accIter.next().toString()); } } } RightHandSide currentRHS = currentAcceptor.getFSMPosition().getAction(); currentRHS.transduce(doc, currentAcceptor.getBindings(), inputAS, outputAS, ontology); //if in ONCE mode stop after first match if(ruleApplicationStyle == ONCE_STYLE) return; //advance in AG lastAGPosition = currentAcceptor.getAGPosition().getOffset().longValue(); } // else if(ruleApplicationStyle == FIRST_STYLE) { // // AcceptingFSMInstances is an ordered structure: // // just execute the shortest (first) rule // // FSMInstance currentAcceptor =(FSMInstance)acceptingFSMInstances.first(); // RightHandSide currentRHS = currentAcceptor.getFSMPosition().getAction(); // currentRHS.transduce(doc, outputAS, currentAcceptor.getBindings()); // //advance in AG // long lastAGPosition = currentAcceptor.getAGPosition(). // getOffset().longValue(); // //advance the index on input // while(inputIndex < annotations.size() && // ((Annotation)annotations.get(inputIndex)). // getStartNode().getOffset().longValue() < lastAGPosition){ // inputIndex++; // } // } else throw new RuntimeException("Unknown rule application style!"); //advance on input // SortedSet OffsetsTailSet = offsets.tailSet(lastAGPosition); SimpleSortedSet OffsetsTailSet = offsets.tailSet(lastAGPosition); //<<< DAM: isEmpty speedup /* if(OffsetsTailSet.isEmpty()){ */ //=== DAM: now long theFirst = OffsetsTailSet.first(); if( theFirst < 0){ //>>> DAM: end //no more input, phew! :) startNodeOff = -1; fireProcessFinished(); }else{ //<<< DAM: use long /* Long nextKey = (Long)OffsetsTailSet.first(); */ //=== DAM: now long nextKey = theFirst; //>>> DAM: end startNode = ((Annotation) ((ArrayList)annotationsByOffset.get(nextKey)).get(0)). //nextKey getStartNode(); startNodeOff = startNode.getOffset().longValue(); //eliminate the possibility for infinite looping if(oldStartNodeOff == startNodeOff){ //Out.prln(""); //Out.pr("SKIP " + startNodeOff); //we are about to step twice in the same place, ...skip ahead lastAGPosition = startNodeOff + 1; OffsetsTailSet = offsets.tailSet(lastAGPosition); //<<< DAM: isEmpty speedup /* if(OffsetsTailSet.isEmpty()){ */ //=== DAM: now theFirst = OffsetsTailSet.first(); if(theFirst < 0){ //>>> DAM: end //no more input, phew! :) startNodeOff = -1; fireProcessFinished(); }else{ //<<< DAM: use long // nextKey = (Long)OffsetsTailSet.first(); //=== DAM: now nextKey = theFirst; //>>> DAM: end startNode = ((Annotation) ((List)annotationsByOffset.get(theFirst)).get(0)). getStartNode(); startNodeOff =startNode.getOffset().longValue(); } //Out.prln(" ->" + startNodeOff); }//if(oldStartNodeOff == startNodeOff) //fire the progress event if(startNodeOff - oldStartNodeOff > 256){ if(isInterrupted()) throw new ExecutionInterruptedException( "The execution of the \"" + getName() + "\" Jape transducer has been abruptly interrupted!"); fireProgressChanged((int)(100 * startNodeOff / lastNodeOff)); oldStartNodeOff = startNodeOff; } } }//while(startNodeOff != -1) fireProcessFinished(); } // transduce /** Clean up (delete action class files, for e.g.). */ public void cleanUp() { // for(DListIterator i = rules.begin(); ! i.atEnd(); i.advance()) // ((Rule) i.get()).cleanUp(); } // cleanUp /** A string representation of this object. */ public String toString() { return toString(""); } // toString() /** A string representation of this object. */ public String toString(String pad) { String newline = Strings.getNl(); String newPad = Strings.addPadding(pad, INDENT_PADDING); StringBuffer buf = new StringBuffer(pad + "SPT: name(" + name + "); ruleApplicationStyle("); switch(ruleApplicationStyle) { case APPELT_STYLE: buf.append("APPELT_STYLE); "); break; case BRILL_STYLE: buf.append("BRILL_STYLE); "); break; default: break; } buf.append("rules(" + newline); Iterator rulesIterator = rules.iterator(); while(rulesIterator.hasNext()) buf.append(((Rule) rulesIterator.next()).toString(newPad) + " "); buf.append(newline + pad + ")." + newline); return buf.toString(); } // toString(pad) //needed by fsm public PrioritisedRuleList getRules() { return rules; } /** * Adds a new type of input annotations used by this transducer. * If the list of input types is empty this transducer will parse all the * annotations in the document otherwise the types not found in the input * list will be completely ignored! To be used with caution! */ public void addInput(String ident) { input.add(ident); } public synchronized void removeProgressListener(ProgressListener l) { if (progressListeners != null && progressListeners.contains(l)) { Vector v = (Vector) progressListeners.clone(); v.removeElement(l); progressListeners = v; } } public synchronized void addProgressListener(ProgressListener l) { Vector v = progressListeners == null ? new Vector(2) : (Vector) progressListeners.clone(); if (!v.contains(l)) { v.addElement(l); progressListeners = v; } } /** * Defines the types of input annotations that this transducer reads. If this * set is empty the transducer will read all the annotations otherwise it * will only "see" the annotations of types found in this list ignoring all * other types of annotations. */ java.util.Set input = new java.util.HashSet(); private transient Vector progressListeners; protected void fireProgressChanged(int e) { if (progressListeners != null) { Vector listeners = progressListeners; int count = listeners.size(); for (int i = 0; i < count; i++) { ((ProgressListener) listeners.elementAt(i)).progressChanged(e); } } } protected void fireProcessFinished() { if (progressListeners != null) { Vector listeners = progressListeners; int count = listeners.size(); for (int i = 0; i < count; i++) { ((ProgressListener) listeners.elementAt(i)).processFinished(); } } } public int getRuleApplicationStyle() { return ruleApplicationStyle; } /* private void writeObject(ObjectOutputStream oos) throws IOException { Out.prln("writing spt"); oos.defaultWriteObject(); Out.prln("finished writing spt"); } // writeObject */ /** Computes all tuples (x1, x2, ..., xn) resulting from the linear combination of the elements of n lists, where x1 comes from the 1st list, x2 comes from the second, etc. This method works recursively. The first call should have those parameters: @param sourceLists an array of n lists whose elements will be combined @param maxTupleSize the number of elements per tuple @param incompleteTuple an empty list */ private static LinkedList combine(ArrayList sourceLists, int maxTupleSize, LinkedList incompleteTuple) { LinkedList newTupleList = new LinkedList(); if (incompleteTuple.size() == maxTupleSize) { newTupleList.add(incompleteTuple); } else { LinkedList currentSourceList = (LinkedList) sourceLists.get(incompleteTuple.size()); // use for loop instead of ListIterator to increase speed (critical here) for (int i=0; i<currentSourceList.size(); i++) { LinkedList augmentedTuple = (LinkedList) incompleteTuple.clone(); augmentedTuple.add(currentSourceList.get(i)); newTupleList.addAll(combine(sourceLists, maxTupleSize, augmentedTuple)); } } return newTupleList; } } // class SinglePhaseTransducer /* class SimpleSortedSet { static final int INCREMENT = 1023; int[] theArray = new int[INCREMENT]; Object[] theObject = new Object[INCREMENT]; int tsindex = 0; int size = 0; public static int avesize = 0; public static int maxsize = 0; public static int avecount = 0; public SimpleSortedSet() { avecount++; java.util.Arrays.fill(theArray, Integer.MAX_VALUE); } public Object get(int elValue) { int index = java.util.Arrays.binarySearch(theArray, elValue); if (index >=0) return theObject[index]; return null; } public boolean add(int elValue, Object o) { int index = java.util.Arrays.binarySearch(theArray, elValue); if (index >=0) { ((ArrayList)theObject[index]).add(o); return false; } if (size == theArray.length) { int[] temp = new int[theArray.length + INCREMENT]; Object[] tempO = new Object[theArray.length + INCREMENT]; System.arraycopy(theArray, 0, temp, 0, theArray.length); System.arraycopy(theObject, 0, tempO, 0, theArray.length); java.util.Arrays.fill(temp, theArray.length, temp.length , Integer.MAX_VALUE); theArray = temp; theObject = tempO; } index = ~index; System.arraycopy(theArray, index, theArray, index+1, size - index ); System.arraycopy(theObject, index, theObject, index+1, size - index ); theArray[index] = elValue; theObject[index] = new ArrayList(); ((ArrayList)theObject[index]).add(o); size++; return true; } public int first() { if (tsindex >= size) return -1; return theArray[tsindex]; } public Object getFirst() { if (tsindex >= size) return null; return theObject[tsindex]; } public SimpleSortedSet tailSet(int elValue) { if (tsindex < theArray.length && elValue != theArray[tsindex]) { if (tsindex<(size-1) && elValue > theArray[tsindex] && elValue <= theArray[tsindex+1]) { tsindex++; return this; } int index = java.util.Arrays.binarySearch(theArray, elValue); if (index < 0) index = ~index; tsindex = index; } return this; } public boolean isEmpty() { return size ==0; } }; */