blob: 06f4a5676196a5b0eedf0b47fc05935d1eeb8192 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.uima.cas.impl;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.NoSuchElementException;
import java.util.function.Predicate;
import org.apache.uima.cas.CASRuntimeException;
import org.apache.uima.cas.FSIterator;
import org.apache.uima.cas.FeatureStructure;
import org.apache.uima.cas.Type;
import org.apache.uima.cas.text.AnnotationFS;
import org.apache.uima.internal.util.Misc;
import org.apache.uima.jcas.cas.TOP;
import org.apache.uima.jcas.impl.JCasImpl;
import org.apache.uima.jcas.tcas.Annotation;
/**
* Subiterator implementation.
*
* There are two bounding styles and 2 underlying forms.
*
* The 2nd form is produced lazily when needed, and
* is made by a one-time forward traversal to compute unambiguous subsets and store them into a list.
* - The 2nd form is needed only for unambiguous style if backwards or moveto(fs) operation.
*
* The 1st form uses the underlying iterator directly, and does skipping as needed, while iterating
* - going forward:
* skip if unambiguous and start is within prev span
* skip if strict and end lies outside of scope span
*
* - going backward:
* if unambiguous - convert to form 2
* skip if strict and end lies outside of scope span
*
* - going to particular fs (left most match)
* if unambiguous - convert to form 2
* skip (forward) if strict and end lies outside of scope span
*
* - going to first:
* unambiguous - no testing needed, no prior span
* skip if strict and end lies outside of scope span
*
* - going to last:
* unambiguous - convert to 2nd form
* skip backwards if strict and end lies outside of scope span
*
* There are two styles of the bounding information.
*
* - the traditional one uses the standard comparator for annotations: begin (ascending), end (descending) and
* type priority ordering
* - the 2nd style uses just a begin value and an end value, no type priority ordering.
*
* Interaction with copy-on-write concurrent modification avoidance
* As with other iterators, the moveToFirst/Last/feature-structure-position "resets" the underlying
* iterators to match their current indexes.
* This implementation maintains local data: the list form and the isEmpty flag. These would
* also need recomputing for the above operations, if isIndexesHaveBeenUpdated() is true.
*
*/
public class Subiterator<T extends AnnotationFS> implements LowLevelIterator<T> {
public static enum BoundsUse {
coveredBy,
covering,
sameBeginEnd,
notBounded,
}
/*
* The generic type of this is Annotation, not T, because that allows the use of a comparator which is comparing
* a key which is not T but some super type of T.
*/
private ArrayList<Annotation> list = null; // used for form 2, lazily initialized
private int pos = 0; // used for form 2
private final FSIterator<Annotation> it;
private final Annotation boundingAnnot; // the bounding annotation need not be a subtype of T
private final Annotation coveringStartPos;
private final Annotation coveringEndPos; // an approx end position
private final boolean isAmbiguous; // true means ordinary, false means to skip until start is past prev end
private final boolean isStrict;
private final boolean isBounded;
private final boolean isTypePriority;
private final boolean isPositionUsesType;
private final boolean isSkipEquals;
private final BoundsUse boundsUse;
/**
* isEmpty is an expensive calculation, involving potentially traversing all the iterators in a particular type hierarchy
* It is only done:
* - at init time
* - at moveToFirst/last/FS - these can cause regenerating the copy-on-write objects via the getNonNullCow call
* if no new nonNullCow was obtained, no need to recalculate empty
*/
private boolean isEmpty;
private int prevEnd = 0; // for unambiguous iterators
/**
* list form is recalculated at moveToFirst/last/fs, same as isEmpty
*/
private boolean isListForm = false;
/**
* startId is recalculated at moveToFirst/last/fs, same as isEmpty
*/
private int startId;
private final int boundBegin;
private final int boundEnd;
private final TypeImpl boundType;
private final Comparator<TOP> annotationComparator; // implements compare(fs1, fs2) for begin/end/type priority
private final JCasImpl jcas;
/**
* Caller is the implementation of AnnotationIndex, FSIndex_annotation.
*
* A normal iterator is passed in, already positioned to where things should start.
*
* A bounding FS is passed in (except for unbounded unambiguous iteration.
*
* @param it the iterator to use, positioned to the correct starting place
* @param boundingAnnot null or the bounding annotation
* @param ambiguous false means to skip annotations whose begin lies between previously returned begin (inclusive) and end (exclusive)
* @param strict true means to skip annotations whose end is greater than the bounding end position (ignoring type priorities)
* @param isBounded false means it's an unambiguous iterator with no bounds narrowing
* @param isTypePriority false to ignore type priorities, and just use begin/end and maybe type
* @param isPositionUsesType only used if isTypePriority is false, and says to still compare types for eq
* @param isSkipEquals used only for bounded case,
* false means to only skip returning an FS if it has the same id() as the bounding fs
* @param annotationComparator indexRepository.getAnnotationComparator() value
*/
Subiterator(
FSIterator<T> it,
AnnotationFS boundingAnnot,
boolean ambiguous,
boolean strict, // omit FSs whose end > bounds
BoundsUse boundsUse, // null if no bounds; boundingAnnot used for start position if non-null
boolean isTypePriority,
boolean isPositionUsesType,
boolean isSkipEquals,
Comparator<TOP> annotationComparator
) {
this.it = (FSIterator<Annotation>) it;
this.boundingAnnot = (Annotation) boundingAnnot;
this.isBounded = boundsUse != null && boundsUse != BoundsUse.notBounded;
this.boundsUse = (boundsUse == null) ? BoundsUse.notBounded : boundsUse;
this.isAmbiguous = ambiguous;
this.isStrict = strict;
this.isPositionUsesType = isPositionUsesType;
this.isSkipEquals = isSkipEquals;
if (isBounded && (null == boundingAnnot || !(boundingAnnot instanceof Annotation))) {
Misc.internalError(new IllegalArgumentException("Bounded Subiterators require a bounding annotation"));
}
this.boundBegin = isBounded ? boundingAnnot.getBegin() : -1;
this.boundEnd = isBounded ? boundingAnnot.getEnd(): -1;
this.boundType = isBounded ? (TypeImpl) boundingAnnot.getType() : null;
this.isTypePriority = (boundsUse == BoundsUse.covering) ? false : isTypePriority;
this.annotationComparator = annotationComparator;
this.jcas = (JCasImpl) ll_getIndex().getCasImpl().getJCas();
if (boundsUse == BoundsUse.covering) {
// compute start position and isEmpty setting
int span = ((LowLevelIterator<?>)it).ll_maxAnnotSpan(); // an optimization, the largest end-begin annotation
int begin = boundEnd - span;
if (begin > boundBegin) {
makeInvalid();
coveringStartPos = coveringEndPos = null;
isEmpty = true;
startId = 0;
return;
}
if (begin < 0) {
begin = 0;
}
coveringStartPos = new Annotation(jcas, begin, Integer.MAX_VALUE);
coveringEndPos = new Annotation(jcas, boundBegin + 1, boundBegin + 1);
} else {
coveringStartPos = coveringEndPos = null;
}
moveToStartSetEmptyAndId();
}
private void moveToStartSetEmptyAndId() {
moveToStart();
isEmpty = !isValid();
startId = isValid() ? get()._id() : 0;
}
/**
* copy constructor - no move to start
* @param it -
* @param boundingAnnot -
* @param ambiguous -
* @param strict -
* @param isBounded -
* @param isTypePriority -
* @param isPositionUsesType -
* @param isSkipEquals -
* @param annotationComparator -
*/
Subiterator(
FSIterator<Annotation> it,
Annotation boundingAnnot,
boolean ambiguous,
boolean strict, // omit FSs whose end > bounds
BoundsUse boundsUse, // null if boundingAnnot being used for starting position in unambiguous iterator
boolean isTypePriority,
boolean isPositionUsesType,
boolean isSkipEquals,
Comparator<TOP> annotationComparator,
int startId,
boolean isEmpty,
Annotation coveringStartPos,
Annotation coveringEndPos
) {
this.it = it;
this.boundingAnnot = boundingAnnot;
this.isBounded = boundsUse != null;
this.boundsUse = boundsUse;
this.isAmbiguous = ambiguous;
this.isStrict = strict;
this.isTypePriority = isTypePriority;
this.isPositionUsesType = isPositionUsesType;
this.isSkipEquals = isSkipEquals;
this.startId = startId;
this.boundBegin = isBounded ? boundingAnnot.getBegin() : -1;
this.boundEnd = isBounded ? boundingAnnot.getEnd(): -1;
this.boundType = isBounded ? (TypeImpl) boundingAnnot.getType() : null;
this.annotationComparator = annotationComparator;
this.isEmpty = isEmpty;
this.jcas = (JCasImpl) ll_getIndex().getCasImpl().getJCas();
this.coveringStartPos = coveringStartPos;
this.coveringEndPos = coveringEndPos;
}
/**
* Converting to list form - called for
* unambiguous iterator going backwards,
* unambiguous iterator doing a moveTo(fs) operation
* iterator doing a moveToLast() operation
*
*/
private void convertToListForm() {
moveToStart(); // moves to the start annotation, including moving past equals for annot style, and accommodating strict
this.list = new ArrayList<Annotation>();
while (isValid()) {
list.add((Annotation) it.getNvc());
moveToNext();
}
this.pos = 0;
isListForm = true; // do at end, so up to this point, iterator is not the list form style
}
private void moveToExact(Annotation targetAnnotation) {
it.moveTo(targetAnnotation); // move to left-most equal one
boolean found = adjustForTypePriorityBoundingBegin(targetAnnotation._id);
if (!found)
while (it.isValid()) { // advance to the exact equal one
if (targetAnnotation._id() == it.getNvc()._id()) {
break;
}
it.moveToNext();
}
}
/**
* Move to the starting position of the sub iterator
* isEmpty may not yet be set
*/
private void moveToStart() {
switch (boundsUse) {
case notBounded:
it.moveToFirst();
break;
case sameBeginEnd:
it.moveTo(boundingAnnot);
adjustForTypePriorityBoundingBegin(0);
skipOverBoundingAnnot(true);
maybeMakeItInvalidSameBeginEnd();
case coveredBy:
it.moveTo(boundingAnnot);
adjustForTypePriorityBoundingBegin(0);
adjustForStrictOrCoveringAndBoundSkip(true); // forward
break;
case covering:
it.moveTo(coveringStartPos); // sufficiently before the bounds
adjustForTypePriorityBoundingBegin(0);
adjustForCoveringAndBoundSkip(true); // forward
break;
}
maybeSetPrevEnd(); // used for unambiguous
}
private void adjustForStrictOrCoveringAndBoundSkip(boolean forward) {
if (boundsUse == BoundsUse.covering) {
adjustForCoveringAndBoundSkip(forward);
return;
}
// this used for both coveredBy and sameBeginEnd
adjustForStrict(forward);
if (skipOverBoundingAnnot(forward)) {
adjustForStrict(forward);
}
}
private void adjustForCoveringAndBoundSkip(boolean forward) {
adjustForCovering(forward);
if (skipOverBoundingAnnot(forward)) {
adjustForCovering(forward);
}
}
/**
* advance over FSs that are equal (2 choices) to the bounding annotation.
* May leave the iterator in invalid state.
* @param forward - true means move forward, false means move backwards
* @return true if some move happened, false if nothing happened
*/
private boolean skipOverBoundingAnnot(boolean forward) {
boolean moved = false;
if (isBounded && it.isValid()) { // skip if not bounded or iterator is invalid
while (equalToBounds(it.getNvc())) {
moved = true;
if (forward) {
it.moveToNextNvc();
} else {
maybeMoveToPrevBounded();
}
if (!it.isValid()) {
break;
}
}
}
return moved;
}
/**
* Special equalToBounds used only for having bounded iterators
* skip returning the bounding annotation
*
* Two styles: uimaFIT style: only skip the exact one (id's the same)
* uima style: skip all that compare equal using the AnnotationIndex comparator
* @param fs -
* @return true if should be skipped
*/
private boolean equalToBounds(Annotation fs) {
return fs._id == boundingAnnot._id ||
(isSkipEquals && annotationComparator.compare(fs, boundingAnnot) == 0);
}
private void maybeSetPrevEnd() {
if (!isAmbiguous && it.isValid()) {
this.prevEnd = it.getNvc().getEnd();
}
}
/**
* For strict mode, advance iterator until the end is within the bounding end
*/
private void adjustForStrict(boolean forward) {
if (isStrict && boundsUse == BoundsUse.coveredBy) {
while (it.isValid() && (it.getNvc().getEnd() > this.boundEnd)) {
if (forward) {
it.moveToNextNvc();
} else {
maybeMoveToPrevBounded();
}
}
}
}
/**
* when covering (which is different from coveredBy and sameBeginEnd,
* means get all annotations which span the bounds),
* skip items where the end < bounds end,
* because those items don't span the bounding FS
*
* Edge case: item with same begin and end is considered "covering"
* but subject to exclusion based on equalToBounds skipping
* Cases:
* position of begin is after span begin - mark "invalid"
* position of begin is before or == span begin:
* position of end is == or > span end: OK
* position of end is < span end:
* if backward: moveToPrev until get valid position or run out. if run out, mark invalid
* if forward: move to next until position of begin advances but is <= span begin.
*
* @param forward
*/
private void adjustForCovering(boolean forward) {
if (!it.isValid()) {
return;
}
// moveTo may move to invalid position
// if the cur pos item has a begin beyond the bound, it cannot be a covering annotation
if (it.getNvc().getBegin() > this.boundBegin) {
makeInvalid();
return;
}
// skip until get an FS whose end >= boundEnd, it is a candidate.
// stop if begin gets too large (going forwards)
while (it.isValid() && (it.getNvc().getEnd() < this.boundEnd)) {
if (forward) {
it.moveToNextNvc();
if (it.isValid() && it.getNvc().getBegin() > this.boundBegin) {
makeInvalid();
return;
}
} else {
maybeMoveToPrevBounded();
}
}
}
/**
* Assume: iterator is valid
*/
private void maybeMoveToPrevBounded() {
if (it.get()._id == startId) {
it.moveToFirst(); // so next is invalid
}
it.moveToPreviousNvc();
}
/**
* moves the iterator backwards if not using type priorities
* to the left most position with the same begin /end (and maybe type) as the fs at the current position.
* @param exactId 0 or the id to stop the traversal on
* @return false unless stopped on the matching exactId
*/
private boolean adjustForTypePriorityBoundingBegin(int exactId) {
if (isTypePriority || !it.isValid()) {
return false;
}
Annotation a = it.get();
int begin = a.getBegin();
int end = a.getEnd();
Type type = a.getType();
do {
int id = it.get()._id;
if (id == exactId) {
return true;
}
if (id == startId) { // start id may not be set yet; if so it is set to 0 so this test is always false
return false; // iterator is at the start, can't move more
}
it.moveToPrevious();
if (!it.isValid()) {
it.moveToFirst(); // not moveToStart - called by moveToStart
// Annotation f = it.getNvc();
// if (!isBeginEndTypeEqualToBound(it.getNvc())) {
// it.moveToPrevious(); // make invalid
// }
return false;
}
} while (isBeginEndTypeEqual(it.get(), begin, end, type));
it.moveToNext(); // went back one to far
return false;
}
// private boolean isBeginEndTypeEqualToBound(Annotation fs) {
// return fs.getBegin() == boundBegin && fs.getEnd() == boundEnd &&
// (!isPositionUsesType || fs.getType() == boundType);
// }
private boolean isBeginEndTypeEqual(Annotation fs, int begin, int end, Type type) {
return fs.getBegin() == begin && fs.getEnd() == end &&
(!isPositionUsesType || fs.getType() == type);
}
/**
* For unambiguous, going forwards
*/
private void movePastPrevAnnotation() {
if (!isAmbiguous) {
while (it.isValid() && (it.get().getBegin() < this.prevEnd)) {
it.moveToNext();
}
}
}
/*
* (non-Javadoc)
*
* @see org.apache.uima.cas.FSIterator#isValid()
*/
@Override
public boolean isValid() {
if (isEmpty) return false;
if (isListForm) {
return (this.pos >= 0) && (this.pos < this.list.size());
}
if (!it.isValid()) {
return false;
}
switch (boundsUse) {
case notBounded:
return true;
case coveredBy:
return it.get().getBegin() <= boundEnd;
case covering:
return it.isValid();
case sameBeginEnd:
Annotation a = it.get();
return a.getBegin() == boundBegin &&
a.getEnd() == boundEnd;
}
return false; // ignored, just hear to get rid of invalid compiler error report
}
/*
* (non-Javadoc)
*
* @see org.apache.uima.cas.FSIterator#get()
*/
@Override
public T get() throws NoSuchElementException {
if (isListForm) {
if ((this.pos >= 0) && (this.pos < this.list.size())) {
return (T) this.list.get(this.pos);
}
} else {
if (isValid()) {
return (T)it.get();
}
}
throw new NoSuchElementException();
}
/*
* (non-Javadoc)
*
* @see org.apache.uima.cas.FSIterator#getNvc()
*/
public T getNvc() {
if (isListForm) {
return (T) this.list.get(this.pos);
} else {
return (T)it.getNvc();
}
}
/*
* (non-Javadoc)
*
* @see org.apache.uima.cas.FSIterator#moveToNext()
*/
@Override
public void moveToNext() {
if (!isValid()) return;
moveToNextNvc();
}
@Override
public void moveToNextNvc() {
if (isListForm) {
++this.pos;
// maybeSetPrevEnd not needed because list form already accounted for unambiguous
return;
}
if (isEmpty) {
return;
}
it.moveToNextNvc();
if (!isAmbiguous) { // skip until start > prev end
movePastPrevAnnotation();
}
adjustForStrictOrCoveringAndBoundSkip(true);
// stop logic going forwards for various bounding cases
if (it.isValid()) {
// stop in bounded case if out of bounds going forwards UIMA-5063
switch(boundsUse) {
case notBounded:
break;
case coveredBy:
maybeMakeItInvalid_bounds(it.getNvc(), a -> a.getBegin() > boundEnd);
break;
case covering:
maybeMakeItInvalid_bounds(it.getNvc(), a -> a.getBegin() > boundBegin);
break;
case sameBeginEnd:
maybeMakeItInvalidSameBeginEnd();
break;
}
}
maybeSetPrevEnd();
}
private void maybeMakeItInvalidSameBeginEnd() {
maybeMakeItInvalid_bounds(it.getNvc(), a -> a.getBegin() != boundBegin || a.getEnd() != boundEnd);
}
private void maybeMakeItInvalid_bounds(Annotation a, Predicate<Annotation> outOfBounds) {
if (outOfBounds.test(a)) {
makeInvalid();
}
}
/*
* (non-Javadoc)
*
* @see org.apache.uima.cas.FSIterator#moveToPrevious()
*/
public void moveToPrevious() {
if (!isValid()) {
return;
}
moveToPreviousNvc();
}
@Override
public void moveToPreviousNvc() {
if (isListForm) {
--this.pos;
return;
}
if (isEmpty) {
return;
}
if (!isAmbiguous) {
// Convert to list form
Annotation currentAnnotation = it.get(); // save to restore position
convertToListForm();
moveToExact(currentAnnotation);
--this.pos;
return;
}
if (it.isValid()) {
maybeMoveToPrevBounded();
}
adjustForStrictOrCoveringAndBoundSkip(false); // moving backwards
// stop logic going backwards for various bounding cases
// this is done by the maybeMoveToPrev call, where it compares to the saved startId
// if (it.isValid()) {
// // stop in bounded case if out of bounds going forwards UIMA-5063
// switch(boundsUse) {
// case notBounded:
// break;
// case coveredBy:
// break;
// case covering:
// maybeMakeItInvalid_bounds(it.getNvc(), a -> a.getBegin() > boundBegin);
// break;
// case sameBeginEnd:
// maybeMakeItInvalidSameBeginEnd();
// break;
// }
// }
}
/*
* (non-Javadoc)
*
* @see org.apache.uima.cas.FSIterator#moveToFirst()
*/
@Override
public void moveToFirst() {
if (isIndexesHaveBeenUpdated()) {
resetList();
moveToStartSetEmptyAndId();
return;
}
if (isEmpty) {
return;
}
if (isListForm) {
this.pos = 0;
} else {
moveToStart();
}
}
private void resetList() {
if (isListForm) {
isListForm = false;
if (list != null) {
list.clear();
};
}
}
/*
* This operation is relatively expensive one time for unambiguous
*
* @see org.apache.uima.cas.FSIterator#moveToLast()
*/
@Override
public void moveToLast() {
if (isIndexesHaveBeenUpdated()) {
moveToFirst(); // done to recompute is empty, reset list, recompute bounds, etc.
}
if (isEmpty) {
return;
}
if (!isAmbiguous && !isListForm) {
convertToListForm();
}
if (isListForm) {
this.pos = this.list.size() - 1;
} else {
// always bounded case because if unambig. case, above logic converted to list form and handled as list
// and unambig is the only Subiterator case without bounds
assert isBounded;
switch (boundsUse) {
case notBounded:
Misc.internalError(); // never happen, because should always be bounded here
break;
case coveredBy:
moveToJustPastBoundsAndBackup(boundEnd + 1, boundEnd + 1, a -> a.getBegin() > boundEnd);
break;
case covering:
moveToJustPastBoundsAndBackup(boundBegin + 1, boundBegin + 1, a -> a.getBegin() > boundBegin);
break;
case sameBeginEnd:
moveToJustPastBoundsAndBackup(boundBegin, boundEnd + 1, a -> a.getEnd() != boundEnd);
break;
}
}
}
/**
* Called by move to Last, to move to a place just beyond the last spot, and then backup
* while the goBacwards is true
*
* Includes adjustForStrictOrCoveringAndBoundSkip going backwards
* @param begin a position just past the last spot
* @param end a position just past the last spot
* @param goBackwards when true, continue to backup
*/
private void moveToJustPastBoundsAndBackup(int begin, int end, Predicate<Annotation> goBackwards) {
it.moveTo(new Annotation(jcas, begin, end));
if (it.isValid()) {
Annotation a = it.getNvc();
while (goBackwards.test(a)) {
if (a._id == startId) {
assert a.getBegin() <= boundEnd; // because it's non-empty
// use this as the last
break; // no need to adjust for strict and bound skip because the startId has that incorporated
}
it.moveToPreviousNvc();
adjustForStrictOrCoveringAndBoundSkip(false);
if (!it.isValid()) {
break;
}
a = it.getNvc();
}
} else {
it.moveToLast();
adjustForStrictOrCoveringAndBoundSkip(false);
}
}
// default visibility - referred to by flat iterator
static Comparator<AnnotationFS> getAnnotationBeginEndComparator(final int boundingBegin, final int boundingEnd) {
return new Comparator<AnnotationFS>() {
@Override
public int compare(AnnotationFS o1, AnnotationFS o2) {
AnnotationFS a = (o1 == null) ? o2 : o1;
boolean isReverse = o1 == null;
final int b;
if ((b = a.getBegin()) < boundingBegin) {
return isReverse? 1 : -1;
}
if (b > boundingBegin) {
return isReverse? -1 : 1;
}
final int e;
if ((e = a.getEnd()) < boundingEnd) {
return isReverse? -1 : 1;
}
if (e > boundingEnd) {
return isReverse? 1 : -1;
}
return 0;
}
};
}
/*
* (non-Javadoc)
*
* @see org.apache.uima.cas.FSIterator#moveTo(org.apache.uima.cas.FeatureStructure)
*/
@Override
public void moveTo(FeatureStructure fs) {
if (isIndexesHaveBeenUpdated()) {
moveToFirst(); // done to recompute is empty, reset list, recompute bounds, etc.
}
if (isEmpty) return;
Annotation fsa = (Annotation) fs;
if (!isAmbiguous && !isListForm) { // unambiguous must be in list form
convertToListForm();
}
// need to handle 4 cases of not-covered, covered-by, covering, and same
if (isListForm) {
// W A R N I N G - don't use it.xxx forms here, they don't work for list form
// Don't need strict, skip-over-boundary, or type priority adjustments,
// because these were done when the list form was created
pos = Collections.binarySearch(this.list, fsa, annotationComparator);
int begin = fsa.getBegin();
int end = fsa.getEnd();
Type type = fsa.getType();
if (pos >= 0) {
if (!isValid()) {
return;
}
// Go back until we find a FS that is really smaller
moveToPrevious();
while (isValid() && isBeginEndTypeEqual((Annotation) get(), begin, end, type)) {
moveToPrevious();
}
if (isValid()) {
moveToNext(); // backed up one to much
} else {
moveToFirst();
}
} else {
// element wasn't found,
//set the position to the next (greater) element in the list or to an invalid position
// if no element is greater
pos = (-pos) - 1;
if (!isValid()) {
moveToLast();
if (!isValid()) {
return;
}
}
while (isValid() && isBeginEndTypeEqual((Annotation) get(), begin, end, type)) {
moveToPrevious();
}
if (!isValid()) {
moveToFirst();
return;
}
moveToNext(); // back up one
}
} else {
// not list form
// is ambiguous, may be strict.
// Always bounded (if unbounded, that's only when subiterator is being used to
// implement "unambiguous", and that mode requires the "list" form above.)
// can be one of 3 bounds: coveredBy, covering, and sameBeginEnd.
it.moveTo(fs); // may move before, within, or after bounds
adjustForTypePriorityBoundingBegin(0); // does nothing if using type priorities
adjustForStrictOrCoveringAndBoundSkip(true); // "covering" case adjustments
if (it.isValid()) {
// if beyond bound, mark invalid
// if before bound, move to first
Annotation a = it.get();
switch (boundsUse) {
case coveredBy:
case sameBeginEnd:
if (a.getBegin() > boundEnd) {
makeInvalid();
} else if (isTypePriority && annotationComparator.compare(a, boundingAnnot) < 0) {
// with type priority, position is before bound.
moveToFirst();
} else { // is not type priority case - see if too low
final int b = a.getBegin();
final int e = a.getEnd();
if (b < boundBegin ||
( b == boundBegin && e > boundEnd)) {
moveToFirst();
} else if (isPositionUsesType &&
b == boundBegin &&
e == boundEnd &&
a.getType() != boundType) {
/** Subiterator {0} has bound type: {1}, begin: {2}, end: {3}, for coveredBy, not using type priorities, matching FS with same begin end and different type {4}, cannot order these*/
throw new CASRuntimeException(CASRuntimeException.SUBITERATOR_AMBIGUOUS_POSITION_DIFFERENT_TYPES,
this, boundType.getName(), b, e, a.getType().getName());
}
}
break;
case covering:
break;
default:
Misc.internalError();
}
}
}
}
/*
* (non-Javadoc)
*
* @see org.apache.uima.cas.FSIterator#copy()
*/
@Override
public FSIterator<T> copy() {
Subiterator<T> copy = new Subiterator<T>(
this.it.copy(),
this.boundingAnnot,
this.isAmbiguous,
this.isStrict,
this.boundsUse,
this.isTypePriority,
this.isPositionUsesType,
this.isSkipEquals,
this.annotationComparator,
this.startId,
this.isEmpty,
this.coveringStartPos,
this.coveringEndPos);
copy.list = this.list; // non-final things
copy.pos = this.pos;
copy.isListForm = this.isListForm;
return copy;
}
/**
* This is unsupported because its expensive to compute
* in many cases, and may not be needed.
*/
@Override
public int ll_indexSize() {
throw new UnsupportedOperationException();
}
@Override
public int ll_maxAnnotSpan() {
if (isEmpty) {
return 0;
}
return ((LowLevelIterator)it).ll_maxAnnotSpan();
}
@Override
public LowLevelIndex<T> ll_getIndex() {
return ((LowLevelIterator<T>)it).ll_getIndex();
}
private void makeInvalid() {
it.moveToFirst();
it.moveToPrevious();
}
/**
* Used to determine when some precomputed things (e.g. listform) need to be recalculated
* @return true if one or more of the underlying indexes of the underlying iterator have been updated
*/
@Override
public boolean isIndexesHaveBeenUpdated() {
return ((LowLevelIterator<?>)it).isIndexesHaveBeenUpdated();
}
// // makes an extra copy of the items
// private void initAmbiguousSubiterator(AnnotationFS annot, final boolean strict) {
// final int start = (annot == null) ? 0 : annot.getBegin();
// final int boundingEnd = (annot == null) ? Integer.MAX_VALUE : annot.getEnd();
// if (annot == null) {
// it.moveToFirst();
// } else {
// it.moveTo(annot); // to "earliest" equal, or if none are equal, to the one just later than annot
// }
//
// // This is a little silly, it skips 1 of possibly many indexed annotations if the earliest one is "equal"
// // (just means matching the keys) to the control annot 4/2015
// if (it.isValid() && it.get().equals(annot)) {
// it.moveToNext();
// }
// // Skip annotations whose start is before the start parameter.
// // should never have any???
// while (it.isValid() && it.get().getBegin() < start) {
// it.moveToNext();
// }
// T current;
// while (it.isValid()) {
// current = it.get();
// // If the start of the current annotation is past the boundingEnd parameter,
// // we're done.
// if (current.getBegin() > boundingEnd) {
// break;
// }
// it.moveToNext();
// if (strict && current.getEnd() > boundingEnd) {
// continue;
// }
// this.list.add(current);
// }
// }
//
// private void initUnambiguousSubiterator(AnnotationFS annot, final boolean strict) {
// final int start = annot.getBegin();
// final int boundingEnd = annot.getEnd();
// it.moveTo(annot);
//
// if (it.isValid() && it.get().equals(annot)) {
// it.moveToNext();
// }
// if (!it.isValid()) {
// return;
// }
// annot = it.get();
// // Skip annotations with begin positions before the given start
// // position.
// while (it.isValid() && ((start > annot.getBegin()) || (strict && annot.getEnd() > boundingEnd))) {
// it.moveToNext();
// }
// // Add annotations.
// if (!it.isValid()) {
// return;
// }
// T current = null;
// while (it.isValid()) {
// final T next = it.get();
// // If the next annotation overlaps, skip it. Don't check while there is no "current" yet.
// if ((current != null) && (next.getBegin() < current.getEnd())) {
// it.moveToNext();
// continue;
// }
// // If we're past the boundingEnd, stop.
// if (next.getBegin() > boundingEnd) {
// break;
// }
// // We have an annotation that's within the boundaries and doesn't
// // overlap
// // with the previous annotation. We add this annotation if we're not
// // strict, or the end position is within the limits.
// if (!strict || next.getEnd() <= boundingEnd) {
// current = next;
// this.list.add(current);
// }
// it.moveToNext();
// }
///**
//* Called for begin/end compare, after moveTo(fs)
//* to eliminate the effect of the type order comparison before any adjustment for strict.
//* Move backwards while equal with begin/end iterator.
//*/
//private void adjustAfterMoveToForBeginEndComparator(FeatureStructure aFs) {
// final Annotation fs = (Annotation) aFs;
// final int begin = fs.getBegin();
// final int end = fs.getEnd();
//
// while (it.isValid()) {
// Annotation item = it.getNvc();
// if (item.getBegin() != begin || item.getEnd() != end) {
// break;
// }
// it.moveToPrevious();
// }
// // are one position too far, move back one
// if (it.isValid()) {
// it.moveToNext();
// } else {
// moveToStart();
// }
//}
// }
}