4848 */
4949package com .lowagie .text .pdf ;
5050
51+ import com .lowagie .text .utils .LongMappedByteBuffer ;
52+
5153import java .io .FileInputStream ;
5254import java .io .FileNotFoundException ;
5355import 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 ;
5757import java .nio .BufferUnderflowException ;
5858import java .nio .ByteBuffer ;
59- import java .nio .MappedByteBuffer ;
6059import java .nio .channels .FileChannel ;
6160
6261/**
6665 */
6766public 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