implement minimal, read-only versions of RandomAccessFile and ZipFile

This commit is contained in:
Joel Dice 2008-10-03 14:15:47 -06:00
parent 07daa9be51
commit 4c307ae8c6
7 changed files with 571 additions and 4 deletions

View File

@ -14,12 +14,14 @@
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <stdint.h>
#include <dirent.h>
#include "jni.h"
#include "jni-util.h"
#ifdef WIN32
# include <windows.h>
# include <io.h>
# define OPEN _open
@ -34,6 +36,7 @@
# define OPEN_MASK O_BINARY
#else
# include <unistd.h>
# include "sys/mman.h"
# define OPEN open
# define CLOSE close
@ -47,6 +50,8 @@
# define OPEN_MASK 0
#endif
inline void* operator new(size_t, void* p) throw() { return p; }
namespace {
inline bool
@ -95,9 +100,118 @@ doWrite(JNIEnv* e, jint fd, const jbyte* data, jint length)
int r = WRITE(fd, data, length);
if (r != length) {
throwNew(e, "java/io/IOException", strerror(errno));
}
}
}
#ifdef WIN32
class Mapping {
public:
Mapping(uint8_t* start, size_t length, HANDLE mapping, HANDLE file):
start(start),
length(length),
mapping(mapping),
file(file)
{ }
uint8_t* start;
size_t length;
HANDLE mapping;
HANDLE file;
};
inline Mapping*
map(JNIEnv* e, const char* path)
{
Mapping* result = 0;
HANDLE file = CreateFile(path, FILE_READ_DATA, FILE_SHARE_READ, 0,
OPEN_EXISTING, 0, 0);
if (file != INVALID_HANDLE_VALUE) {
unsigned size = GetFileSize(file, 0);
if (size != INVALID_FILE_SIZE) {
HANDLE mapping = CreateFileMapping(file, 0, PAGE_READONLY, 0, size, 0);
if (mapping) {
void* data = MapViewOfFile(mapping, FILE_MAP_READ, 0, 0, 0);
if (data) {
void* p = allocate(e, sizeof(Mapping));
if (not e->ExceptionOccurred()) {
result = new (p)
Mapping(static_cast<uint8_t*>(data), size, file, mapping);
}
}
if (result == 0) {
CloseHandle(mapping);
}
}
}
if (result == 0) {
CloseHandle(file);
}
}
if (result == 0 and not e->ExceptionOccurred()) {
throwNew(e, "java/io/IOException", "%d", GetLastError());
}
return result;
}
inline void
unmap(JNIEnv*, Mapping* mapping)
{
UnmapViewOfFile(mapping->start);
CloseHandle(mapping->mapping);
CloseHandle(mapping->file);
free(mapping);
}
#else // not WIN32
class Mapping {
public:
Mapping(uint8_t* start, size_t length):
start(start),
length(length)
{ }
uint8_t* start;
size_t length;
};
inline Mapping*
map(JNIEnv* e, const char* path)
{
Mapping* result = 0;
int fd = open(path, O_RDONLY);
if (fd != -1) {
struct stat s;
int r = fstat(fd, &s);
if (r != -1) {
void* data = mmap(0, s.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
if (data) {
void* p = allocate(e, sizeof(Mapping));
if (not e->ExceptionOccurred()) {
result = new (p) Mapping(static_cast<uint8_t*>(data), s.st_size);
}
}
}
close(fd);
}
if (result == 0 and not e->ExceptionOccurred()) {
throwNew(e, "java/io/IOException", strerror(errno));
}
return result;
}
inline void
unmap(JNIEnv*, Mapping* mapping)
{
munmap(mapping->start, mapping->length);
free(mapping);
}
#endif // not WIN32
} // namespace
extern "C" JNIEXPORT jstring JNICALL
@ -333,3 +447,42 @@ Java_java_io_FileOutputStream_close(JNIEnv* e, jclass, jint fd)
{
doClose(e, fd);
}
extern "C" JNIEXPORT void JNICALL
Java_java_io_RandomAccessFile_open(JNIEnv* e, jclass, jstring path,
jlongArray result)
{
const char* chars = e->GetStringUTFChars(path, 0);
if (chars) {
Mapping* mapping = map(e, chars);
jlong peer = reinterpret_cast<jlong>(mapping);
e->SetLongArrayRegion(result, 0, 1, &peer);
jlong length = mapping->length;
e->SetLongArrayRegion(result, 1, 1, &length);
e->ReleaseStringUTFChars(path, chars);
}
}
extern "C" JNIEXPORT void JNICALL
Java_java_io_RandomAccessFile_copy(JNIEnv* e, jclass, jlong peer,
jlong position, jbyteArray buffer,
int offset, int length)
{
uint8_t* dst = reinterpret_cast<uint8_t*>
(e->GetPrimitiveArrayCritical(buffer, 0));
memcpy(dst + offset,
reinterpret_cast<Mapping*>(peer)->start + position,
length);
e->ReleasePrimitiveArrayCritical(buffer, dst, 0);
}
extern "C" JNIEXPORT void JNICALL
Java_java_io_RandomAccessFile_close(JNIEnv* e, jclass, jlong peer)
{
unmap(e, reinterpret_cast<Mapping*>(peer));
}

