package org.albite.io;
import java.io.DataInput;
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UTFDataFormatException;
import java.util.Vector;
import javax.microedition.io.Connection;
import javax.microedition.io.Connector;
import javax.microedition.io.file.FileConnection;
import org.albite.albite.AlbiteMIDlet;
import org.albite.lang.TextTools;
/**
* Implements random reading from a
* {@link javax.microedition.io.file.FileConnection FileConnection}
*
* The essential methods are {@link RandomReadingFile#seek(int)
* seek(int position)}
* and {@link RandomReadingFile#getPointer() getPointer()}.
*
* This class also re-implements all the methods from
* {@link java.io.InputStream}
*
* All other methods are only wrappings around
* the underlying {@link java.io.DataInputStream} or <code>FileConnection</code>
*
* @author Svetlin Ankov <galileostudios@gmail.com>
* @version 1.0.0
*/
public class RandomReadingFile extends InputStream
implements DataInput, Connection {
private FileConnection file;
private DataInputStream in;
/**
* The pointer is set to {@link java.lang.Integer#MAX_VALUE} in order to
* be able to initialize the {@link java.io.DataInputStream} simply by
* using {@link RandomReadingFile#seek(int) seek(0)}
*/
private int pointer = Integer.MAX_VALUE;
private RandomReadingFile() {}
/**
* Creates a new RandomReadingFile from a valid file URL.
*
* @param url The URL of the file that is being opened.
* @throws IOException
*/
public RandomReadingFile(String url) throws IOException {
//#debug
AlbiteMIDlet.LOGGER.log("Opening RRF: [" + url + "]");
file = (FileConnection) Connector.open(url, Connector.READ);
if (file.isDirectory() || !file.exists()) {
//#mdebug
if (file.isDirectory()) {
AlbiteMIDlet.LOGGER.logError("RRF: File `" + url + "` is a directory");
} else {
AlbiteMIDlet.LOGGER.logError("RRF: File `" + url + "` not found");
}
//#enddebug
throw new IOException("File not found by RRF");
}
seek(0);
}
/**
* @return the size of the file
* @throws ConnectionClosedException if the file is closed.
* @throws java.lang.SecurityException if the security of the
* application does not have read
* access for the file
*/
public final int length() throws IOException {
return (int) file.fileSize();
}
public final String getName() {
return file.getName();
}
public final String getURL() {
return file.getURL();
}
public final String getPath() {
return file.getPath();
}
/**
* Seeks to the specified position
*
* @param position
* @throws IllegalArgumentException if the <code>position</code> argument
* is wrong, i.e.:<p />
* <code>position < 0</code><p />
* or<p />
* {@link RandomReadingFile#length()} <= position</code>
* @throws IOException if an IOException occurred
*/
public final void seek(final int position) throws IOException {
if (position == pointer) {
return;
}
if (position < 0 || position >= length()) {
throw new IllegalArgumentException(
"Trying to seek outside file's contents");
}
final int pos;
if (position < pointer) {
/*
* Must go back, i.e. it's necessary to reopen the input stream
*/
/*
* Close input before reopening it
*/
if (in != null) {
in.close();
}
in = file.openDataInputStream();
pos = position;
} else {
pos = position - pointer;
}
skipBytes(pos);
pointer = position;
}
public final long skip(long n) throws IOException {
return skipBytes((int) n);
}
public final int skipBytes(final int n) throws IOException {
int skipped = in.skipBytes(n);
pointer += skipped;
return skipped;
}
public final boolean readBoolean() throws IOException {
pointer++;
return in.readBoolean();
}
public final byte readByte() throws IOException {
pointer++;
return in.readByte();
}
public final char readChar() throws IOException {
pointer += 2;
return in.readChar();
}
public final double readDouble() throws IOException {
pointer += 8;
return in.readDouble();
}
public final float readFloat() throws IOException {
pointer +=4;
return in.readFloat();
}
public final int readInt() throws IOException {
pointer += 4;
return in.readInt();
}
public final int readUnsignedByte() throws IOException {
pointer++;
return in.readUnsignedByte();
}
public final int readUnsignedShort() throws IOException {
pointer += 2;
return in.readUnsignedShort();
}
public final String readUTF() throws IOException {
return new String(readUTFchars());
}
public final char[] readUTFchars() throws IOException {
/*
* There is no need to advance the pointer manually,
* as the pointer is advanced automatically by
* readUnsignedShort() and readFully methods
* and this method interacts with the input stream
* only through them
*/
int utflen = readUnsignedShort();
StringBuffer str = new StringBuffer(utflen);
byte bytearr[] = new byte[utflen];
int c, char2, char3;
int count = 0;
readFully(bytearr, 0, utflen);
while (count < utflen) {
c = (int) bytearr[count] & 0xff;
switch (c >> 4) {
case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7:
/* 0xxxxxxx*/
count++;
str.append( (char)c);
break;
case 12: case 13:
/* 110x xxxx 10xx xxxx*/
count += 2;
if (count > utflen) {
throw new UTFDataFormatException();
}
char2 = (int) bytearr[count-1];
if ((char2 & 0xC0) != 0x80) {
throw new UTFDataFormatException();
}
str.append((char)(((c & 0x1F) << 6) | (char2 & 0x3F)));
break;
case 14:
/* 1110 xxxx 10xx xxxx 10xx xxxx */
count += 3;
if (count > utflen) {
throw new UTFDataFormatException();
}
char2 = (int) bytearr[count-2];
char3 = (int) bytearr[count-1];
if (((char2 & 0xC0) != 0x80) || ((char3 & 0xC0) != 0x80)) {
throw new UTFDataFormatException();
}
str.append((char)(((c & 0x0F) << 12) |
((char2 & 0x3F) << 6) |
( char3 & 0x3F )));
break;
default:
/* 10xx xxxx, 1111 xxxx */
throw new UTFDataFormatException();
}
}
// The number of chars produced may be less than utflen
final char[] result = new char[str.length()];
str.getChars(0, str.length(), result, 0);
return result;
}
public final long readLong() throws IOException {
pointer += 8;
return in.readLong();
}
public final short readShort() throws IOException {
pointer += 2;
return in.readShort();
}
public final int read(byte[] b) throws IOException {
return read(b, 0, b.length);
}
public final int read(byte[] b, int off, int len) throws IOException {
int count = in.read(b, off, len);
if (count > 0) {
pointer += count;
}
return count;
}
public final int read() throws IOException {
int read = in.read();
if (read >= 0) {
pointer++;
}
return read;
}
public final void readFully(byte[] b) throws IOException {
readFully(b, 0, b.length);
}
public final void readFully(byte b[], int off, int len)
throws IOException {
pointer += len;
in.readFully(b, off, len);
}
/**
* Closes the file and the underlying
* {@link javax.microedition.io.file.FileConnection}
* @throws IOException
*/
public final void close() throws IOException {
in.close();
file.close();
}
/**
* Returns the current position of the reading pointer in the file
* @return the current position of the reading pointer in the file
*/
public final int getPointer() {
return pointer;
}
public static String getPathFromURL(final String url) {
int i = url.lastIndexOf('/');
if (i == -1) {
i = url.lastIndexOf('\\');
}
if (i >= 0) {
return url.substring(0, i + 1);
}
return "";
}
public static String relativeToAbsoluteURL(final String url) {
final Vector split = TextTools.split(url, new char[] { '\\', '/'});
int size = split.size();
String s;
for (int i = 0; i < split.size();) {
s = (String) split.elementAt(i);
if (s.equals(".")) {
split.removeElementAt(i);
continue;
}
if (s.equals("..")) {
split.removeElementAt(i);
if (i > 0) {
i--;
split.removeElementAt(i);
}
continue;
}
i++;
}
if (split.isEmpty()) {
return "";
}
final StringBuffer newUrl =
new StringBuffer((String) split.elementAt(0));
for (int i = 1; i < split.size(); i++) {
newUrl.append('/');
newUrl.append((String) split.elementAt(i));
}
return newUrl.toString();
}
/**
* Changes the extension of a URL
*
* Note: the new extension may be with or without a preceding dot.
*
* @param url The original URL
* @param newExtension
* @return
*/
public static String changeExtension(
final String url, String newExtension) {
if (!newExtension.startsWith(".")) {
newExtension = "." + newExtension;
}
int dotpos = url.lastIndexOf('.');
if (dotpos == -1 ) {
/*
* Original URL doesn't have an extension
*/
return url + newExtension;
} else {
return url.substring(0, dotpos) + newExtension;
}
}
}