/*
* $Id$
*
* Copyright (c) 2008-2009 by Joel Uckelman
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License (LGPL) as published by the Free Software Foundation.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, copies are available
* at http://www.opensource.org.
*/
package VASSAL.launch;
import VASSAL.tools.concurrent.listener.EventListener;
import VASSAL.tools.image.tilecache.ZipFileImageTiler;
/**
* A state machine for parsing the output of {@link ZipFileImageTiler}.
*
* @since 3.2.0
* @author Joel Uckelman
*/
class TileProgressPumpStateMachine {
protected final EventListener<String> nameListener;
protected final EventListener<Integer> progListener;
/**
* Creates a <code>TileProgressPumpStateMachine</code>.
*
* @param nameListener the listener for new filename events
* @param progListener the listener for progress events
*/
public TileProgressPumpStateMachine(EventListener<String> nameListener,
EventListener<Integer> progListener) {
this.nameListener = nameListener;
this.progListener = progListener;
}
public static final int INIT = 0;
public static final int NAME = 1;
public static final int NAME_LF = 2;
public static final int DOTS = 3;
public static final int DOTS_LF = 4;
public static final int DONE = 5;
protected void appendName(StringBuilder sb, byte[] buf, int beg, int end) {
sb.append(new String(buf, beg, end-beg));
}
protected boolean hasName(StringBuilder sb) {
return sb.length() > 0;
}
protected void sendName(StringBuilder sb) {
nameListener.receive(this, sb.toString());
sb.setLength(0);
}
protected void sendProgress(int prog) {
progListener.receive(this, prog);
}
protected int[] runName(byte[] buf, int beg, int end, StringBuilder sb) {
// look for end of line
for (int pos = beg; pos < end; ++pos) {
if (buf[pos] == '\r' || buf[pos] == '\n') {
// found the end of line
// terminate if the buffer is empty
if (pos == beg && !hasName(sb)) {
return new int[] { DONE, end };
}
// otherwise, send the buffer up to this position as the filename
appendName(sb, buf, beg, pos);
sendName(sb);
// found a carriage return
if (buf[pos] == '\r') {
// now look for a linefeed
return new int[] { NAME_LF, pos+1 };
}
else {
// now look for dots
return new int[] { DOTS, pos+1 };
}
// found a regular character, keep looking
}
}
// exhausted the buffer without finding end of line
// store the pratial filename we've read
appendName(sb, buf, beg, end);
// continue looking for end of line
return new int[] { NAME, end };
}
protected int[] runNameLF(byte[] buf, int beg, int end, StringBuilder sb) {
// look for a linefeed
switch (buf[beg]) {
case '\n':
// found a linefeed
// now look for dots
return new int[] { DOTS, beg+1 };
default:
// found something else, protocol violation
throw new IllegalStateException(
"found '" + buf[beg] + "', expecting '\\n'");
}
}
protected int[] runDots(byte[] buf, int beg, int end, StringBuilder sb) {
// look for end of line
for (int pos = beg; pos < end; ++pos) {
switch (buf[pos]) {
case '\r':
case '\n':
// found the end of line
// send the buffer up to this position as the progress
sendProgress(pos-beg);
// found a carriage return
if (buf[pos] == '\r') {
// now look for a linefeed
return new int[] { DOTS_LF, pos+1 };
}
else {
// now look for filename
return new int[] { NAME, pos+1 };
}
case '.':
// found a progress dot, keep looking
break;
default:
// found somethine else, protocol violation
throw new IllegalStateException(
"found '" + buf[pos] + "', expecting '.'");
}
}
// exhausted the buffer without finding end of line
// send the progress to this point
sendProgress(end-beg);
// continue looking for end of line
return new int[] { DOTS, end };
}
protected int[] runDotsLF(byte[] buf, int beg, int end, StringBuilder sb) {
// look for a linefeed
switch (buf[beg]) {
case '\n':
// found a linefeed
// now look for filename
return new int[] { NAME, beg+1 };
default:
// found something else, protocol violation
throw new IllegalStateException(
"found '" + buf[beg] + "', expecting '\\n'");
}
}
/**
* Run the state machine to the end of the buffer.
*
* @param state the current state
* @param buf the byte buffer to read
* @param beg the beginning position in the buffer (inclusive)
* @param end the the ending position in the buffer (exclusive)
* @param sb the string builder for holding name
*/
public int run(int state, byte[] buf, int beg, int end, StringBuilder sb) {
if (state == DONE) {
throw new IllegalArgumentException("DONE is terminal");
}
if (state == INIT) {
state = NAME;
}
while (beg < end) {
int[] result;
switch (state) {
case NAME: result = runName( buf, beg, end, sb); break;
case NAME_LF: result = runNameLF(buf, beg, end, sb); break;
case DOTS: result = runDots( buf, beg, end, sb); break;
case DOTS_LF: result = runDotsLF(buf, beg, end, sb); break;
default:
// should never happen
throw new IllegalStateException("state == " + state);
}
state = result[0];
beg = result[1];
}
return state;
}
}