View File

@ -0,0 +1,62 @@
package java.io;
public class RandomAccessFile {
private long peer;
private long length;
private long position = 0;
public RandomAccessFile(String name, String mode)
throws FileNotFoundException
{
if (! mode.equals("r")) throw new IllegalArgumentException();
long[] result = new long[2];
open(name, result);
peer = result[0];
length = result[1];
}
private static native void open(String name, long[] result)
throws FileNotFoundException;
public long length() throws IOException {
return length;
}
public long getFilePointer() throws IOException {
return position;
}
public void seek(long position) throws IOException {
if (position < 0 || position > length) throw new IOException();
this.position = position;
}
public void readFully(byte[] buffer, int offset, int length)
throws IOException
{
if (peer == 0) throw new IOException();
if (length == 0) return;
if (position + length > this.length) throw new EOFException();
if (offset < 0 || offset + length > buffer.length)
throw new ArrayIndexOutOfBoundsException();
copy(peer, position, buffer, offset, length);
}
private static native void copy(long peer, long position, byte[] buffer,
int offset, int length);
public void close() throws IOException {
if (peer != 0) {
close(peer);
peer = 0;
}
}
private static native void close(long peer);
}

View File

@ -0,0 +1,6 @@
package java.util.zip;
public abstract class ZipEntry {
public abstract String getName();
public abstract int getCompressedSize();
}

View File

