package org.netbeans.gradle.project.output; import java.io.IOException; import java.io.Reader; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import org.jtrim.utils.ExceptionHelper; import org.netbeans.gradle.project.util.StringUtils; public final class ReplaceLineFeedReader extends Reader { private final Reader src; private final LfReplacingBuffer buffer; private final Lock bufferLock; public ReplaceLineFeedReader(Reader src, String newLineSeparator) { ExceptionHelper.checkNotNullArgument(src, "src"); ExceptionHelper.checkNotNullArgument(newLineSeparator, "newLineSeparator"); this.src = src; this.buffer = new LfReplacingBuffer(newLineSeparator); this.bufferLock = new ReentrantLock(); } public static Reader replaceLfWithOsLineSeparator(Reader src) { return replaceLf(src, StringUtils.getOsLineSeparator()); } public static Reader replaceLf(Reader src, String newLineSeparator) { ExceptionHelper.checkNotNullArgument(src, "src"); ExceptionHelper.checkNotNullArgument(newLineSeparator, "newLineSeparator"); if ("\n".equals(newLineSeparator)) { return src; } else { return new ReplaceLineFeedReader(src, newLineSeparator); } } @Override public int read(char[] cbuf, int off, int len) throws IOException { // It is possible to optimize this method much more but there is // no practical reason to do so. int readCount = src.read(cbuf, off, len); bufferLock.lock(); try { if (readCount > 0) { buffer.appendChars(cbuf, off, readCount); } if (buffer.getCharCount() <= 0) { assert readCount <= 0; return readCount; } return buffer.moveFirstTo(cbuf, off, len); } finally { bufferLock.unlock(); } } @Override public void close() throws IOException { src.close(); } private static final class LfReplacingBuffer { private static final char[] NO_CHARS = new char[0]; private final char[] newLineSeparatorChars; private int charCount; private char[] chars; private char lastChar; public LfReplacingBuffer(String newLineSeparator) { this.newLineSeparatorChars = newLineSeparator.toCharArray(); this.charCount = 0; this.chars = NO_CHARS; this.lastChar = '\0'; } public int getCharCount() { return charCount; } private char[] getBuffer(int requiredExtraLength) { int requiredLength = charCount + requiredExtraLength; char[] result = chars; if (result.length >= requiredLength) { return result; } char[] newChars = new char[Math.max(requiredLength, 2 * result.length)]; System.arraycopy(chars, 0, newChars, 0, charCount); chars = newChars; return newChars; } private int getLfCount(char[] cbuf, int off, int len) { int result = 0; for (int i = off + len - 1; i >= off; i--) { if (cbuf[i] == '\n') { result++; } } return result; } public void appendChars(char[] cbuf, int off, int len) { int lfCount = getLfCount(cbuf, off, len); int extraChars = len + lfCount * (newLineSeparatorChars.length - 1); char[] buffer = getBuffer(extraChars); int destOffset = charCount; char prevChar = lastChar; int endSrcIndex = off + len; for (int i = off; i < endSrcIndex; i++) { char ch = cbuf[i]; if (prevChar != '\r' && ch == '\n') { System.arraycopy(newLineSeparatorChars, 0, buffer, destOffset, newLineSeparatorChars.length); destOffset += newLineSeparatorChars.length; } else { buffer[destOffset] = ch; destOffset++; } prevChar = ch; } lastChar = prevChar; charCount = destOffset; } public int moveFirstTo(char[] cbuf, int off, int len) { int resultLength = Math.min(len, charCount); System.arraycopy(chars, 0, cbuf, off, resultLength); System.arraycopy(chars, resultLength, chars, 0, charCount - resultLength); charCount -= resultLength; return resultLength; } } }