class RegexModifier implements Modifier {

    @Override
    public AfterModification modify(StringBuilder characterBuffer, int firstModifiableCharacterInBuffer,
            boolean endOfStreamHit) {

        // the first position we will match from.
        Integer minFrom = null;

        while (true) {

            // determine the range [minFrom, maxFrom] that will contain the
            // first character of the matching string

            if (minFrom == null) {
                minFrom = firstModifiableCharacterInBuffer;

                if (unseenCharactersToSkip > 0) {

                    // is there at least one modifiable character in the buffer?
                    if (minFrom + unseenCharactersToSkip > characterBuffer.length()) {
                        // no -> we need more input to skip the characters

                        if (endOfStreamHit) {
                            // -> stop
                            return factory.stop(characterBuffer, firstModifiableCharacterInBuffer, endOfStreamHit);
                        } else {
                            // -> fetch more input
                            return factory.fetchMoreInput(0, characterBuffer, firstModifiableCharacterInBuffer,
                                    endOfStreamHit);
                        }

                    } else {
                        // yes -> increase the *minFrom*
                        minFrom += unseenCharactersToSkip;
                        unseenCharactersToSkip = 0;
                    }
                }
            }

            // we have to restrict maxFrom in order to prevent that the
            // requested number of characters increases more and more
            int maxFrom = firstModifiableCharacterInBuffer + newNumberOfChars;

            // adjust maxFrom if it is bigger than the given buffer
            if (maxFrom > characterBuffer.length()) {
                // this is NOT set to characterBuffer.length() -1 by intention
                // as a regular expression might match on the zero length string
                // (but with positive look-behind)!
                maxFrom = characterBuffer.length();
            }

            // find first match
            // (as the match processor might have modified the buffer, we reset
            // the matcher inside the loop instead of outside of the loop)
            matcher.reset(characterBuffer);
            boolean matchFound = matcher.findUnlessHitEnd(minFrom, maxFrom);

            if (matchFound) {
                // we found a match

                // could change this positive match into a negative one
                // (matcher.requireEnd()) or into a longer one (greedy
                // operator)?
                if (matcher.hitEnd() && !endOfStreamHit) {
                    // (match_open) yes, it could -> we need more input

                    int numberOfCharactersToSkip = matcher.lastFrom() - firstModifiableCharacterInBuffer;

                    // read more input (skip some characters if possible)
                    AfterModification mod = factory.fetchMoreInput(numberOfCharactersToSkip, characterBuffer,
                            firstModifiableCharacterInBuffer, endOfStreamHit);

                    assert __checkpoint( //
                            "name", "match_open", //
                            "minLen", firstModifiableCharacterInBuffer, //
                            "characterBuffer", characterBuffer, //
                            "endOfStreamHit", endOfStreamHit, //
                            "afterModification", mod);

                    return mod;
                } else {
                    // no -> thus we can use this match -> process the match

                    // process the match
                    MatchResult matchResult = matcher; // .toMatchResult()?
                    // (I could pass firstModifiableCharacterInBuffer instead of
                    // minFrom as well)
                    MatchProcessorResult matchProcessorResult = matchProcessor.process(characterBuffer, minFrom,
                            matchResult);
                    minFrom = matchProcessorResult.getFirstModifiableCharacterInBuffer();

                    // match again without skip? (even for minFrom == maxFrom we
                    // try a match) (minFrom <= maxFrom is needed so that the
                    // buffer does not increase if the replacement is longer
                    // than the replaced string, i.e. minFrom > maxFrom means
                    // that a SKIP is needed)
                    // (I (rwoo) think an earlier SKIP (minFrom < maxFrom
                    // instead of minFrom <= maxFrom) would also be possible.
                    // This has no impact on the matching and only minimal
                    // impact on the performance)
                    if (minFrom <= maxFrom && matchProcessorResult.isContinueMatching()) {
                        // (match_n_continue) no skip needed yet -> continue
                        // matching on the existing buffer content

                        assert __checkpoint( //
                                "name", "match_n_continue", //
                                "minLen", firstModifiableCharacterInBuffer, //
                                "characterBuffer", characterBuffer, //
                                "endOfStreamHit", endOfStreamHit, //
                                "minFrom", minFrom);

                        // We try the next match on the modified input, i.e.
                        // not match only once -> next loop
                        continue;
                    } else {

                        // we shall not continue matching on the
                        // existing buffer content but skip (keep the buffer
                        // small)

                        int numberOfCharactersToSkip;
                        if (minFrom > characterBuffer.length()) {
                            // this happens when we avoid endless loops after
                            // we matched an empty string
                            unseenCharactersToSkip = minFrom - characterBuffer.length();
                            ZzzAssert.isTrue(unseenCharactersToSkip == 1, "unseenCharactersToSkip must be one but was "
                                    + unseenCharactersToSkip);
                            numberOfCharactersToSkip = characterBuffer.length() - firstModifiableCharacterInBuffer;
                        } else {
                            numberOfCharactersToSkip = minFrom - firstModifiableCharacterInBuffer;
                        }

                        if (numberOfCharactersToSkip == 0) {

                            // (match_n_refill) there are no characters left in
                            // the buffer after the replacement ->
                            // MODIFY_AGAIN_IMMEDIATELY is only thing we can do
                            // (the match processor implementation must not
                            // cause an endless loop)

                            // (passing false for endOfStreamHit is ugly!!!)
                            // we should offer a new method in
                            // ModificationFactory, something like
                            // continueAfterModification(...) that chooses the
                            // appropriate action. the following code is always
                            // a MODIFY_AGAIN_IMMEDIATELY
                            AfterModification mod = factory.fetchMoreInput(numberOfCharactersToSkip, characterBuffer,
                                    firstModifiableCharacterInBuffer, false);

                            assert __checkpoint( //
                                    "name", "match_n_refill", //
                                    "minLen", firstModifiableCharacterInBuffer, //
                                    "characterBuffer", characterBuffer, //
                                    "endOfStreamHit", endOfStreamHit, //
                                    "afterModification", mod);

                            return mod;
                        } else {
                            // (match_n_skip) there are characters left in
                            // the buffer -> SKIP

                            AfterModification mod = factory.skipOrStop(numberOfCharactersToSkip, characterBuffer,
                                    firstModifiableCharacterInBuffer, endOfStreamHit);

                            assert __checkpoint( //
                                    "name", "match_n_skip", //
                                    "minLen", firstModifiableCharacterInBuffer, //
                                    "characterBuffer", characterBuffer, //
                                    "endOfStreamHit", endOfStreamHit, //
                                    "afterModification", mod);

                            return mod;
                        }
                    }

                }
            } else {
                // we haven't found a match

                // By looking for matches (including the empty string) that
                // start in the range [from, maxFrom], is the end of the buffer
                // hit?
                if (matcher.lastFrom() <= maxFrom) {
                    // yes, the end of the buffer was hit

                    // can we get more input?
                    if (endOfStreamHit) {
                        // (nomatch_eos) no, in the entire stream we will not
                        // found more matches that start in range [from,
                        // maxFrom] -> skip the characters from range [from,
                        // maxFrom]

                        int numberOfCharactersToSkip = maxFrom - firstModifiableCharacterInBuffer;
                        AfterModification mod = factory.skipOrStop(numberOfCharactersToSkip, characterBuffer,
                                firstModifiableCharacterInBuffer, endOfStreamHit);

                        assert __checkpoint( //
                                "name", "nomatch_eos", //
                                "minLen", firstModifiableCharacterInBuffer, //
                                "characterBuffer", characterBuffer, //
                                "endOfStreamHit", endOfStreamHit, //
                                "afterModification", mod);

                        return mod;

                    } else {
                        // (nomatch_fetch) yes > we should fetch more input
                        // (because end of stream is not hit yet)

                        // if maxFrom == characterBuffer.length() and lastFrom()
                        // == maxFrom we cannot decide whether this is really an
                        // open match or rather a not a match at all. But by
                        // skipping the characters in front of lastFrom() and
                        // fetching more input we cannot do anything wrong

                        int numberOfCharactersToSkip = matcher.lastFrom() - firstModifiableCharacterInBuffer;
                        AfterModification mod = factory.fetchMoreInput(numberOfCharactersToSkip, characterBuffer,
                                firstModifiableCharacterInBuffer, endOfStreamHit);

                        assert __checkpoint( //
                                "name", "nomatch_fetch", //
                                "minLen", firstModifiableCharacterInBuffer, //
                                "characterBuffer", characterBuffer, //
                                "endOfStreamHit", endOfStreamHit, //
                                "afterModification", mod);

                        return mod;
                    }

                } else { // matcher.lastFrom() == maxFrom + 1

                    // (nomatch_skip) no, we are matching not a single character

                    // -> skip the characters from range [from, maxFrom]
                    int numberOfCharactersToSkip = maxFrom - firstModifiableCharacterInBuffer;
                    AfterModification mod = factory.skipOrStop(numberOfCharactersToSkip, characterBuffer,
                            firstModifiableCharacterInBuffer, endOfStreamHit);

                    assert __checkpoint( //
                            "name", "nomatch_skip", //
                            "minLen", firstModifiableCharacterInBuffer, //
                            "characterBuffer", characterBuffer, //
                            "endOfStreamHit", endOfStreamHit, //
                            "afterModification", mod);

                    return mod;
                }
            }
        }

    }

    protected  RegexModifier();
    public  RegexModifier(String regex, int flags, String replacement);
    public  RegexModifier(String regex, int flags, String replacement, int minimumLengthOfLookBehind,
            int newNumberOfChars);
    public  RegexModifier(String regex, int flags, MatchProcessor matchProcessor, int minimumLengthOfLookBehind,
            int newNumberOfChars);
    public  RegexModifier(OnStreamMatcher matcher, MatchProcessor matchProcessor, int minimumLengthOfLookBehind,
            int newNumberOfChars);

}