class TransactionAwareBufferedWriter extends Writer {

	private final Object bufferKey;

	private final Object closeKey;

	private final FileChannel channel;

	private final Runnable closeCallback;

	// default encoding for writing to output files - set to UTF-8.
	private static final String DEFAULT_CHARSET = "UTF-8";

	private String encoding = DEFAULT_CHARSET;

	private boolean forceSync = false;

	/**
	 * Create a new instance with the underlying file channel provided, and a callback to
	 * execute on close. The callback should clean up related resources like output
	 * streams or channels.
	 * @param channel channel used to do the actual file IO
	 * @param closeCallback callback to execute on close
	 */
	public TransactionAwareBufferedWriter(FileChannel channel, Runnable closeCallback) {
		super();
		this.channel = channel;
		this.closeCallback = closeCallback;
		this.bufferKey = new Object();
		this.closeKey = new Object();
	}

	public void setEncoding(String encoding) {
		this.encoding = encoding;
	}

	/**
	 * Flag to indicate that changes should be force-synced to disk on flush. Defaults to
	 * false, which means that even with a local disk changes could be lost if the OS
	 * crashes in between a write and a cache flush. Setting to true may result in slower
	 * performance for usage patterns involving many frequent writes.
	 * @param forceSync the flag value to set
	 */
	public void setForceSync(boolean forceSync) {
		this.forceSync = forceSync;
	}

	/**
	 * @return the current buffer
	 */
	private StringBuilder getCurrentBuffer() {

		if (!TransactionSynchronizationManager.hasResource(bufferKey)) {

			TransactionSynchronizationManager.bindResource(bufferKey, new StringBuilder());

			TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
				@Override
				public void afterCompletion(int status) {
					clear();
				}

				@Override
				public void beforeCommit(boolean readOnly) {
					try {
						if (!readOnly) {
							complete();
						}
					}
					catch (IOException e) {
						throw new FlushFailedException("Could not write to output buffer", e);
					}
				}

				private void complete() throws IOException {
					StringBuilder buffer = (StringBuilder) TransactionSynchronizationManager.getResource(bufferKey);
					if (buffer != null) {
						String string = buffer.toString();
						byte[] bytes = string.getBytes(encoding);
						int bufferLength = bytes.length;
						ByteBuffer bb = ByteBuffer.wrap(bytes);
						int bytesWritten = channel.write(bb);
						if (bytesWritten != bufferLength) {
							throw new IOException("All bytes to be written were not successfully written");
						}
						if (forceSync) {
							channel.force(false);
						}
						if (TransactionSynchronizationManager.hasResource(closeKey)) {
							closeCallback.run();
						}
					}
				}

				private void clear() {
					if (TransactionSynchronizationManager.hasResource(bufferKey)) {
						TransactionSynchronizationManager.unbindResource(bufferKey);
					}
					if (TransactionSynchronizationManager.hasResource(closeKey)) {
						TransactionSynchronizationManager.unbindResource(closeKey);
					}
				}

			});

		}

		return (StringBuilder) TransactionSynchronizationManager.getResource(bufferKey);

	}

	/**
	 * Convenience method for clients to determine if there is any unflushed data.
	 * @return the current size (in bytes) of unflushed buffered data
	 */
	public long getBufferSize() {<FILL_FUNCTION_BODY>}

	/**
	 * @return true if the actual transaction is active, false otherwise
	 */
	private boolean transactionActive() {
		return TransactionSynchronizationManager.isActualTransactionActive();
	}

	@Override
	public void close() throws IOException {
		if (transactionActive()) {
			if (getCurrentBuffer().length() > 0) {
				TransactionSynchronizationManager.bindResource(closeKey, Boolean.TRUE);
			}
			return;
		}
		closeCallback.run();
	}

	@Override
	public void flush() throws IOException {
		if (!transactionActive() && forceSync) {
			channel.force(false);
		}
	}

	@Override
	public void write(char[] cbuf, int off, int len) throws IOException {

		if (!transactionActive()) {
			byte[] bytes = new String(cbuf, off, len).getBytes(encoding);
			int length = bytes.length;
			ByteBuffer bb = ByteBuffer.wrap(bytes);
			int bytesWritten = channel.write(bb);
			if (bytesWritten != length) {
				throw new IOException(
						"Unable to write all data.  Bytes to write: " + len + ".  Bytes written: " + bytesWritten);
			}
			return;
		}

		StringBuilder buffer = getCurrentBuffer();
		buffer.append(cbuf, off, len);
	}

	@Override
	public void write(String str, int off, int len) throws IOException {

		if (!transactionActive()) {
			byte[] bytes = str.substring(off, off + len).getBytes(encoding);
			int length = bytes.length;
			ByteBuffer bb = ByteBuffer.wrap(bytes);
			int bytesWritten = channel.write(bb);
			if (bytesWritten != length) {
				throw new IOException(
						"Unable to write all data.  Bytes to write: " + len + ".  Bytes written: " + bytesWritten);
			}
			return;
		}

		StringBuilder buffer = getCurrentBuffer();
		buffer.append(str, off, off + len);
	}

}

		if (!transactionActive()) {
			return 0L;
		}
		try {
			return getCurrentBuffer().toString().getBytes(encoding).length;
		}
		catch (UnsupportedEncodingException e) {
			throw new WriteFailedException(
					"Could not determine buffer size because of unsupported encoding: " + encoding, e);
		}
	
<methods>public java.io.Writer append(java.lang.CharSequence) throws java.io.IOException,public java.io.Writer append(char) throws java.io.IOException,public java.io.Writer append(java.lang.CharSequence, int, int) throws java.io.IOException,public abstract void close() throws java.io.IOException,public abstract void flush() throws java.io.IOException,public static java.io.Writer nullWriter() ,public void write(int) throws java.io.IOException,public void write(char[]) throws java.io.IOException,public void write(java.lang.String) throws java.io.IOException,public abstract void write(char[], int, int) throws java.io.IOException,public void write(java.lang.String, int, int) throws java.io.IOException<variables>private static final int WRITE_BUFFER_SIZE,protected java.lang.Object lock,private char[] writeBuffer