package net.contrapunctus.rngzip;
import java.util.List;
import java.io.IOException;
import java.io.PrintStream;
import net.contrapunctus.rngzip.io.ChoiceEncoder;
import net.contrapunctus.rngzip.io.RNGZOutputInterface;
class TentativeOutput
implements RNGZOutputInterface,
Comparable<TentativeOutput>
{
private RNGZOutputInterface out;
private boolean tentative;
private Event history;
private static final boolean DEBUG = false;
private static final PrintStream dbg = System.err;
public TentativeOutput(RNGZOutputInterface out)
{
assert out != null;
this.out = out;
this.tentative = false;
this.history = null;
assert invariants();
}
private TentativeOutput(RNGZOutputInterface out, Event history)
{
assert out != null;
this.out = out;
this.tentative = true;
this.history = history;
assert invariants();
}
private boolean invariants()
{
assert out != null : "This stream may have already been aborted.";
assert history == null || tentative : "Non-tentative stream has a history.";
assert !tentative || history != null : "Tentative stream lacks a history.";
for(Event e = history; e != null; e = e.prev) {
assert e.next == null : "Part of history has already been committed.";
}
return true;
}
public ChoiceEncoder makeChoiceEncoder(int limit, Object id)
{
return out.makeChoiceEncoder(limit, id);
}
public TentativeOutput fork()
{
assert invariants();
tentative = true;
if(history == null) {
history = new Sentinel();
}
return new TentativeOutput(out, history);
}
public void writeChoice(ChoiceEncoder ce, int choice) throws IOException
{
assert invariants();
if(tentative) {
if(DEBUG) {
dbg.printf("tentative choice: %s at %s on %s%n", choice, ce, this);
}
history = new ChoiceEvent(ce, choice, history);
}
else {
out.writeChoice(ce, choice);
}
}
public void writeContent(List<String> path, String s) throws IOException
{
assert invariants();
if(tentative) {
history = new ContentStrEvent(path, s, history);
}
else {
out.writeContent(path, s);
}
}
public void writeContent(List<String> path, char[] buf, int start, int length)
throws IOException
{
assert invariants();
if(tentative) {
history = new ContentBufEvent(path, buf, start, length, history);
}
else {
out.writeContent(path, buf, start, length);
}
}
public void close() throws IOException
{
assert !tentative;
assert invariants();
out.close();
}
public void flush() throws IOException
{
assert !tentative;
assert invariants();
out.flush();
}
public int compareTo(TentativeOutput that)
{
int this_mag = this.history == null? 0 : this.history.magnitude;
int that_mag = that.history == null? 0 : that.history.magnitude;
return this_mag - that_mag;
}
private abstract class Event
{
private Event prev;
private Event next = null;
protected int magnitude = 0;
private Event(Event prev)
{
this.prev = prev;
if(prev != null) magnitude = prev.magnitude;
}
protected abstract void playback(RNGZOutputInterface out) throws IOException;
}
private class Sentinel extends Event
{
private Sentinel() { super(null); }
protected void playback(RNGZOutputInterface out) { }
}
private class ChoiceEvent extends Event
{
private ChoiceEncoder enc;
private int ch;
private ChoiceEvent(ChoiceEncoder enc, int ch, Event prev)
{
super(prev);
this.enc = enc;
this.ch = ch;
magnitude ++;
}
protected void playback(RNGZOutputInterface out) throws IOException
{
out.writeChoice(enc, ch);
}
}
private class ContentStrEvent extends Event
{
private List<String> path;
private String str;
private ContentStrEvent(List<String> path, String str, Event prev)
{
super(prev);
this.path = path;
this.str = str;
magnitude += str.length();
}
protected void playback(RNGZOutputInterface out) throws IOException
{
out.writeContent(path, str);
}
}
private class ContentBufEvent extends Event
{
private List<String> path;
private char[] buf;
private int start, length;
private ContentBufEvent(List<String> path, char[] buf,
int start, int length, Event prev)
{
super(prev);
this.path = path;
this.buf = buf;
this.start = start;
this.length = length;
magnitude += length;
}
protected void playback(RNGZOutputInterface out) throws IOException
{
out.writeContent(path, buf, start, length);
}
}
public void commit() throws IOException
{
assert invariants();
if(tentative) {
/* History is recorded in reverse order, with prev links only.
Go through and fill in the next links, so we can read it
forward. */
Event start = null;
for(Event e = history; e != null; e = e.prev) {
/* If any of the next pointers are already set, another
stream must have committed that event already. */
if(e.prev == null) start = e;
else e.prev.next = e;
}
/* Now we can play it back. */
for(Event e = start; e != null; e = e.next) {
e.playback(out);
}
tentative = false;
history = null;
assert invariants();
}
}
public void abort()
{
assert tentative;
tentative = false;
history = null;
out = null;
/* invariant will no longer hold; this object should
never be used again. */
}
// public static void main(String[] args) throws IOException
// {
// TentativeOutput a, b, c, d, e, f;
// a = new TentativeOutput(new VerboseOutput(System.out));
//
// a.writeContent(null, "Hello");
// b = a.fork();
// a.writeContent(null, "there");
// b.writeContent(null, "world");
// c = b.fork();
// b.writeContent(null, "one");
// c.writeContent(null, "two");
//
// c.abort();
//
// d = a.fork();
// e = b.fork();
//
// a.writeContent(null, "dar");
// b.writeContent(null, "kd");
// d.writeContent(null, "ani");
// e.writeContent(null, "anne");
//
// a.commit();
//
// }
}