public class AfterModification {

    //
    // injected
    //

    /**
     * See <code>ModifyingReader#newMinimumLengthOfLookBehind</code>.
     * <p>
     * The value of this property must be a non-negative number.
     */
    private int newMinimumLengthOfLookBehind;

    /**
     * See {@link #getNewNumberOfChars()}
     */
    private int newNumberOfChars;

    /**
     * See {@link #getNumberOfCharactersToSkip()}
     */
    private int numberOfCharactersToSkip;

    /**
     * See {@link #isModifyAgainImmediately()}.
     */
    private boolean modifyAgainImmediately;

    //
    // constructors
    //

    /**
     * @param numberOfCharactersToSkip
     *            See {@link #getNumberOfCharactersToSkip()}
     * @param modifyAgainImmediately
     *            See {@link #isModifyAgainImmediately()}
     * @param newMinimumLengthOfLookBehind
     *            See {@link #getNewMinimumLengthOfLookBehind()}
     * @param newNumberOfChars
     *            See {@link #getNewNumberOfChars()}
     */
    public AfterModification(int numberOfCharactersToSkip, boolean modifyAgainImmediately,
            int newMinimumLengthOfLookBehind, int newNumberOfChars) {

        init(numberOfCharactersToSkip, modifyAgainImmediately, newMinimumLengthOfLookBehind, newNumberOfChars);

    }

    protected void init(int numberOfCharactersToSkip_, boolean modifyAgainImmediately_,
            int newMinimumLengthOfLookBehind_, int newNumberOfChars_) {

        ZzzValidate.isZeroOrPositiveNumber(numberOfCharactersToSkip_, "numberOfCharactersToSkip");
        ZzzValidate.isZeroOrPositiveNumber(newMinimumLengthOfLookBehind_, "minimumLengthOfLookBehind");
        // (newNumberOfChars should not be zero if end of stream is
        // not hit but we cannot check this constraint here as in this method we
        // don't know whether the end of stream is hit.)
        ZzzValidate.isZeroOrPositiveNumber(newNumberOfChars_, "newNumberOfChars_");

        if (modifyAgainImmediately && numberOfCharactersToSkip_ != 0) {
            throw new IllegalArgumentException("if modify again immediately,"
                    + " the number of characters to skip are ignored .. "
                    + "so why is the number of characters to skip " + numberOfCharactersToSkip_ + " instead of zero?");
        }

        this.numberOfCharactersToSkip = numberOfCharactersToSkip_;
        this.modifyAgainImmediately = modifyAgainImmediately_;
        this.newMinimumLengthOfLookBehind = newMinimumLengthOfLookBehind_;
        this.newNumberOfChars = newNumberOfChars_;
    }

    //
    // getter
    //

    /**
     * @return Returns the {@link #newMinimumLengthOfLookBehind}.
     *         <p>
     *         The value must be a positive number or zero.
     */
    public int getNewMinimumLengthOfLookBehind() {
        return newMinimumLengthOfLookBehind;
    }

    /**
     * @return Returns the number of unmodifiable characters (look-behind) and modifiable characters in the buffer that
     *         shall be passed to {@link Modifier#modify(StringBuilder, int, boolean)} the next time this method is
     *         called.
     *         <p>
     *         Unless the end of the stream is hit, {@link ModifyingReader} and {@link ModifyingWriter} do not provide
     *         fewer characters to the modifier than requested by the value returned here.
     *         <p>
     *         The value must be a positive number greater than zero.
     */
    public int getNewNumberOfChars() {
        return newNumberOfChars;
    }

    /**
     * @return Returns true if the modifier shall be called again immediately.
     *         <p>
     *         This is usually true either if we want to edit the insertion itself or if the modifier has to read more
     *         characters to decide again about an appropriate modification. The latter one is particularly useful for
     *         modifiers that use (greedy) regular expressions.
     *         <p>
     *         See also {@link #getNumberOfCharactersToSkip()}.
     */
    public boolean isModifyAgainImmediately() {
        return modifyAgainImmediately;
    }

    /**
     * @return Returns the number of characters to skip if the modification shall no be tried again, i.e. if
     *         {@link #isModifyAgainImmediately()} is false. In this case this number must be at least one unless the
     *         end of the stream is hit.
     *         <p>
     *         After the modifiable characters are investigated by the modifier and some characters might be deleted or
     *         inserted, you have to specify the number of characters that are no longer considered modifiable. If the
     *         end of the stream is not hit and {@link #isModifyAgainImmediately()} returns false, then this method must
     *         return at least 1 -- so that the processing of the characters of the stream makes any progress.
     *         <p>
     *         In all cases the value of this property must be a non-negative number.
     *         <p>
     *         This property will be ignored by {@link ModifyingReader} and {@link ModifyingWriter} if
     *         {@link #isModifyAgainImmediately()} returns true.
     */
    public int getNumberOfCharactersToSkip() {
        return numberOfCharactersToSkip;
    }

    /**
     * @return Returns an informal description of the type of the message, i.e. either SKIP or MODIFY AGAIN IMMEDIATELY
     *         or STOP.
     */
    public String getMessageType() {
        if (numberOfCharactersToSkip != 0) {
            return "SKIP";
        } else if (modifyAgainImmediately) {
            return "MODIFY AGAIN IMMEDIATELY";
        } else {
            return "STOP";
        }
    }

    //
    // Object.*
    //

