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.maven.doxia.util.DoxiaStringUtils; 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 LastTwoLinesAwareWriter extends Writer { 032 033 private final Writer out; 034 private String previousLine; 035 private StringBuilder currentLine; 036 private final String lineSeparator; 037 038 public LastTwoLinesAwareWriter(Writer out) { 039 // don't use System.lineSeparator, as overwritten in AbstractModuleTest 040 this(out, System.getProperty("line.separator")); 041 } 042 043 LastTwoLinesAwareWriter(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 DoxiaStringUtils.isBlank(currentLine.toString()) && DoxiaStringUtils.isBlank(previousLine); 057 } 058 059 public boolean isInBlankLine() { 060 return DoxiaStringUtils.isBlank(currentLine.toString()); 061 } 062 063 @Override 064 public void write(char[] cbuf, int off, int len) throws IOException { 065 int offsetWrittenInLineBuffer = off; 066 int index = 0; 067 while (index < len) { 068 // potentially a line break... 069 if (cbuf[off + index] == '\r' || cbuf[off + index] == '\n') { 070 int lenToWrite = index + 1 - (offsetWrittenInLineBuffer - off); 071 flushLine(cbuf, offsetWrittenInLineBuffer, lenToWrite); 072 offsetWrittenInLineBuffer += lenToWrite; 073 } 074 index++; 075 } 076 flushLine(cbuf, offsetWrittenInLineBuffer, index - (offsetWrittenInLineBuffer - off)); 077 out.write(cbuf, off, len); 078 } 079 080 private void flushLine(char[] cbuf, int off, int len) { 081 this.currentLine.append(cbuf, off, len); 082 // really a line break? 083 if (currentLine.toString().endsWith(lineSeparator)) { 084 previousLine = currentLine.toString(); 085 currentLine.setLength(0); 086 } 087 } 088 089 @Override 090 public void flush() throws IOException { 091 out.flush(); 092 } 093 094 @Override 095 public void close() throws IOException { 096 out.close(); 097 } 098 099 public boolean isAfterDigit() { 100 return currentLine.length() > 1 && Character.isDigit(currentLine.charAt(currentLine.length() - 1)); 101 } 102}