/*******************************************************************************
* Copyright (c) 2007, 2013 Wind River Systems, Inc. and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Wind River Systems - initial API and implementation
*******************************************************************************/
package org.eclipse.tcf.util;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.io.OutputStream;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import org.eclipse.tcf.protocol.IToken;
import org.eclipse.tcf.services.IFileSystem;
import org.eclipse.tcf.services.IFileSystem.FileSystemException;
import org.eclipse.tcf.services.IFileSystem.IFileHandle;
/**
* TCFFileOutputStream is high performance OutputStream implementation over TCF FileSystem service.
* The class uses write-back buffers to achieve maximum throughput.
*/
public final class TCFFileOutputStream extends OutputStream {
private static final int MAX_WRITE_BACK = 8;
private final IFileHandle handle;
private final IFileSystem fs;
private final int buf_size;
private final Set<IToken> write_commands = new HashSet<IToken>();
private final int[] dirty = new int[1];
private final byte[] buf;
private int buf_pos = 0;
private long offset = 0;
private IOException flush_error;
private boolean closed;
public TCFFileOutputStream(IFileHandle handle) {
this(handle, 0x1000);
}
public TCFFileOutputStream(IFileHandle handle, int buf_size) {
this.handle = handle;
this.fs = handle.getService();
this.buf_size = buf_size;
buf = new byte[buf_size];
}
@Override
public synchronized void write(int b) throws IOException {
if (closed) throw new IOException("Stream is closed");
if (buf_pos == buf_size) flush();
buf[buf_pos++] = (byte)b;
}
@Override
public void write(byte b[], int off, int len) throws IOException {
if (len == 0) return;
if (b == null) throw new NullPointerException();
if (off < 0 || off > b.length || len < 0 ||
off + len > b.length || off + len < 0)
throw new IndexOutOfBoundsException();
while (len > 0) {
if (buf_pos == buf_size) flush();
if (buf_pos == 0 && len > buf_size) {
flush(b, off, len);
return;
}
int n = buf_size - buf_pos;
if (len < n) n = len;
System.arraycopy(b, off, buf, buf_pos, n);
off += n;
len -= n;
buf_pos += n;
}
}
@Override
public synchronized void flush() throws IOException {
if (buf_pos == 0) return;
flush(buf, 0, buf_pos);
buf_pos = 0;
}
private void flush(final byte[] buf, final int off, final int len) throws IOException {
synchronized (dirty) {
if (flush_error != null) throw flush_error;
while (dirty[0] >= MAX_WRITE_BACK) {
try {
dirty.wait();
}
catch (InterruptedException e) {
throw new InterruptedIOException();
}
}
}
new TCFTask<Object>() {
public void run() {
write_commands.add(fs.write(handle, offset, buf, off, len, new IFileSystem.DoneWrite() {
public void doneWrite(IToken token, FileSystemException error) {
assert write_commands.contains(token);
write_commands.remove(token);
if (error != null) {
for (Iterator<IToken> i = write_commands.iterator(); i.hasNext();) {
if (i.next().cancel()) i.remove();
}
}
synchronized (dirty) {
if (error != null && flush_error == null) flush_error = error;
dirty[0] = write_commands.size();
dirty.notifyAll();
}
}
}));
synchronized (dirty) {
dirty[0] = write_commands.size();
}
done(this);
}
}.getIO();
offset += len;
}
@Override
public synchronized void close() throws IOException {
if (closed) return;
flush();
synchronized (dirty) {
while (dirty[0] > 0) {
try {
dirty.wait();
}
catch (InterruptedException e) {
throw new InterruptedIOException();
}
}
}
new TCFTask<Object>() {
public void run() {
fs.close(handle, new IFileSystem.DoneClose() {
public void doneClose(IToken token, FileSystemException error) {
if (error != null) error(error);
else done(this);
}
});
}
}.getIO();
closed = true;
}
}