@ -0,0 +1,295 @@
package java.util.zip;
import java.io.RandomAccessFile;
import java.io.InputStream;
import java.io.IOException;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.Map;
import java.util.HashMap;
public class ZipFile {
private final RandomAccessFile file;
private final Window window;
private final Map<String,Integer> index = new HashMap();
public ZipFile(String name) throws IOException {
file = new RandomAccessFile(name, "r");
window = new Window(file, 4096);
int fileLength = (int) file.length();
int pointer = fileLength - 22;
byte[] magic = new byte[] { 0x50, 0x4B, 0x05, 0x06 };
while (pointer > 0) {
if (equal(window.data, window.seek(pointer, magic.length),
magic, 0, magic.length))
{
pointer = directoryOffset(window, pointer);
magic = new byte[] { 0x50, 0x4B, 0x01, 0x02 };
while (pointer < fileLength) {
if (equal(window.data, window.seek(pointer, magic.length),
magic, 0, magic.length))
{
index.put(entryName(window, pointer), pointer);
pointer = entryEnd(window, pointer);
} else {
pointer = fileLength;
}
}
pointer = 0;
} else {
-- pointer;
}
}
}
public int size() {
return index.size();
}
public Enumeration<ZipEntry> entries() {
return new MyEnumeration(window, index.values().iterator());
}
public ZipEntry getEntry(String name) {
Integer pointer = index.get(name);
return (pointer == null ? null : new MyZipEntry(window, pointer));
}
public InputStream getInputStream(ZipEntry entry) throws IOException {
int pointer = ((MyZipEntry) entry).pointer;
int method = compressionMethod(window, pointer);
int size = compressedSize(window, pointer);
InputStream in = new MyInputStream(file, fileData(window, pointer), size);
final int Stored = 0;
final int Deflated = 8;
switch (method) {
case Stored:
return in;
case Deflated:
return new InflaterInputStream(in, new Inflater(true));
default:
throw new IOException();
}
}
private static boolean equal(byte[] a, int aOffset, byte[] b, int bOffset,
int size)
{
for (int i = 0; i < size; ++i) {
if (a[aOffset + i] != b[bOffset + i]) return false;
}
return true;
}
private static int get2(Window w, int p) throws IOException {
int offset = w.seek(p, 2);
return
((w.data[offset + 1] & 0xFF) << 8) |
((w.data[offset ] & 0xFF) );
}
private static int get4(Window w, int p) throws IOException {
int offset = w.seek(p, 4);
return
((w.data[offset + 3] & 0xFF) << 24) |
((w.data[offset + 2] & 0xFF) << 16) |
((w.data[offset + 1] & 0xFF) << 8) |
((w.data[offset ] & 0xFF) );
}
private static int directoryOffset(Window w, int p) throws IOException {
return get4(w, p + 16);
}
private static int entryNameLength(Window w, int p) throws IOException {
return get2(w, p + 28);
}
private static String entryName(Window w, int p) throws IOException {
int length = entryNameLength(w, p);
return new String(w.data, w.seek(p + 46, length), length);
}
private static int compressionMethod(Window w, int p) throws IOException {
return get2(w, p + 10);
}
private static int compressedSize(Window w, int p) throws IOException {
return get4(w, p + 20);
}
private static int fileNameLength(Window w, int p) throws IOException {
return get2(w, p + 28);
}
private static int extraFieldLength(Window w, int p) throws IOException {
return get2(w, p + 30);
}
private static int commentFieldLength(Window w, int p) throws IOException {
return get2(w, p + 32);
}
private static int entryEnd(Window w, int p) throws IOException {
final int HeaderSize = 46;
return p + HeaderSize
+ fileNameLength(w, p)
+ extraFieldLength(w, p)
+ commentFieldLength(w, p);
}
private static int fileData(Window w, int p) throws IOException {
int localHeader = localHeader(w, p);
final int LocalHeaderSize = 30;
return localHeader
+ LocalHeaderSize
+ localFileNameLength(w, localHeader)
+ localExtraFieldLength(w, localHeader);
}
private static int localHeader(Window w, int p) throws IOException {
return get4(w, p + 42);
}
private static int localFileNameLength(Window w, int p) throws IOException {
return get2(w, p + 26);
}
private static int localExtraFieldLength(Window w, int p)
throws IOException
{
return get2(w, p + 28);
}
private static class Window {
private final RandomAccessFile file;
public final byte[] data;
public int start;
public int length;
public Window(RandomAccessFile file, int size) {
this.file = file;
data = new byte[size];
}
public int seek(int start, int length) throws IOException {
int fileLength = (int) file.length();
if (length > data.length) {
throw new IllegalArgumentException
("length " + length + " greater than buffer length " + data.length);
}
if (start < 0) {
throw new IllegalArgumentException("negative start " + start);
}
if (start + length > fileLength) {
throw new IllegalArgumentException
("end " + (start + length) + " greater than file length " +
fileLength);
}
if (start < this.start || start + length > this.start + this.length) {
this.length = Math.min(data.length, fileLength);
this.start = start - ((this.length - length) / 2);
if (this.start < 0) {
this.start = 0;
} else if (this.start + this.length > fileLength) {
this.start = fileLength - this.length;
}
file.seek(this.start);
// System.out.println("start " + start + " length " + length + " this start " + this.start + " this.length " + this.length + " file length " + fileLength);
file.readFully(data, 0, this.length);
}
return start - this.start;
}
}
private static class MyZipEntry extends ZipEntry {
public final Window window;
public final int pointer;
public MyZipEntry(Window window, int pointer) {
this.window = window;
this.pointer = pointer;
}
public String getName() {
try {
return entryName(window, pointer);
} catch (IOException e) {
return null;
}
}
public int getCompressedSize() {
try {
return compressedSize(window, pointer);
} catch (IOException e) {
return 0;
}
}
}
private static class MyEnumeration implements Enumeration<ZipEntry> {
private final Window window;
private final Iterator<Integer> iterator;
public MyEnumeration(Window window, Iterator<Integer> iterator) {
this.window = window;
this.iterator = iterator;
}
public boolean hasMoreElements() {
return iterator.hasNext();
}
public ZipEntry nextElement() {
return new MyZipEntry(window, iterator.next());
}
}
private static class MyInputStream extends InputStream {
private RandomAccessFile file;
private int offset;
private int length;
public MyInputStream(RandomAccessFile file, int start, int length) {
this.file = file;
this.offset = start;
this.length = length;
}
public int read() throws IOException {
byte[] b = new byte[1];
int c = read(b);
return (c == -1 ? -1 : b[0] & 0xFF);
}
public int read(byte[] b, int offset, int length) throws IOException {
if (this.length == 0) return -1;
if (length > this.length) length = this.length;
file.seek(this.offset);
file.readFully(b, offset, length);
this.offset += length;
this.length -= length;
return length;
}
public void close() throws IOException {
file = null;
}
}
}