    /**
     * @see java.lang.Object#toString()
     */
    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder();
        builder.append("AfterModification [\nnewMinimumLengthOfLookBehind=");
        builder.append(newMinimumLengthOfLookBehind);
        builder.append(", \nnewNumberOfModifiableChars=");
        builder.append(newNumberOfChars);
        builder.append(", \nnumberOfCharactersToSkip=");
        builder.append(numberOfCharactersToSkip);
        builder.append(", \nmodifyAgainImmediately=");
        builder.append(modifyAgainImmediately);
        builder.append("]");
        return builder.toString();
    }
} public interface Modifier {

    /**
     * Processes the characters in the given character buffer, i.e. deletes or replaces or inserts characters, or keeps
     * the characters as they are.
     * 
     * @param characterBuffer
     *            The next characters provided from the modifiable stream. It contains the modifiable characters,
     *            (optionally) preceded by unmodifiable characters.
     *            <p>
     *            The modifier can modify the content of the buffer as appropriate the modifier must not modify the
     *            unmodifiable characters, i.e. the characters before position
     *            <code>firstModifiableCharacterInBufferIndex</code> must not be modified.
     *            <p>
     *            Your modifier should manage the {@link StringBuilder#capacity() capacity} of the buffer on its own -
     *            as the optimal management of the capacity depends on the specific purpose of the modifier.
     *            <p>
     *            The given buffer must be never null.
     * @param firstModifiableCharacterInBuffer
     *            index of the first modifiable character in the buffer. The index of the first character in the buffer
     *            is zero. If there is not modifiable character in the buffer, then this value is equal the length of
     *            the buffer.
     * @param endOfStreamHit
     *            True if no more characters can be read from the stream, i.e. the character buffer contains the all the
     *            characters up to the end of the stream.
     * @return Returns an object that defines how {@link ModifyingReader} or {@link ModifyingWriter} shall behave before
     *         calling {@link Modifier#modify(StringBuilder, int, boolean)} again.
     */
    public AfterModification modify(StringBuilder characterBuffer, int firstModifiableCharacterInBuffer,
            boolean endOfStreamHit);
}
 public class ModifyingReader extends Reader {

    //
    // injected properties
    //

    /**
     * The reader that the reads from the underlying character stream.
     */
    protected Reader delegate;

    /**
     * The modifier thats replaces, deletes, inserts characters of the underlying stream, and manages the buffer size.
     */
    private Modifier modifier;

    //
    // properties that represent the mutable state
    //

    /**
     * At its begin the buffer contains unmodifiable characters (might be needed for look-behind matches as known from
     * regular expression matching). After the unmodifiable characters the buffer holds the modifiable characters. See
     * {@link #firstModifiableCharacterInBuffer}.
     */
    private StringBuilder characterBuffer;

    /**
     * The position of the first character in the {@link #characterBuffer input buffer} that is modifiable.
     * <p>
     * This character and the following characters in {@link #characterBuffer} can be modified by the {@link #modifier}.
     */
    private int firstModifiableCharacterInBuffer;

    /**
     * The value is taken from {@link AfterModification#getNewMinimumLengthOfLookBehind()} after
     * {@link Modifier#modify(StringBuilder, int, boolean)} is called. The value is <em>requested</em> by the
     * {@link Modifier}.
     * <p>
     * If this value is greater than {@link #firstModifiableCharacterInBuffer}, the modifier is probably faulty.
     */
    private int minimumLengthOfLookBehind;

    /**
     * The value is the sum of {@link AfterModification#getNewMinimumLengthOfLookBehind()} and
     * {@link AfterModification#getNewNumberOfChars()} after {@link Modifier#modify(StringBuilder, int, boolean)} is
     * called. The value is <em>requested</em> by the {@link Modifier}.
     */
    private int requestedNumCharactersInBuffer;

    /**
     * The value is initially taken from {@link AfterModification#getNumberOfCharactersToSkip()} after
     * {@link Modifier#modify(StringBuilder, int, boolean)} is called. Then during reading characters from the reader
     * this value is decremented.
     */
    private int numberOfCharactersToSkip;

    /**
     * True if the end of stream was detected.
     */
    private boolean endOfStreamHit = false;

    /**
     * The holds the last {@link AfterModification} provided by the {@link #modifier}. This property serves debugging
     * purposes only.
     */
    private AfterModification lastAfterModificationForDebuggingOnly = null;

    /**
     * @param reader
     *            The underlying reader that provides the original, not modified characters. For optimal performance
     *            choose a {@link BufferedReader}.
     * @param modifier
     *            the object that modifies the stream.
     */
    public ModifyingReader(Reader reader, Modifier modifier) {
        super();

        this.delegate = reader;

        // take injected properties
        this.modifier = modifier;

        // initialize the mutable state
        this.numberOfCharactersToSkip = 0;
        this.minimumLengthOfLookBehind = 0; // we cannot do anything else
        this.firstModifiableCharacterInBuffer = 0;
        this.requestedNumCharactersInBuffer = 1; // any value
        this.characterBuffer = new StringBuilder(requestedNumCharactersInBuffer);
        adjustCapacityOfBuffer();
    }

    /**
     * Updates the input buffer according to {@link #minimumLengthOfLookBehind} and
     * {@link #requestedNumCharactersInBuffer}, and then fills the buffer up to its capacity.
     */
    private void updateBuffer() throws IOException {

        removeCharactersInBufferNotNeededAnyLonger();

        adjustCapacityOfBuffer();

        fill();
    }

    /**
     * Deletes those characters at the start of the buffer we do not need any longer.
     */
    private void removeCharactersInBufferNotNeededAnyLonger() {

        int charactersToDelete = firstModifiableCharacterInBuffer - minimumLengthOfLookBehind;

        if (charactersToDelete > 0) {
            characterBuffer.delete(0, charactersToDelete);
            firstModifiableCharacterInBuffer -= charactersToDelete;
        } else if (charactersToDelete < 0) {
            onFaultyModifier(-52, charactersToDelete + " characters to delete but this is not possible ("
                    + firstModifiableCharacterInBuffer + "," + minimumLengthOfLookBehind + ")");
        }
    }

    /**
     * Adjusts the capacity of the buffer.
     */
    protected void adjustCapacityOfBuffer() {

        // is the current capacity not big enough?
        if (characterBuffer.capacity() < requestedNumCharactersInBuffer) {

            // increase the capacity (we delegate to the default behavior of
            // StringBuffer)
            characterBuffer.ensureCapacity(requestedNumCharactersInBuffer);

        }

    }

    /**
     * Reads more characters into the input buffer. This method will block until the buffer is filled with
     * {@link #requestedNumCharactersInBuffer} characters , or an I/O error occurs, or the end of the stream is reached.
     */
    private void fill() throws IOException {

        int length = requestedNumCharactersInBuffer - characterBuffer.length();
        if (length <= 0) {
            // nothing to do
            return;
        }

        char[] buffer = new char[length];
        int offset = 0;

        while (length > 0) {

            int readChars = delegate.read(buffer, offset, length);
            if (readChars != -1) {
                characterBuffer.append(buffer, offset, readChars);
                offset += readChars;
                length -= readChars;
            } else {
                endOfStreamHit = true;
                break;
            }

        }

    }

    protected void onFaultyModifier(int errorCode, String errorMessage) {
        // should we silently ignore any errors and fallback to a meaningful
        // behavior? No because a faulty modifier should be fixed by a
        // ValidModifier, i.e. the user must take the responsibility for the
        // errors and deal with them. Therefore, we throw an exception here.

        // (1) collect the current state of the involved objects
        Map<String, Object> description = new HashMap<String, Object>();

        // describe arguments
        description.put("argument errorCode", errorCode);
        description.put("argument errorMessage", errorMessage);

        // describe reader
        description.put("this", this);

        // (2) create an error message
        List<String> keys = new ArrayList<String>(description.keySet());
        Collections.sort(keys);
        StringBuilder sb = new StringBuilder();
        for (String key : keys) {
            sb.append("\n" + key + ": " + description.get(key));
        }

        // (3) throw exception
        throw new FaultyModifierException(sb.toString(), description);
    }

    //
    // interface Reader
    //

    /**
     * TODO more doc
     * 
     * @see java.io.Reader#read(char[], int, int)
     */
    @Override
    public int read(char[] cbuf, int off, int len) throws IOException {
        int index = 0;
        int read = 0;
        while (index < len && (read = readCharacter()) != -1) {
            cbuf[off + index] = (char) read;
            index++;
        }

        if (index == 0 && read == -1) {
            return -1;
        } else {
            return index;
        }
    }

    /**
     * Contract is similar to {@link java.io.Reader#read()}.
     * <p>
     * TODO more doc
     */
    protected int readCharacter() throws IOException {

        if (numberOfCharactersToSkip == 0) {

            boolean modifyAgainImmediately = false;

            AfterModification afterModification = null;
            do {

                updateBuffer();

                afterModification = modifier.modify(characterBuffer, firstModifiableCharacterInBuffer, endOfStreamHit);

                lastAfterModificationForDebuggingOnly = afterModification;

                // update minimumLengthOfLookBehind
                minimumLengthOfLookBehind = afterModification.getNewMinimumLengthOfLookBehind();
                if (minimumLengthOfLookBehind > firstModifiableCharacterInBuffer
                        + afterModification.getNumberOfCharactersToSkip()) {
                    onFaultyModifier(-11, "Requested Look Behind" + " is impossible because there are not enough "
                            + "characters in the stream.");
                }

                // update requestedNumCharactersInBuffer
                requestedNumCharactersInBuffer = minimumLengthOfLookBehind + afterModification.getNewNumberOfChars();
                if (requestedNumCharactersInBuffer < minimumLengthOfLookBehind + 1) {
                    onFaultyModifier(-13, "Requested Capacity" + " is two small because there must at least one"
                            + " unread characters available after the " + "look behind characters characters in the"
                            + " stream.");
                }

                modifyAgainImmediately = false;
                // do we have to read at least a single character as there is no
                // modifiable character left in the character buffer?
                if (firstModifiableCharacterInBuffer >= characterBuffer.length() && !endOfStreamHit) {
                    // yes, we need fresh input ->
                    modifyAgainImmediately = true;
                }
                // has the modifier requested a modification at the current
                // position?
                else if (afterModification.isModifyAgainImmediately()) {
                    modifyAgainImmediately = true;
                }

            } while (modifyAgainImmediately);

            numberOfCharactersToSkip = afterModification.getNumberOfCharactersToSkip();

            if (!afterModification.isModifyAgainImmediately() && numberOfCharactersToSkip == 0 && !endOfStreamHit) {
                onFaultyModifier(-16, "Not a single characters shall be " + "skipped but this is not possible of "
                        + "modifyAgain() returns false and the end of " + "stream is not reached yet.");
            }
            numberOfCharactersToSkip--;
        } else {
            numberOfCharactersToSkip--;
        }

        // is the end of the stream reached?
        if (firstModifiableCharacterInBuffer >= characterBuffer.length()) {
            // yes -> return -1
            return -1;
        } else {
            // no -> return the next unread character
            char result = characterBuffer.charAt(firstModifiableCharacterInBuffer);

            firstModifiableCharacterInBuffer++;

            return result;
        }
    }

    //
    // override Object.*
    //

    /**
     * @see java.lang.Object#toString()
     */
    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder();
        builder.append("ModifyingReader [\ncharacterBuffer=");
        builder.append(characterBuffer);
        builder.append(", \nnextUnreadCharacterInBuffer=");
        builder.append(firstModifiableCharacterInBuffer);
        builder.append(", \nmodifier=");
        builder.append(modifier);
        builder.append(", \nminimumLengthOfLookBehind=");
        builder.append(minimumLengthOfLookBehind);
        builder.append(", \nrequestedCapacityOfCharacterBuffer=");
        builder.append(requestedNumCharactersInBuffer);
        builder.append(", \nlockedCharacters=");
        builder.append(numberOfCharactersToSkip);
        builder.append(", \nendOfStreamHit=");
        builder.append(endOfStreamHit);
        builder.append(", \nlastModificationForDebuggingOnly=");
        builder.append(lastAfterModificationForDebuggingOnly);
        builder.append("]");
        return builder.toString();
    }

    //
    // delegating methods
    //

    /**
     * @see java.io.Reader#close()
     */
    @Override
    public void close() throws IOException {
        delegate.close();
    }

    // What do I need this delegating method for? As long I don't know I don't
    // delegate.
    // /**
    // * @see java.io.Reader#ready()
    // */
    // @Override
    // public boolean ready() throws IOException {
    // return delegate.ready();
    // }

}
 public interface MatchProcessor {

    /**
     * Processes the given match. The match processor is allowed to modify the given character buffer but it must not
     * modify the characters before the first modifiable character in the buffer because the characters before that
     * position are considered unmodifiable.
     * 
     * @param characterBuffer
     *            Must not be <code>null</code>.
     * @param firstModifiableCharacterInBuffer
     *            In comparison to the parameter with the same name in
     *            {@link Modifier#modify(StringBuilder, int, boolean)} this parameter might be the position at the end
     *            of the previous match if there is one. (TODO should we rather pass two parameters like 'first char'
     *            and 'end of previous match' instead of this single parameter?)
     * @param matchResult
     *            The match that is found in the given character buffer. Must not be <code>null</code>.
     * @return Returns an {@link MatchProcessorResult object} that describes where to continue the matching.
     */
    public MatchProcessorResult process(StringBuilder characterBuffer, int firstModifiableCharacterInBuffer,
            MatchResult matchResult);

}
 public class MatchProcessorResult {

    /**
     * A position in the character buffer. The characters before this position shall be considered unmodifiable by the
     * {@link RegexModifier}.
     * <p>
     * This position must not be smaller than the (originally) first modifiable character in the buffer.
     * <p>
     * This position must not be greater than the size of the buffer plus one.
     * <p>
     * EXAMPLE: This position is equal to the size of the buffer plus one if the empty string is matched (and processed)
     * and the matching shall be continued after the first character after the empty match. This avoids endless loops.
     */
    private int firstModifiableCharacterInBuffer;

    /**
     * True if the matching shall be continued using the characters that already available in the buffer.
     */
    private boolean continueMatching;

    /**
     * @param firstModifiableCharacterInBuffer
     * @param continueMatching
     */
    public MatchProcessorResult(int firstModifiableCharacterInBuffer, boolean continueMatching) {
        super();
        this.firstModifiableCharacterInBuffer = firstModifiableCharacterInBuffer;
        this.continueMatching = continueMatching;
    }

    /**
     * @return Returns the {@link #firstModifiableCharacterInBuffer}.
     */
    public int getFirstModifiableCharacterInBuffer() {
        return firstModifiableCharacterInBuffer;
    }

    /**
     * @return Returns the {@link #continueMatching}.
     */
    public boolean isContinueMatching() {
        return continueMatching;
    }
}
 public interface OnStreamMatcher extends MatchResult {

    // * <p>
    // * What is the technical idea behind this matcher? The purpose of this
    // matcher
    // * is too extend the class {@link Matcher} with the following code.
    // <code><pre>
    // private int lastFrom;
    //
    // public boolean findUntilMatchOrHitEnd(int from, int to) {
    // this.hitEnd = false;
    // this.requireEnd = false;
    // from = from < 0 ? 0 : from;
    // this.first = from;
    // this.oldLast = oldLast < 0 ? from : oldLast;
    // for (int i = 0; i < groups.length; i++)
    // groups[i] = -1;
    // acceptMode = NOANCHOR;
    // boolean result = false;
    // for (this.lastFrom = from; lastFrom <= to; lastFrom++) {
    // result = parentPattern.matchRoot.match(this, lastFrom, text);
    // if (result || hitEnd()) {
    // break;
    // }
    // }
    // if (!result)
    // this.first = -1;
    // this.oldLast = this.last;
    // return result;
    // }
    //
    // public int lastFrom() {
    // return lastFrom;
    // }
    // </pre></code>
    // * <p>
    // * Unfortunately, {@link Matcher} cannot by extended via subclassing
    // because
    // * {@link Matcher} is a final type. The new interface {@link
    // OnStreamMatcher}
    // * provides all methods necessary to do match regular expresions on
    // character
    // * stream including the method {@link #findUnlessHitEnd(int, int)}.

    /**
     * @see Matcher#reset(CharSequence)
     */
    public void reset(CharSequence input);

    /**
     * Looks for the first position that could be the start of a match that starts in the range [ <code>from</code>
     * (including), <code>maxFrom</code> (including)]. If such a position is found, then the method stops and returns
     * true if the input provides enough characters to make a match.
     * <p>
     * ATTENTION! maxFrom can be the position behind(!) the last character of the input.
     * <p>
     * This first position can retrieved via {@link #lastFrom()}.
     * 
     * @param minFrom
     * @param maxFrom
     * @return Returns true if the method found a match at the position {@link #lastFrom()}.
     */
    public boolean findUnlessHitEnd(int minFrom, int maxFrom);

    /**
     * @see Matcher#hitEnd()
     */
    public boolean hitEnd();

    /**
     * @see Matcher#requireEnd()
     */
    public boolean requireEnd();

    /**
     * This property is set by calling {@link #findUnlessHitEnd(int, int)} .
     * <p>
     * ATTENTION! This property returns maxFrom + 1, if {@link #findUnlessHitEnd(int, int)} neither has found a match
     * nor has hit the end of input as long it looked for matches that started somewhere in the interval [minFrom,
     * maxFrom].
     * <p>
     * ATTENTION! This information is not really helpful when the buffer size is equal to maxFrom. In this case you
     * cannot say whether the position of lastFrom is the start of a potential match or not.
     * 
     * @return Returns the last position {@link #findUnlessHitEnd(int, int)} has investigated before it returned.
     */
    public int lastFrom();
}
 public class RegexModifier implements Modifier {

    // * <p>
    // * <h2>A summary of the internal algorithm (some details are left
    // out)</h2>
    // * <p>
    // * Is there a match in the buffer?
    // * <ul>
    // * <li>we found a match
    // * <ul>
    // * <li>entire buffer matches and end of stream not hit yet -> the match
    // might
    // * change with more input -> FETCH_MORE_INPUT (<i>match_open</i>)
    // * <li>replace the matched content, and then
    // * <ul>
    // * <li>the match processor decides that no skip is needed yet -> continue
    // with
    // * the existing buffer content -> try another match
    // (<i>match_n_continue</i>)
    // * <li>skip needed but no characters left in the buffer after the
    // replacement ->
    // * MODIFY_AGAIN_IMMEDIATELY is only thing we can do
    // (<i>match_n_refill</i>)
    // * <li>skip needed and there are characters left in the buffer -> SKIP
    // * (<i>match_n_skip</i>)
    // * </ul>
    // * </ul>
    // * <li>we haven't found a match.
    // * <ul>
    // * <li>By looking for matches (including the empty string) that start in
    // the
    // * range [from, maxFrom], the end of the buffer is hit.
    // * <ul>
    // * <li>end of stream hit -> no match possible -> SKIP the entire buffer
    // * (<i>nomatch_eos</i>)
    // * <li>end of stream not hit -> match might be possible or end of buffer
    // is hit
    // * -> FETCH_MORE_INPUT cannot be wrong (<i>nomatch_fetch</i>)
    // * </ul>
    // * <li>We did not match a single character or the empty string -> SKIP the
    // * entire buffer (<i>nomatch_skip</i>)
    // * </ul>
    // * </ul>

    //
    // injected
    //

    protected ModificationFactory factory;

    protected MatchProcessor matchProcessor;

    /**
     * The compiled representation of a regular expression. If the regular expression matches, then a modification shall
     * be carried out.
     */
    protected OnStreamMatcher matcher;

    protected int newNumberOfChars = -1;

    //
    // state
    //

    /**
     * The number of characters that shall be skipped automatically if the modifier is called the next time.
     * <p>
     * This property is either zero or one (not greater). If this property is one, then the modifier tries to match
     * behind the first modifiable character. If this property is zero, then the modifier tries to match before the
     * first modifiable character.
     */
    private int unseenCharactersToSkip = 0;

    //
    // constructors
    //

    /**
     * Only for subclasses.
     */
    protected RegexModifier() {
        super();
    }

    /**
     * Like {@link RegexModifier#RegexModifier(String, int, String, int, int)} but uses defaults for
     * <code>minimumLengthOfLookBehind</code> (1) and <code>newNumberOfChars</code> (2048).
     */
    public RegexModifier(String regex, int flags, String replacement) {
        this(regex, flags, replacement, 1, 2048);
    }

    /**
     * Creates a modifier that matches a regular expression on character streams and replaces the matches.
     * <p>
     * This modifier uses {@link OnStreamStandardMatcher} which is not the fastest implementation of
     * {@link OnStreamMatcher}. If you want to use a faster matcher, use
     * {@link #RegexModifier(OnStreamMatcher, MatchProcessor, int, int)} instead.
     * <p>
     * A more convenient use of a {@link RegexModifier} is provided by the {@link ModifyingReaderFactory} respectively
     * {@link ModifyingWriterFactory}.
     * 
     * @param regex
     *            the regular expression that describe the text that shall be replaced. See
     *            {@link Pattern#compile(String, int)}.
     * @param flags
     *            the flags that are to use when the regex is applied on the character stream. See
     *            {@link Pattern#compile(String, int)}.
     * @param replacement
     *            the replacement for the text that is matched via <code>regex</code>. See
     *            {@link Matcher#appendReplacement(StringBuffer, String)}.
     * @param minimumLengthOfLookBehind
     *            See {@link RegexModifier#RegexModifier(OnStreamMatcher, MatchProcessor, int, int)} .
     * @param newNumberOfChars
     *            See {@link RegexModifier#RegexModifier(OnStreamMatcher, MatchProcessor, int, int)} .
     */
    public RegexModifier(String regex, int flags, String replacement, int minimumLengthOfLookBehind,
            int newNumberOfChars) {
        this(regex, flags, new ReplacingProcessor(replacement), minimumLengthOfLookBehind, newNumberOfChars);
    }

    /**
     * See {@link #RegexModifier(String, int, String, int, int)}.
     */
    public RegexModifier(String regex, int flags, MatchProcessor matchProcessor, int minimumLengthOfLookBehind,
            int newNumberOfChars) {

        Matcher jdkMatcher = Pattern.compile(regex, flags).matcher("");
        jdkMatcher.useTransparentBounds(true);
        jdkMatcher.useAnchoringBounds(false);
        init(new OnStreamStandardMatcher(jdkMatcher), matchProcessor, minimumLengthOfLookBehind, newNumberOfChars);
    }

    /**
     * Creates a modifier that matches a regular expression on character streams and does 'something' if matches are
     * found.
     * <p>
     * 
     * @param matcher
     *            Matches a regular expression on a <code>CharSequence</code>.
     * @param matchProcessor
     *            Defines what to do if the regular expression matches some text in the stream.
     * @param minimumLengthOfLookBehind
     *            See {@link AfterModification#getNewMinimumLengthOfLookBehind()}.
     * @param newNumberOfChars
     *            See {@link AfterModification#getNewNumberOfChars()}. This should not be smaller than the length of the
     *            characters sequence the pattern needs to match properly. In case you want to match more than once, the
     *            value should be higher.
     */
    public RegexModifier(OnStreamMatcher matcher, MatchProcessor matchProcessor, int minimumLengthOfLookBehind,
            int newNumberOfChars) {

        init(matcher, matchProcessor, minimumLengthOfLookBehind, newNumberOfChars);
    }

    @SuppressWarnings("hiding")
    protected void init(OnStreamMatcher matcher, MatchProcessor matchProcessor, int minimumLengthOfLookBehind,
            int newNumberOfChars) {

        this.factory = new ModificationFactory(minimumLengthOfLookBehind, newNumberOfChars);
        this.matchProcessor = matchProcessor;
        this.matcher = matcher;
        this.newNumberOfChars = newNumberOfChars;
    }

    //
    // interface Modifier
    //

    /**
     * @see com.github.rwitzel.streamflyer.core.Modifier#modify(java.lang.StringBuilder, int, boolean)
     */
    @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;
                }
            }
        }

    }

    /**
     * This method is called if a certain line of code is reached ("checkpoint").
     * <p>
     * This method should be called only if the modifier is tested. Otherwise you might experience performance
     * penalties.
     * 
     * @param checkpointDescription
     *            A list of objects describing the checkpoint. The objects should be given as name-value-pairs.
     * @return Returns true. This allows you to use this method as side-effect in Java assertions.
     */
    protected boolean __checkpoint(Object... checkpointDescription) {
        // nothing to do here
        return true;
    }

    //
    // override Object.*
    //

    /**
     * @see java.lang.Object#toString()
     */
    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder();
        builder.append("RegexModifier [\nfactory=");
        builder.append(factory);
        builder.append(", \nreplacement=");
        builder.append(matchProcessor);
        builder.append(", \nmatcher=");
        builder.append(matcher);
        builder.append(", \nnewNumberOfChars=");
        builder.append(newNumberOfChars);
        builder.append("]");
        return builder.toString();
    }

    //
    // injected
    //

    /**
     * @param matchProcessor
     *            The {@link #matchProcessor} to set.
     */
    public void setMatchProcessor(MatchProcessor matchProcessor) {
        this.matchProcessor = matchProcessor;
    }

}
 public class DoNothingProcessor extends AbstractMatchProcessor implements MatchProcessor {

    @Override
    public MatchProcessorResult process(StringBuilder characterBuffer, int firstModifiableCharacterInBuffer,
            MatchResult matchResult) {

        return createResult(matchResult, matchResult.end(), true);
    }

}
 public class ModificationFactory {

    private int minimumLengthOfLookBehind;

    private int newNumberOfChars;

    /**
     * To be used by subclasses only.
     */
    protected ModificationFactory() {
        // nothing to do here
    }

    /**
     * @param minimumLengthOfLookBehind
     * @param newNumberOfChars
     */
    public ModificationFactory(int minimumLengthOfLookBehind, int newNumberOfChars) {

        ZzzValidate.isGreaterThanZero(newNumberOfChars, "newNumberOfChars");
        ZzzValidate.isZeroOrPositiveNumber(minimumLengthOfLookBehind, "minimumLengthOfLookBehind");

        this.minimumLengthOfLookBehind = minimumLengthOfLookBehind;
        this.newNumberOfChars = newNumberOfChars;
    }

    //
    // public methods
    //

    /**
     * @param characterBuffer
     * @param firstModifiableCharacterInBuffer
     * @param endOfStreamHit
     * @return Returns a modification that skips some characters (SKIP), i.e. marks them as not modifiable, or returns a
     *         STOP.
     */
    public AfterModification skipEntireBuffer(StringBuilder characterBuffer, int firstModifiableCharacterInBuffer,
            boolean endOfStreamHit) {

        return skipOrStop(characterBuffer.length() - firstModifiableCharacterInBuffer, characterBuffer,
                firstModifiableCharacterInBuffer, endOfStreamHit);
    }

    /**
     * Use this method if you
     * <ul>
     * <li>either want to skip some characters (SKIP) or don't skip any characters as there are no modifiable characters
     * in the buffer left but the end of stream is not hit (SKIP), or
     * <li>don't skip any characters as there are no modifiable characters in the buffer left and the end of stream is
     * hit (STOP).
     * </ul>
     * 
     * @param numberOfCharactersToSkip
     *            the number of characters to skip. This must greater than zero unless the end of the stream is hit or
     *            the buffer does not contain modifiable characters.
     * @param characterBuffer
     * @param firstModifiableCharacterInBuffer
     * @param endOfStreamHit
     *            See parameter in {@link Modifier#modify(StringBuilder, int, boolean)}.
     * @return Returns a modification that skips some characters (SKIP), i.e. marks them as not modifiable, or returns a
     *         STOP.
     */
    public AfterModification skipOrStop(int numberOfCharactersToSkip, StringBuilder characterBuffer,
            int firstModifiableCharacterInBuffer, boolean endOfStreamHit) {

        if (endOfStreamHit && characterBuffer.length() - firstModifiableCharacterInBuffer == 0) {

            ZzzValidate.isTrue(numberOfCharactersToSkip == 0, "numberOfCharactersToSkip must be zero");

            return stop(characterBuffer, firstModifiableCharacterInBuffer, endOfStreamHit);
        } else {
            return skip(numberOfCharactersToSkip, characterBuffer, firstModifiableCharacterInBuffer, endOfStreamHit);
        }
    }

    /**
     * Skips some characters if there are some in the buffer. This method requires that are modifiable characters
     * somewhere in the stream.
     * 
     * @param numberOfCharactersToSkip
     *            the number of characters to skip. This must greater than zero unless the end of stream is not hit and
     *            there no modifiable characters in the buffer.
     * @param characterBuffer
     * @param firstModifiableCharacterInBuffer
     * @param endOfStreamHit
     * @return Returns a modification that skips some characters (SKIP).
     */
    public AfterModification skip(int numberOfCharactersToSkip, StringBuilder characterBuffer,
            int firstModifiableCharacterInBuffer, boolean endOfStreamHit) {

        if (numberOfCharactersToSkip == 0) {
            if (characterBuffer.length() - firstModifiableCharacterInBuffer == 0 && !endOfStreamHit) {
                // OK, buffer is empty but the stream is not empty yet.
            } else {
                String msg = String.format("Probably a programming error. Therefore, "
                        + "we don't fix it automatically." + " (%s, %s, %s, %s)", //
                        characterBuffer, firstModifiableCharacterInBuffer, characterBuffer.length(), endOfStreamHit);
                // System.out.println(msg);
                throw new IllegalStateException(msg);
            }
        }

        // (1) check numberOfCharactersToSkip
        int numberOfModifiableCharactersInBuffer = characterBuffer.length() - firstModifiableCharacterInBuffer;
        if (numberOfCharactersToSkip > numberOfModifiableCharactersInBuffer) {
            String msg = String.format("Probably a programming error. Therefore, "
                    + "we don't fix it automatically. (%s, %s, %s, %s)", characterBuffer,
                    firstModifiableCharacterInBuffer, numberOfCharactersToSkip, characterBuffer.length());
            // System.out.println(msg);
            throw new IllegalStateException(msg);
        }

        // (2) adjust minimumLengthOfLookBehind
        int minimumLengthOfLookBehind_ = minimumLengthOfLookBehind;
        if (minimumLengthOfLookBehind_ > firstModifiableCharacterInBuffer + numberOfCharactersToSkip) {
            // requested value is too big -> adjust
            minimumLengthOfLookBehind_ = firstModifiableCharacterInBuffer + numberOfCharactersToSkip;
        }

        // (3) adjust numberOfChars (nothing to do)
        int newNumberOfChars_ = this.newNumberOfChars;

        // (4) create and return the modification
        return new AfterModification(numberOfCharactersToSkip, false, minimumLengthOfLookBehind_, newNumberOfChars_);
    }

    /**
     * This method
     * <ul>
     * <li>skips the given characters to use the character buffer as optimal as possible,
     * <li>checks whether skipping and the current value of {@link AfterModification#getNewNumberOfChars()} would fetch
     * more input in the character buffer. If not then {@link AfterModification#getNewNumberOfChars()} is doubled until
     * the number would fetch more input in the character buffer.
     * </ul>
     * 
     * @param numberOfCharactersToSkip
     * @param characterBuffer
     * @param firstModifiableCharacterInBuffer
     * @param endOfStreamHit
     * @return Returns a SKIP or MODIFY AGAIN IMMEDIATELY.
     */
    public AfterModification fetchMoreInput(int numberOfCharactersToSkip, StringBuilder characterBuffer,
            int firstModifiableCharacterInBuffer, boolean endOfStreamHit) {

        ZzzValidate.isTrue(!endOfStreamHit, "endOfStreamHit must not be false");

        // (1) check numberOfCharactersToSkip
        int numberOfModifiableCharactersInBuffer = characterBuffer.length() - firstModifiableCharacterInBuffer;
        if (numberOfCharactersToSkip > numberOfModifiableCharactersInBuffer) {
            String msg = String.format("Probably a programming error. Therefore, "
                    + "we don't fix it automatically. (%s, %s, %s, %s)", characterBuffer,
                    firstModifiableCharacterInBuffer, numberOfCharactersToSkip, characterBuffer.length());
            // System.out.println(msg);
            throw new IllegalStateException(msg);
        }

        // (2) adjust minimumLengthOfLookBehind
        int minimumLengthOfLookBehind_ = this.minimumLengthOfLookBehind;
        if (minimumLengthOfLookBehind_ > firstModifiableCharacterInBuffer + numberOfCharactersToSkip) {
            // requested value is too big -> adjust
            minimumLengthOfLookBehind_ = firstModifiableCharacterInBuffer + numberOfCharactersToSkip;
        }

        // (3) adjust newNumberOfChars
        // Can we get automatically more input by skipping
        // the characters? Or - rephrased - does the
        // current character buffer already provide
        // more characters than requested?
        int newNumberOfChars_ = this.newNumberOfChars;
        int modifiableCharsInBufferAfterSkip = numberOfModifiableCharactersInBuffer - numberOfCharactersToSkip;
        while (newNumberOfChars_ <= modifiableCharsInBufferAfterSkip) {
            // yes, by skipping the characters and
            // request the standard number of characters
            // (newNumberOfChars) we would not see more
            // input -> request more input
            newNumberOfChars_ *= 2;
        }

        // (4) choose appropriate modification
        if (numberOfCharactersToSkip > 0) {

            // we skip some characters - and try to match again then
            return new AfterModification(numberOfCharactersToSkip, false, minimumLengthOfLookBehind_, newNumberOfChars_);
        } else {
            // modify again immediately
            return new AfterModification(0, true, //
                    minimumLengthOfLookBehind_, newNumberOfChars_);
        }
    }

    /**
     * Use this method if you want to request another number of characters from the {@link ModifyingReader} and re-try a
     * modification (MODIFY AGAIN IMMEDIATELY).
     * 
     * @return Returns a modification that requests another number of characters from the {@link ModifyingReader} and
     *         requests a call to {@link Modifier#modify(StringBuilder, int, boolean)} via
     *         {@link AfterModification#isModifyAgainImmediately()}.
     */
    public AfterModification modifyAgainImmediately(int newNumberOfChars_, int firstModifiableCharacterInBuffer) {

        return new AfterModification(0, true, firstModifiableCharacterInBuffer, newNumberOfChars_);
    }

    /**
     * @param characterBuffer
     * @param firstModifiableCharacterInBuffer
     * @param endOfStreamHit
     * @return Returns an {@link AfterModification} that is appropriate if the end of stream is hit and there are no
     *         more modifiable characters and you don't want to add characters at the end of the stream. If there are
     *         still modifiable characters in the stream yet, then this method will throw an exception.
     */
    public AfterModification stop(StringBuilder characterBuffer, int firstModifiableCharacterInBuffer,
            boolean endOfStreamHit) {

        // validate arguments
        if (characterBuffer.length() - firstModifiableCharacterInBuffer != 0 || !endOfStreamHit) {
            String msg = String.format("Probably a programming error. Therefore, " + "we don't fix it automatically."
                    + " (%s, %s, %s, %s)", //
                    characterBuffer, firstModifiableCharacterInBuffer, characterBuffer.length(), endOfStreamHit);
            // System.out.println(msg);
            throw new IllegalStateException(msg);
        }

        return new AfterModification(0, false, 0, 1); // 1 <- doesn't matter
    }

    //
    // getter methods
    //

    /**
     * @return Returns the {@link #newNumberOfChars}.
     */
    public int getNewNumberOfChars() {
        return newNumberOfChars;
    }

    /**
     * @see java.lang.Object#toString()
     */
    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder();
        builder.append("ModificationFactory [\nminimumLengthOfLookBehind=");
        builder.append(minimumLengthOfLookBehind);
        builder.append(", \nnewNumberOfChars=");
        builder.append(newNumberOfChars);
        builder.append("]");
        return builder.toString();
    }

}
 public class LineColumnAwareModificationFactory extends ModificationFactoryDecorator {

    //
    // properties that represent the internal mutable state
    //

    /**
     * The index of the line of the position of the next unread character.
     * <p>
     * The index of the first line is zero.
     * <p>
     * The number of lines is aware of aware of the three line endings - <code>\r</code>, <code>\n</code>,
     * <code>\r\n</code>.
     */
    protected long currentLine = 0;

    /**
     * The index of the column of the position of the next unread character. The index of the column is the number of
     * characters read after the last line break, see {@link #currentLine}.
     * <p>
     * The index of the first column is zero.
     */
    protected long currentColumn = 0;

    /**
     * The last character read.
     */
    protected char lastChar = ' ';

    //
    // constructors
    //

    /**
     * @param delegate
     */
    public LineColumnAwareModificationFactory(ModificationFactory delegate) {
        super(delegate);

    }

    //
    // interface ModificationFactory.* methods
    //

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

        AfterModification afterModification = super.fetchMoreInput(numberOfCharactersToSkip, characterBuffer,
                firstModifiableCharacterInBuffer, endOfStreamHit);

        onCharactersSkipped(afterModification.getNumberOfCharactersToSkip(), characterBuffer,
                firstModifiableCharacterInBuffer);

        return afterModification;
    }

    /**
     * @see com.github.rwitzel.streamflyer.util.statistics.ModificationFactoryDecorator#modifyAgainImmediately(int, int)
     */
    @Override
    public AfterModification modifyAgainImmediately(int newNumberOfChars, int firstModifiableCharacterInBuffer) {

        AfterModification afterModification = super.modifyAgainImmediately(newNumberOfChars,
                firstModifiableCharacterInBuffer);

        // no characters to skip

        return afterModification;
    }

    /**
     * @see com.github.rwitzel.streamflyer.util.statistics.ModificationFactoryDecorator#skip(int, java.lang.StringBuilder,
     *      int, boolean)
     */
    @Override
    public AfterModification skip(int numberOfCharactersToSkip, StringBuilder characterBuffer,
            int firstModifiableCharacterInBuffer, boolean endOfStreamHit) {

        AfterModification afterModification = super.skip(numberOfCharactersToSkip, characterBuffer,
                firstModifiableCharacterInBuffer, endOfStreamHit);

        onCharactersSkipped(afterModification.getNumberOfCharactersToSkip(), characterBuffer,
                firstModifiableCharacterInBuffer);

        return afterModification;
    }

    /**
     * @see com.github.rwitzel.streamflyer.util.ModificationFactory#skipEntireBuffer(java.lang.StringBuilder, int, boolean)
     */
    @Override
    public AfterModification skipEntireBuffer(StringBuilder characterBuffer, int firstModifiableCharacterInBuffer,
            boolean endOfStreamHit) {

        AfterModification afterModification = super.skipEntireBuffer(characterBuffer, firstModifiableCharacterInBuffer,
                endOfStreamHit);

        onCharactersSkipped(afterModification.getNumberOfCharactersToSkip(), characterBuffer,
                firstModifiableCharacterInBuffer);

        return afterModification;
    }

    /**
     * @see com.github.rwitzel.streamflyer.util.ModificationFactory#skipOrStop(int, java.lang.StringBuilder, int, boolean)
     */
    @Override
    public AfterModification skipOrStop(int numberOfCharactersToSkip, StringBuilder characterBuffer,
            int firstModifiableCharacterInBuffer, boolean endOfStreamHit) {

        AfterModification afterModification = super.skipOrStop(numberOfCharactersToSkip, characterBuffer,
                firstModifiableCharacterInBuffer, endOfStreamHit);

        onCharactersSkipped(afterModification.getNumberOfCharactersToSkip(), characterBuffer,
                firstModifiableCharacterInBuffer);

        return afterModification;
    }

    /**
     * @see com.github.rwitzel.streamflyer.util.statistics.ModificationFactoryDecorator#stop(java.lang.StringBuilder, int,
     *      boolean)
     */
    @Override
    public AfterModification stop(StringBuilder characterBuffer, int firstModifiableCharacterInBuffer,
            boolean endOfStreamHit) {

        AfterModification afterModification = super.stop(characterBuffer, firstModifiableCharacterInBuffer,
                endOfStreamHit);

        onCharactersSkipped(afterModification.getNumberOfCharactersToSkip(), characterBuffer,
                firstModifiableCharacterInBuffer);

        return afterModification;
    }

    protected void onCharactersSkipped(int numberOfCharactersToSkip, StringBuilder characterBuffer,
            int firstModifiableCharacterInBuffer) {

        int end = firstModifiableCharacterInBuffer + numberOfCharactersToSkip;

        for (int index = firstModifiableCharacterInBuffer; index < end; index++) {
            onCharacterSkipped(characterBuffer.charAt(index));
        }
    }

    protected void onCharacterSkipped(char ch) {

        // update current position, current line, current column

        if (ch == '\r') {
            currentLine++;
            currentColumn = 0;
        } else if (ch == '\n') {
            if (lastChar != '\r') {
                currentLine++;
                currentColumn = 0;
            }
        } else {
            currentColumn++;
        }
        lastChar = ch;
    }

    //
    // getter methods
    //

    /**
     * @return Returns the {@link #currentColumn}.
     */
    public long getCurrentColumn() {
        return currentColumn;
    }

    /**
     * @return Returns the {@link #currentLine}.
     */
    public long getCurrentLine() {
        return currentLine;
    }

}
import static org.junit.Assert.assertEquals;
import java.io.Reader;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.MatchResult;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.io.IOUtils;
import org.junit.Test;
import com.github.rwitzel.streamflyer.core.AfterModification;
import com.github.rwitzel.streamflyer.core.Modifier;
import com.github.rwitzel.streamflyer.core.ModifyingReader;
import com.github.rwitzel.streamflyer.regex.MatchProcessor;
import com.github.rwitzel.streamflyer.regex.MatchProcessorResult;
import com.github.rwitzel.streamflyer.regex.OnStreamMatcher;
import com.github.rwitzel.streamflyer.regex.RegexModifier;
import com.github.rwitzel.streamflyer.regex.addons.util.DoNothingProcessor;
import com.github.rwitzel.streamflyer.util.ModificationFactory;
import com.github.rwitzel.streamflyer.util.statistics.LineColumnAwareModificationFactory;
package com.github.rwitzel.streamflyer.support;





/**
 * <a href="https://groups.google.com/forum/#!topic/streamflyer-discuss/tfoOlLFqLLc">Tracking the line and column
 * number</a>
 *
 * @author rwoo
 */
public class LineColumnOnRegexMatchTest {

    private LineColumnAwareModificationFactory fac;

    private int firstCharIndex;

    public class PositionAwareRegexModifier extends RegexModifier {