Skip to content

Commit f4725c3

Browse files
committed
MappedRandomAccessFile support for > 2GB files using LongMappedByteBuffer
1 parent f9cf6c1 commit f4725c3

4 files changed

Lines changed: 637 additions & 46 deletions

File tree

openpdf/src/main/java/com/lowagie/text/pdf/MappedRandomAccessFile.java

Lines changed: 60 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -48,15 +48,14 @@
4848
*/
4949
package com.lowagie.text.pdf;
5050

51+
import com.lowagie.text.utils.LongMappedByteBuffer;
52+
5153
import java.io.FileInputStream;
5254
import java.io.FileNotFoundException;
5355
import java.io.IOException;
54-
import java.lang.invoke.MethodHandle;
55-
import java.lang.invoke.MethodHandles;
56-
import java.lang.invoke.MethodType;
56+
import java.lang.reflect.Method;
5757
import java.nio.BufferUnderflowException;
5858
import java.nio.ByteBuffer;
59-
import java.nio.MappedByteBuffer;
6059
import java.nio.channels.FileChannel;
6160

6261
/**
@@ -66,7 +65,7 @@
6665
*/
6766
public class MappedRandomAccessFile implements AutoCloseable {
6867

69-
private MappedByteBuffer mappedByteBuffer = null;
68+
private LongMappedByteBuffer mappedByteBuffer = null;
7069
private FileChannel channel = null;
7170

7271
/**
@@ -102,27 +101,42 @@ public static boolean clean(final java.nio.ByteBuffer buffer) {
102101
if (buffer == null || !buffer.isDirect()) {
103102
return false;
104103
}
105-
return cleanJava11(buffer);
104+
return cleanJava17(buffer);
106105

107106
}
108107

109-
private static boolean cleanJava11(final ByteBuffer buffer) {
110-
Boolean success = Boolean.FALSE;
108+
/**
109+
* Attempts to clean the direct ByteBuffer using its internal cleaner.
110+
* Works on Java 17+ (HotSpot JVMs) without using Unsafe or MethodHandles.
111+
*
112+
* @param buffer the direct ByteBuffer to unmap
113+
* @return true if successfully cleaned, false otherwise
114+
*/
115+
private static boolean cleanJava17(final ByteBuffer buffer) {
116+
if (buffer == null || !buffer.isDirect()) {
117+
return false;
118+
}
119+
111120
try {
112-
MethodHandles.Lookup lookup = MethodHandles.lookup();
113-
Class<?> unsafeClass = Class.forName("sun.misc.Unsafe");
114-
MethodHandle methodHandle = lookup.findStatic(unsafeClass, "getUnsafe", MethodType.methodType(unsafeClass));
115-
Object theUnsafe = methodHandle.invoke();
116-
MethodHandle invokeCleanerMethod = lookup.findVirtual(unsafeClass, "invokeCleaner",
117-
MethodType.methodType(void.class, ByteBuffer.class));
118-
invokeCleanerMethod.invoke(theUnsafe, buffer);
119-
success = Boolean.TRUE;
120-
} catch (Throwable ignore) {
121-
// Ignore
121+
// Access DirectByteBuffer.cleaner() -> Cleaner.clean()
122+
Method cleanerMethod = buffer.getClass().getMethod("cleaner");
123+
cleanerMethod.setAccessible(true);
124+
Object cleaner = cleanerMethod.invoke(buffer);
125+
126+
if (cleaner != null) {
127+
Method cleanMethod = cleaner.getClass().getMethod("clean");
128+
cleanMethod.setAccessible(true);
129+
cleanMethod.invoke(cleaner);
130+
return true;
131+
}
132+
} catch (Exception e) {
133+
// Cleaning not available (not a HotSpot JVM or access denied)
122134
}
123-
return success;
135+
136+
return false;
124137
}
125138

139+
126140
/**
127141
* initializes the channel and mapped bytebuffer
128142
*
@@ -133,12 +147,10 @@ private static boolean cleanJava11(final ByteBuffer buffer) {
133147
private void init(FileChannel channel, FileChannel.MapMode mapMode)
134148
throws IOException {
135149

136-
if (channel.size() > Integer.MAX_VALUE) {
137-
throw new PdfException("The PDF file is too large. Max 2GB. Size: " + channel.size());
138-
}
139150

140151
this.channel = channel;
141-
this.mappedByteBuffer = channel.map(mapMode, 0L, channel.size());
152+
this.mappedByteBuffer = new LongMappedByteBuffer(channel, mapMode);
153+
142154
mappedByteBuffer.load();
143155
}
144156

@@ -173,19 +185,33 @@ public int read() {
173185
* @see java.io.RandomAccessFile#read(byte[], int, int)
174186
*/
175187
public int read(byte[] bytes, int off, int len) {
176-
int pos = mappedByteBuffer.position();
177-
int limit = mappedByteBuffer.limit();
178-
if (pos == limit) {
179-
return -1; // EOF
188+
if (bytes == null) {
189+
throw new NullPointerException();
180190
}
181-
int newlimit = pos + len - off;
182-
if (newlimit > limit) {
183-
len = limit - pos; // don't read beyond EOF
191+
if (off < 0 || len < 0 || off + len > bytes.length) {
192+
throw new IndexOutOfBoundsException();
184193
}
185-
mappedByteBuffer.get(bytes, off, len);
186-
return len;
194+
195+
long pos = mappedByteBuffer.position();
196+
long limit = mappedByteBuffer.limit();
197+
198+
if (pos >= limit) return -1;
199+
200+
int remaining = (int) Math.min(len, limit - pos);
201+
if (remaining <= 0) return -1;
202+
203+
int totalRead = 0;
204+
while (totalRead < remaining) {
205+
int n = mappedByteBuffer.read(bytes, off + totalRead, remaining - totalRead);
206+
if (n <= 0) break; // EOF or read failed
207+
totalRead += n;
208+
}
209+
210+
return totalRead == 0 ? -1 : totalRead;
187211
}
188212

213+
214+
189215
/**
190216
* @return long
191217
* @see java.io.RandomAccessFile#getFilePointer()
@@ -199,7 +225,7 @@ public long getFilePointer() {
199225
* @see java.io.RandomAccessFile#seek(long)
200226
*/
201227
public void seek(long pos) {
202-
mappedByteBuffer.position((int) pos);
228+
mappedByteBuffer.position(pos);
203229
}
204230

205231
/**
@@ -217,24 +243,12 @@ public long length() {
217243
* @see java.io.RandomAccessFile#close()
218244
*/
219245
public void close() throws IOException {
220-
clean(mappedByteBuffer);
246+
mappedByteBuffer.clean();
221247
mappedByteBuffer = null;
222248
if (channel != null) {
223249
channel.close();
224250
}
225251
channel = null;
226252
}
227253

228-
/**
229-
* invokes the close method
230-
*
231-
* @see java.lang.Object#finalize()
232-
*/
233-
@Override
234-
@Deprecated(since = "OpenPDF-2.0.2", forRemoval = true)
235-
protected void finalize() throws Throwable {
236-
close();
237-
super.finalize();
238-
}
239-
240254
}

0 commit comments

Comments
 (0)