Hi Mario, if I understand you correctly, you imagine select() to be a streaming operation. Actually, it is not - at least not immediately.
When select() is invoked, it creates an object that is a hybrid between a builder, an Iterable and a Stream. If at any point you invoke an Iterable or Stream method on it, it loses the other personalities. The methods such as the following are part of the "builder" personality: - following(x) - coveredBy(x) - covering(x) - ... - shifted(y) - backwards() - noneOverlapping() - typePriorities() - ... While operating on the "builder" personality, the order of methods has no effect. E.g. the following calls are all equivalent: cas.select(Token.class).shifted(-1).following(t3).backwards() cas.select(Token.class).following(t3).backwards().shifted(-1) cas.select(Token.class).backwards().shifted(-1).following(t3) If you try to give conflicting instructions to the builder personality, the last instruction should be used, e.g. cas.select(Token.class).following(t3).shifted(-1).preceding(t4) should be equivalent to cas.select(Token.class).preceding(t4).shifted(-1) (... or if there are bugs it might do something unexpected ...) Methods like coveredBy(x) or covering(x) set up bounds for the iterator internally created by SelectFS. I think the initial idea for following(x)/preceding(x) was that they would not define bounds - but IMHO that doesn't make too much sense. From my perspective they also define bounds either from the beginning of the document to x (preceding) or from x to the end of the document (following). There is also the startAt(x) method - this does not define a boundary - it just moves the iterator to a given start position. So while the following operations are bounded: cas.select(Token.class).following(x).asList() cas.select(Token.class).preceding(x).asList() these operations are their respective not-bounded versions cas.select(Token.class).startAt(x).asList() cas.select(Token.class).startAt(x).backwards().asList() The not-bounded versions behave a bit differently from the bounded ones. E.g. preceding(x) returns annotations in document order while startAt(x).backwards() returns them in iteration order. Also, following(x) and preceding(x) would never include x in their results, while startAt(x) should return x as the first entry in the result list. I do hope that I explained this correctly and that it makes sense and that it mostly matches the implementation. I am still working on setting up a tighter test suite to ensure it does ;) select() only really becomes a stream if you invoke stream() or a method from the Stream interface (e.g. filter() or map()). It can also become a list, an array, or an iterator. So the following is actually *not* possible: select(Token.class).filter(t -> t.getCoveredText().equals("blah")).shifted(1) because "shifted()" is a method from the builder personality of SelectFS while "filter()" is a method of the Stream personality. However, this would work: select(Token.class).filter(t -> t.getCoveredText().equals("blah")).skip(1) because "skip()" is a method on Stream. Ok, but independent of the different personalities of select(), I understand that you'd find it not logical or intuitive that limit and shifted interact with each other. But you do support the idea of capping shift at 0 and simply ignoring any smaller values for bounded selections. Cheers, -- Richard