001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *   http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied.  See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 */
019package org.apache.maven.doxia.module.markdown;
020
021import java.io.IOException;
022import java.io.Writer;
023
024import org.apache.commons.lang3.StringUtils;
025
026/**
027 * Decorates an existing writer to additionally temporarily buffer the last two lines written.
028 * Useful to collapse subsequent new lines or blank lines by evaluating {@link #isWriterAfterBlankLine()} and {@link #isWriterAfterBlankLine()}.
029 * The buffering does not affect or defer delegation to the underlying writer, though.
030 */
031public class LastTwoLinesBufferingWriter extends Writer {
032
033    private final Writer out;
034    private String previousLine;
035    private StringBuilder currentLine;
036    private final String lineSeparator;
037
038    public LastTwoLinesBufferingWriter(Writer out) {
039        // don't use System.lineSeparator, as overwritten in AbstractModuleTest
040        this(out, System.getProperty("line.separator"));
041    }
042
043    LastTwoLinesBufferingWriter(Writer out, String lineSeparator) {
044        super();
045        this.out = out;
046        this.previousLine = "";
047        this.currentLine = new StringBuilder();
048        this.lineSeparator = lineSeparator;
049    }
050
051    public boolean isWriterAtStartOfNewLine() {
052        return currentLine.length() == 0;
053    }
054
055    public boolean isWriterAfterBlankLine() {
056        return StringUtils.isAllBlank(currentLine) && StringUtils.isAllBlank(previousLine);
057    }
058
059    @Override
060    public void write(char[] cbuf, int off, int len) throws IOException {
061        int offsetWrittenInLineBuffer = off;
062        int index = 0;
063        while (index < len) {
064            // potentially a line break...
065            if (cbuf[off + index] == '\r' || cbuf[off + index] == '\n') {
066                int lenToWrite = index + 1 - (offsetWrittenInLineBuffer - off);
067                flushLine(cbuf, offsetWrittenInLineBuffer, lenToWrite);
068                offsetWrittenInLineBuffer += lenToWrite;
069            }
070            index++;
071        }
072        flushLine(cbuf, offsetWrittenInLineBuffer, index - (offsetWrittenInLineBuffer - off));
073        out.write(cbuf, off, len);
074    }
075
076    private void flushLine(char[] cbuf, int off, int len) {
077        this.currentLine.append(cbuf, off, len);
078        // really a line break?
079        if (currentLine.toString().endsWith(lineSeparator)) {
080            previousLine = currentLine.toString();
081            currentLine.setLength(0);
082        }
083    }
084
085    @Override
086    public void flush() throws IOException {
087        out.flush();
088    }
089
090    @Override
091    public void close() throws IOException {
092        out.close();
093    }
094}