/**
Copyright (C) 2012 Delcyon, Inc.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.delcyon.capo.datastream;
import java.io.ByteArrayOutputStream;
import java.io.FilterOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Vector;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @author jeremiah
*
*/
public class RegexFilterOutputStream extends FilterOutputStream
{
private ByteArrayOutputStream buffer = new ByteArrayOutputStream();
private Vector<Integer> lineVector = new Vector<Integer>();
private Pattern regex;
private String replacement;
private int lineBufferCount;
private StringBuilder stringBuilder = new StringBuilder();
private boolean isCloseing = false;
/**
* remember, this filter will run BEFORE the outputStream parameter does.
* So always make sure that you reverse the list of filters before creating them.
* @param outputStream
* @param regex
* @param replacement
* @param lineBufferCount how many lines we should have buffered before we start searching things.
*/
public RegexFilterOutputStream(OutputStream outputStream, String regex,String replacement,int lineBufferCount)
{
super(outputStream);
if (lineBufferCount == 1)
{
this.regex = Pattern.compile(regex);
}
else
{
//if we are buffering more than one line at a time, then enable multi-line support, otherwise there is no point.
this.regex = Pattern.compile(regex,Pattern.MULTILINE);
}
this.replacement = replacement;
this.lineBufferCount = lineBufferCount;
}
@Override
public void close() throws IOException
{
isCloseing = true;
processBuffer();
out.write(stringBuilder.toString().getBytes());
out.flush();
super.close();
}
/**
* override write method
*/
@Override
public void write(int b) throws IOException {
if (b == '\n')
{
//got new line
buffer.write(b);
processBuffer();
}
else
{
buffer.write(b);
}
}
private void processBuffer() throws IOException
{
String workingLine = buffer.toString();
buffer.reset();
lineVector.add(workingLine.length());
stringBuilder.append(workingLine);
//slid the buffer window
if (lineBufferCount != 0 && lineVector.size() > lineBufferCount)
{
//release head of vector
workingLine = stringBuilder.substring(0, lineVector.remove(0));
stringBuilder.delete(0, workingLine.length());
out.write(workingLine.getBytes());
}
//make sure the buffer is full before running any tests, I think this will result in more expected output than any other solution
//the corner case is when the stream is closing, the buffer may not be full, but we should run it anyway
if (isCloseing == false && lineVector.size() != lineBufferCount)
{
return;
}
//this does not do in line replacement, but instead returns a result
//so we need to determine if something changed
//and how that impacts our lineLengthVector
//should we just flush the buffers and start over?
//or should we do some serious math, and resize everything accordingly?
//being a lazy programmer, I've opted for flushing everything on a find/replace
//you can always set the window size to 0 if you want perfect multi-line regex
Matcher matcher = regex.matcher(stringBuilder);//replaceAll(replacement);
boolean foundMatch = matcher.find();
if (foundMatch) {
StringBuffer stringBuffer = new StringBuffer();
while(foundMatch == true)
{
matcher.appendReplacement(stringBuffer, replacement);
foundMatch = matcher.find();
}
matcher.appendTail(stringBuffer);
out.write(stringBuffer.toString().getBytes());
stringBuilder = new StringBuilder();
lineVector.clear();
}
}
/**
* override write method
*/
@Override
public void write(byte[] data, int offset, int length) throws IOException
{
for (int i = offset; i < offset + length; i++)
{
this.write(data[i]);
}
}
/**
* override write method
*/
@Override
public void write(byte[] b) throws IOException
{
write(b, 0, b.length);
}
}