/*
* (c) Copyright 2009 Hewlett-Packard Development Company, LP
* All rights reserved.
* [See end of file]
*/
package io;
import java.io.IOException;
import java.io.OutputStream;
import java.io.Writer;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.WritableByteChannel;
import lib.Bytes;
import lib.Sink;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.hp.hpl.jena.sparql.util.ALog;
import lib.AtlasException;
/** Buffering writer (and own methods which do not throw checked exceptions).
* Only support UTF-8.
*
* The java.io classes have hidden synchronization so in some very critical
* situations, this can be expensive (such situations are not common).
* This class generalises the notion of destination via the Sink
* abstraction (block output based on ByteBuffers).
*
* This class is not thread safe.
* @see PeekReader
*/
public final class BufferingWriter extends Writer
{
private static Logger log = LoggerFactory.getLogger(BufferingWriter.class) ;
// ComOpposite of PeekReader.
// As usualy, the java.io classes have hidden synchronization
// so in some very critical situations, this can be expensive.
// Also, this class generalises the notion of destination via the Sink
// abstraction (block outout based on ByteBuffers).
// UTF-8 notes:
// In the very worse case, one codepoint is 4 bytes. That's very unlikely.
// http://www.unicode.org/versions/Unicode5.0.0/ch03.pdf#G7404 Table 3-7
// We assume worse case (alternative would be encode to a temp buffer
// to find the length). The effect is just that not quite 8K bytes will
// be sent, assumning that typical items are in the range 0-100 chars.
// Default sizes
private static final int SIZE = 8*1024 ; // Unit size in bytes.
private static final int BLOB_SIZE = SIZE/2 ; // Large object size, worse case, bytes
// Sizes for this instance
private final int blockSize ;
private final int blobSize ;
private ByteBuffer buffer = ByteBuffer.allocate(SIZE) ;
private Sink out ;
private char[] oneChar = new char[1] ;
/** Convenience factor operation to output to a WritableByteChannel */
public static BufferingWriter create(WritableByteChannel out)
{
return create(out, SIZE) ;
}
/** Convenience factor operation to output to a WritableByteChannel */
public static BufferingWriter create(WritableByteChannel out, int size)
{
return new BufferingWriter(new SinkChannel(out), size, size/2) ;
}
/** Create a buffering output stream of charcaters to a {@link lib.Sink} */
public BufferingWriter(Sink sink) { this(sink, SIZE, BLOB_SIZE) ; }
/** Create a buffering output stream of charcaters to a {@link lib.Sink} */
public BufferingWriter(Sink sink, int size, int blobSize)
{
this.out = sink ;
this.blockSize = size ;
this.blobSize = blobSize ;
}
/** Output characters (The String class implements CharSequence)*/
public void output(CharSequence string)
{
int space = string.length() ; // Chars
space = 4*space ; // Very worst case bytes.
boolean largeBlob = (space > blobSize) ;
// There is no space or too big
if ( largeBlob || (blockSize-bufferSize()) < space )
flush() ;
// If too big, do directly.
if ( largeBlob /* too big */ )
{
ByteBuffer bb = ByteBuffer.allocate(space) ;
Bytes.toByteBuffer(string, bb) ;
send(out, bb) ;
return ;
}
// This always sets "end of input" in the encoder which is
// fine if we assume no spanning char sequnces across strings or other
// units written to this writer.
Bytes.toByteBuffer(string, buffer) ;
}
private int bufferSize()
{
return buffer.position() ;
}
/** Output an array of characters */
public void output(char chars[])
{
output(CharBuffer.wrap(chars)) ;
}
/** Output an array of characters
*
* @param chars Characters
* @param start Start (inclusive)
* @param finish Finish (exclusive)
*/
public void output(char chars[],int start, int finish)
{
output(CharBuffer.wrap(chars, start, finish)) ;
}
/** Output a single character */
public void output(int ch)
{
oneChar[0] = (char)ch ;
output(oneChar) ;
oneChar[0] = 0;
}
private static void send(Sink out, ByteBuffer bb)
{
if ( log.isDebugEnabled() )
log.debug("send: "+bb) ;
if ( out == null )
{
System.out.write(bb.array(), 0, bb.position()) ;
try { System.out.flush() ; } catch (Throwable th) {}
return ;
}
if ( bb.position() == 0 )
ALog.warn(BufferingWriter.class, "Sending zero bytes") ;
bb.flip() ;
out.send(bb) ;
}
private static void exception(IOException ex)
{ throw new AtlasException(ex) ; }
// ---- Writer
@Override
public void close()
{
out.close() ;
}
@Override
public void flush()
{
if ( bufferSize() > 0 )
{
send(out, buffer) ;
buffer.clear() ;
out.flush() ;
}
}
// Good, old fashioned macros would be nice.
public static class SinkChannel implements Sink
{
private WritableByteChannel out ;
public SinkChannel(WritableByteChannel out)
{ this.out = out ; }
@Override
public void send(ByteBuffer bb)
{ try { out.write(bb) ; } catch (IOException ex) { exception(ex) ; } }
@Override
public void close()
{ try { out.close() ; } catch (IOException ex) { exception(ex) ; } }
@Override
public void flush()
{ }
}
public static class SinkBuffer implements Sink
{
private ByteBuffer out ;
public SinkBuffer(ByteBuffer out)
{ this.out = out ; }
@Override
public void send(ByteBuffer bb)
{ out.put(bb) ; }
@Override
public void close()
{ }
@Override
public void flush()
{ }
}
public static class SinkOutputStream implements Sink
{
private OutputStream out ;
public SinkOutputStream(OutputStream out)
{ this.out = out ; }
@Override
public void send(ByteBuffer bb)
{ try { out.write(bb.array(), 0, bb.limit()) ; } catch (IOException ex) { exception(ex) ; } }
@Override
public void close()
{ try { out.close(); } catch (IOException ex) { exception(ex) ; } }
@Override
public void flush()
{ try { out.flush() ; } catch (IOException ex) { exception(ex) ; } }
}
@Override
public void write(char[] cbuf, int off, int len) throws IOException
{
output(CharBuffer.wrap(cbuf, off, len)) ;
}
@Override
public void write(char[] cbuf) throws IOException
{
write(cbuf, 0, cbuf.length) ;
}
// @Override
// public void write(String string, int off, int len) throws IOException
// { }
@Override
public void write(String string) throws IOException
{ output(string) ; }
@Override
public void write(int ch) throws IOException
{ output(ch) ; }
}
/*
* (c) Copyright 2009 Hewlett-Packard Development Company, LP
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/