View File

@ -11,6 +11,9 @@
#ifndef JNI_UTIL
#define JNI_UTIL
#include "stdio.h"
#include "stdlib.h"
#undef JNIEXPORT
#ifdef __MINGW32__
# define JNIEXPORT __declspec(dllexport)
@ -23,15 +26,37 @@
namespace {
inline void
throwNew(JNIEnv* e, const char* class_, const char* message)
throwNew(JNIEnv* e, const char* class_, const char* message, ...)
{
jclass c = e->FindClass(class_);
if (c) {
e->ThrowNew(c, message);
if (message) {
static const unsigned BufferSize = 256;
char buffer[BufferSize];
va_list list;
va_start(list, message);
vsnprintf(buffer, BufferSize - 1, message, list);
va_end(list);
e->ThrowNew(c, buffer);
} else {
e->ThrowNew(c, 0);
}
e->DeleteLocalRef(c);
}
}
inline void*
allocate(JNIEnv* e, unsigned size)
{
void* p = malloc(size);
if (p == 0) {
throwNew(e, "java/lang/OutOfMemoryError", 0);
}
return p;
}
} // namespace
#endif//JNI_UTIL

26
test/Zip.java Normal file
View File

@ -0,0 +1,26 @@
import java.io.InputStream;
import java.util.Enumeration;
import java.util.zip.ZipFile;
import java.util.zip.ZipEntry;
public class Zip {
public static void main(String[] args) throws Exception {
ZipFile file = new ZipFile("build/classpath.jar");
byte[] buffer = new byte[4096];
for (Enumeration<ZipEntry> e = file.entries(); e.hasMoreElements();) {
ZipEntry entry = e.nextElement();
InputStream in = file.getInputStream(entry);
try {
int size = 0;
int c; while ((c = in.read(buffer)) != -1) size += c;
System.out.println
(entry.getName() + " " + entry.getCompressedSize() + " " + size);
} finally {
in.read();
}
}
}
}

View File

@ -17,7 +17,7 @@ for test in ${tests}; do
printf "%16s" "${test}: "
case ${mode} in
debug|debug-fast|fast )
debug|debug-fast|fast|small )
${vm} ${flags} ${test} >>${log} 2>&1;;
stress* )