diff --git a/classpath/java-io.cpp b/classpath/java-io.cpp index 481032209a..b7e29e8058 100644 --- a/classpath/java-io.cpp +++ b/classpath/java-io.cpp @@ -14,12 +14,14 @@ #include #include #include +#include #include #include "jni.h" #include "jni-util.h" #ifdef WIN32 +# include # include # define OPEN _open @@ -34,6 +36,7 @@ # define OPEN_MASK O_BINARY #else # include +# 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(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(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(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 + (e->GetPrimitiveArrayCritical(buffer, 0)); + + memcpy(dst + offset, + reinterpret_cast(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(peer)); +} diff --git a/classpath/java/io/RandomAccessFile.java b/classpath/java/io/RandomAccessFile.java new file mode 100644 index 0000000000..9c73f607c1 --- /dev/null +++ b/classpath/java/io/RandomAccessFile.java @@ -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); +} diff --git a/classpath/java/util/zip/ZipEntry.java b/classpath/java/util/zip/ZipEntry.java new file mode 100644 index 0000000000..0440d01873 --- /dev/null +++ b/classpath/java/util/zip/ZipEntry.java @@ -0,0 +1,6 @@ +package java.util.zip; + +public abstract class ZipEntry { + public abstract String getName(); + public abstract int getCompressedSize(); +} diff --git a/classpath/java/util/zip/ZipFile.java b/classpath/java/util/zip/ZipFile.java new file mode 100644 index 0000000000..5497ea5ea3 --- /dev/null +++ b/classpath/java/util/zip/ZipFile.java @@ -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 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 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 { + private final Window window; + private final Iterator iterator; + + public MyEnumeration(Window window, Iterator 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; + } + } +} diff --git a/classpath/jni-util.h b/classpath/jni-util.h index 8d29a12d1b..9e0972225a 100644 --- a/classpath/jni-util.h +++ b/classpath/jni-util.h @@ -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 diff --git a/test/Zip.java b/test/Zip.java new file mode 100644 index 0000000000..b18d5f1a0b --- /dev/null +++ b/test/Zip.java @@ -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 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(); + } + } + } + +} diff --git a/test/test.sh b/test/test.sh index cfd03fe2df..fd028e39f0 100644 --- a/test/test.sh +++ b/test/test.sh @@ -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* )