package org.reldb.dbrowser.ui.content.cmd;
import java.io.IOException;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.reldb.rel.client.connection.string.StringReceiverClient;
public abstract class ConcurrentStringReceiverClient {
private static final int threadLoadMax = 10;
private static class QueueEntry {
Exception exception;
String string;
QueueEntry(Exception exception) {this.exception = exception; this.string = null;}
QueueEntry(String string) {this.string = string; this.exception = null;}
QueueEntry() {this.string = null; this.exception = null;}
public String toString() {
if (string != null)
return string;
else if (exception != null)
return "Exception: " + exception.toString();
else
return "EOL";
}
public boolean isEOL() {
return (string == null && exception == null);
}
}
private StringReceiverClient connection;
private Display display;
private Composite parent;
private BlockingQueue<QueueEntry> rcache;
public ConcurrentStringReceiverClient(Composite parent, StringReceiverClient connection) {
this.parent = parent;
display = parent.getDisplay();
this.connection = connection;
}
private abstract class Runner {
private boolean running;
private int updateCount = 0;
private int updateMax = 0;
public Runner() {
rcache = new LinkedBlockingQueue<QueueEntry>();
running = true;
// UI updater thread
new Thread(new Runnable() {
@Override
public void run() {
updateCount = 0;
updateMax = 0;
while (running) {
// wait for data to show up
QueueEntry awaitedEntry;
try {
awaitedEntry = rcache.take();
} catch (InterruptedException e1) {
continue;
}
if (display.isDisposed()) {
running = false;
return;
}
display.syncExec(new Runnable() {
@Override
public void run() {
if (!parent.isDisposed()) {
try {
QueueEntry r = awaitedEntry;
int threadLoadCount = 0;
boolean nullUpdate = false;
do {
if (r.isEOL()) {
running = false;
finished();
return;
}
else if (r.string != null) {
received(r.string);
if (++updateCount > updateMax) {
// update every so often, so we can see what's going on
update();
updateCount = 0;
updateMax++;
nullUpdate = false;
} else
nullUpdate = true;
if (++threadLoadCount > threadLoadMax) {
// exit every so often, because staying in syncExec too long causes UI lag
break;
}
}
else if (r.exception != null) {
running = false;
received(r.exception);
finished();
return;
}
} while ((r = rcache.poll(10, TimeUnit.MILLISECONDS)) != null);
if (nullUpdate)
update();
} catch (InterruptedException e) {
finished();
return;
}
}
}
});
}
}
}).start();
// Query runner thread
new Thread(new Runnable() {
@Override
public void run() {
try {
doQuery();
} catch (IOException e) {
rcache.add(new QueueEntry(e));
}
}
}).start();
// Query processor thread
new Thread(new Runnable() {
@Override
public void run() {
try {
String r;
while ((r = connection.receive()) != null) {
rcache.add(new QueueEntry(r));
}
rcache.add(new QueueEntry());
} catch (IOException e) {
rcache.add(new QueueEntry(e));
}
}
}).start();
}
public abstract void doQuery() throws IOException;
}
public String getInitialServerResponse() {
try {
return connection.getServerAnnouncement();
} catch (IOException e) {
return "Unable to obtain server announcement.";
}
}
public void sendExecute(final String s) {
new Runner() {
public void doQuery() throws IOException {
connection.sendExecute(s);
}
};
}
public void sendEvaluate(String s) {
new Runner() {
public void doQuery() throws IOException {
connection.sendEvaluate(s);
}
};
}
public void reset() {
try {
connection.reset();
if (rcache == null)
return;
rcache.add(new QueueEntry("Cancel."));
rcache.add(new QueueEntry());
rcache.clear();
rcache.add(new QueueEntry("Cancel."));
rcache.add(new QueueEntry());
} catch (IOException e) {
System.out.println("ConcurrentStringReceiverClient: Exception during reset(): " + e);
}
}
public void close() {
try {
connection.reset();
connection.close();
} catch (IOException e) {
e.printStackTrace();
}
}
/** Override to be notified that a string was received. This will run in the SWT widget thread so is safe to update SWT widgets. */
public abstract void received(String s);
/** Override to be notified that an exception occurred. This will run in the SWT widget thread so is safe to update SWT widgets. */
public abstract void received(Exception e);
/** Override to be notified that processing has finished. This will run in the SWT widget thread so is safe to update SWT widgets. */
public abstract void finished();
/** Override to perform expensive periodic display updates after having received multiple strings. Safe to update SWT widgets. */
public abstract void update();
}