/*********************************************************************************
* TotalCross Software Development Kit *
* Copyright (C) 2000-2012 SuperWaba Ltda. *
* All Rights Reserved *
* *
* This library and virtual machine 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. *
* *
* This file is covered by the GNU LESSER GENERAL PUBLIC LICENSE VERSION 3.0 *
* A copy of this license is located in file license.txt at the root of this *
* SDK or can be downloaded here: *
* http://www.gnu.org/licenses/lgpl-3.0.txt *
* *
*********************************************************************************/
package totalcross.io;
import totalcross.io.device.PortConnector;
import totalcross.net.Socket;
import totalcross.sys.Settings;
import totalcross.sys.Vm;
/**
* Used to read lines ending with \r\n (enter/linefeed) or \n (linefeed) from a stream. Consecutive newlines are skipped.
* This class does not work well with multi-byte characters when the second byte contains the delimiter or enter/linefeed.
* <p>
* Here's a sample:
*
* <pre>
* LineReader reader = new LineReader(new File("text.txt",File.READ_WRITE));
* String line;
* while ((line = reader.readLine()) != null)
* {
* ... do whatever you want with the line.
* }
* </pre>
* Note that this class already uses a buffer for faster detection of the newline.
* Don't use LineReader with a BufferedStream, it's nonsense and it will throw a warning on the desktop.
*
* @author Guilherme Campos Hazan (guich)
* @since SuperWaba 5.12
*/
public class LineReader
{
protected Stream f;
protected ByteArrayStream readBuf = new ByteArrayStream(!Settings.onJavaSE ? 4096 : 32768); // guich@554_10: increase buffer size on applet
protected int ofs;
/**
* The number of times it tries to read more data if none is available.
* Defaults to 10 if the Stream is a Socket or a PortConnector; 0, otherwise.
*/
public int maxTries;
/** Set to true to apply a trim in the string that is returned.
* @since TotalCross 1.23
*/
public boolean doTrim; // guich@tc123_37
/** Set to true to receive empty lines (\r\n\r\n returns "","").
* @since TotalCross 1.3
*/
public boolean returnEmptyLines;
/**
* Constructs a new LineReader and sets maxTries accordingly to the type of
* class: 10 if its a Socket or a PortConnector, 0 otherwise.
*
* @throws totalcross.io.IOException
*/
public LineReader(Stream f) throws totalcross.io.IOException
{
this(f, null,0,0);
}
/**
* Constructs a new LineReader and sets maxTries accordingly to the type of
* class: 10 if its a Socket or a PortConnector; 0, otherwise.
* The given buffer contents are added to the internal buffer to start reading from them.
*
* @throws totalcross.io.IOException
* @since TotalCross 1.25
*/
public LineReader(Stream f, byte[] buffer, int start, int len) throws totalcross.io.IOException // guich@tc125_16
{
this.f = f;
if ((f instanceof Socket) || (f instanceof PortConnector))
maxTries = 10;
else
if (f instanceof BufferedStream && Settings.onJavaSE)
Vm.warning("Don't use "+getClass().getName()+" with a BufferedStream, because the LineReader class already uses a buffer for faster operation. Pass to LineReader's constructor the Stream you're using with the BufferedStream and discard the BufferedStream");
if (buffer != null && len > 0)
readBuf.writeBytes(buffer, start, len);
}
/** Change the initial Stream to the attached one, and fetches some data.
* Reusing a LineReader throught this method can preserve memory.
* @since TotalCross 1.23
*/
public void setStream(Stream f) throws IOException // guich@tc123_34
{
this.f = f;
readBuf.reset();
ofs = 0;
}
/** Returns the Stream attached to this LineReader.
* @since TotalCross 1.23
*/
public Stream getStream()
{
return f;
}
/**
* Move the buffer to the beginning, in order to preserve the bytes that were
* not read yet.
*/
protected void reuse()
{
int pos = readBuf.getPos();
int stillUnread = pos - ofs;
readBuf.skipBytes(-pos); // reset the pos to 0
readBuf.skipBytes(ofs); // move up to where we already read
readBuf.reuse();
readBuf.skipBytes(stillUnread);
// reset the offset
ofs = 0;
}
/**
* Read more bytes from the stream. If there's no data immediately to be
* read, waits 100ms and try again, up to <code>maxTries</code> times.
*
* @throws totalcross.io.IOException
*/
protected boolean readMore() throws totalcross.io.IOException
{
// read more bytes
if (readBuf.available() == 0)
readBuf.setSize(readBuf.getPos() + 1024, true); // grow the buffer if needed
int r = f.readBytes(readBuf.getBuffer(), readBuf.getPos(), readBuf.available());
if (r < 0) // try some more times until fail; maybe we're reading from a socket or serialport
for (int i = maxTries - 1; i >= 0; i--)
{
Vm.sleep(100);
r = f.readBytes(readBuf.getBuffer(), readBuf.getPos(), readBuf.available());
if (r > 0)
break;
}
if (r > 0)
readBuf.skipBytes(r); // mark the size, moving pos to the end.
return r > 0;
}
/**
* Returns the next line available on this stream or null if none. Empty
* lines are skipped by default.
*
* @throws totalcross.io.IOException
*/
public String readLine() throws totalcross.io.IOException
{
byte[] buf = readBuf.getBuffer();
int size = readBuf.getPos();
// skip starting control chars
if (!returnEmptyLines)
while (ofs < size && (buf[ofs] == '\n' || buf[ofs] == '\r')) // guich@tc123_31
ofs++;
else
{
if (ofs < size && buf[ofs] == '\r')
ofs++;
if (ofs < size && buf[ofs] == '\n')
ofs++;
}
while (true)
{
int i;
for (i = ofs; i < size; i++)
{
if (buf[i] == '\n') // found an enter? - guich@tc123_31
{
int len = i - ofs; // guich@552_28: verify if the length is not 0
if (i > 0 && buf[i-1] == '\r') // guich@tc123_47: is the previous character a \r?
len--;
if (len > 0 || returnEmptyLines)
{
int ii = ofs+len;
if (doTrim && (buf[ofs] <= ' ' || buf[ii-1] <= ' ')) // guich@tc123_37
{
while (ofs < ii && buf[ofs] <= ' ')
ofs++;
while (ii > ofs && buf[ii-1] <= ' ')
ii--;
len = ii - ofs;
}
// allocate the new String and return
String s = new String(buf, ofs, len);
ofs = i;
return s;
}
ofs++; // guich@552_28: strip the cr/lf from the string
}
}
// no enter found; fetch more data
int lastOfs = ofs;
reuse();
boolean foundMore = readMore();
size = readBuf.getPos(); // size had changed
buf = readBuf.getBuffer(); // buffer may have changed
if (!foundMore)
{
int len = i - lastOfs;
if (len > 0 || (foundMore && returnEmptyLines)) // any remaining string on the buffer?
{
ofs = len;
lastOfs = 0;
if (doTrim && len > 0 && (buf[0] <= ' ' || buf[len-1] <= ' ')) // guich@tc123_37
{
while (lastOfs < len && buf[lastOfs] <= ' ')
lastOfs++;
while (len > lastOfs && buf[len-1] <= ' ')
len--;
}
String s = new String(buf, lastOfs, len-lastOfs);
return s;
}
return null;
}
}
}
}