1
1
/*
2
- * Copyright 2012-2023 the original author or authors.
2
+ * Copyright 2012-2024 the original author or authors.
3
3
*
4
4
* Licensed under the Apache License, Version 2.0 (the "License");
5
5
* you may not use this file except in compliance with the License.
16
16
17
17
package org .springframework .boot .loader .zip ;
18
18
19
+ import java .io .File ;
19
20
import java .io .IOException ;
21
+ import java .io .RandomAccessFile ;
20
22
import java .nio .ByteBuffer ;
21
23
import java .nio .channels .ClosedByInterruptException ;
22
24
import java .nio .channels .ClosedChannelException ;
29
31
import org .springframework .boot .loader .log .DebugLogger ;
30
32
31
33
/**
32
- * Reference counted {@link DataBlock} implementation backed by a {@link FileChannel } with
34
+ * Reference counted {@link DataBlock} implementation backed by a {@link File } with
33
35
* support for slicing.
34
36
*
35
37
* @author Phillip Webb
36
38
*/
37
- class FileChannelDataBlock implements CloseableDataBlock {
39
+ class FileDataBlock implements CloseableDataBlock {
38
40
39
- private static final DebugLogger debug = DebugLogger .get (FileChannelDataBlock .class );
41
+ private static final DebugLogger debug = DebugLogger .get (FileDataBlock .class );
40
42
41
- static Tracker tracker ;
43
+ static Tracker tracker = Tracker . NONE ;
42
44
43
- private final ManagedFileChannel channel ;
45
+ private final FileAccess fileAccess ;
44
46
45
47
private final long offset ;
46
48
47
49
private final long size ;
48
50
49
- FileChannelDataBlock (Path path ) throws IOException {
50
- this .channel = new ManagedFileChannel (path );
51
+ FileDataBlock (Path path ) throws IOException {
52
+ this .fileAccess = new FileAccess (path );
51
53
this .offset = 0 ;
52
54
this .size = Files .size (path );
53
55
}
54
56
55
- FileChannelDataBlock ( ManagedFileChannel channel , long offset , long size ) {
56
- this .channel = channel ;
57
+ FileDataBlock ( FileAccess fileAccess , long offset , long size ) {
58
+ this .fileAccess = fileAccess ;
57
59
this .offset = offset ;
58
60
this .size = size ;
59
61
}
@@ -78,7 +80,7 @@ public int read(ByteBuffer dst, long pos) throws IOException {
78
80
originalDestinationLimit = dst .limit ();
79
81
dst .limit (dst .position () + remaining );
80
82
}
81
- int result = this .channel .read (dst , this .offset + pos );
83
+ int result = this .fileAccess .read (dst , this .offset + pos );
82
84
if (originalDestinationLimit != -1 ) {
83
85
dst .limit (originalDestinationLimit );
84
86
}
@@ -91,7 +93,7 @@ public int read(ByteBuffer dst, long pos) throws IOException {
91
93
* @throws IOException on I/O error
92
94
*/
93
95
void open () throws IOException {
94
- this .channel .open ();
96
+ this .fileAccess .open ();
95
97
}
96
98
97
99
/**
@@ -101,7 +103,7 @@ void open() throws IOException {
101
103
*/
102
104
@ Override
103
105
public void close () throws IOException {
104
- this .channel .close ();
106
+ this .fileAccess .close ();
105
107
}
106
108
107
109
/**
@@ -111,30 +113,30 @@ public void close() throws IOException {
111
113
* @throws E if the channel is closed
112
114
*/
113
115
<E extends Exception > void ensureOpen (Supplier <E > exceptionSupplier ) throws E {
114
- this .channel .ensureOpen (exceptionSupplier );
116
+ this .fileAccess .ensureOpen (exceptionSupplier );
115
117
}
116
118
117
119
/**
118
- * Return a new {@link FileChannelDataBlock } slice providing access to a subset of the
119
- * data. The caller is responsible for calling {@link #open()} and {@link #close()} on
120
- * the returned block.
120
+ * Return a new {@link FileDataBlock } slice providing access to a subset of the data.
121
+ * The caller is responsible for calling {@link #open()} and {@link #close()} on the
122
+ * returned block.
121
123
* @param offset the start offset for the slice relative to this block
122
- * @return a new {@link FileChannelDataBlock } instance
124
+ * @return a new {@link FileDataBlock } instance
123
125
* @throws IOException on I/O error
124
126
*/
125
- FileChannelDataBlock slice (long offset ) throws IOException {
127
+ FileDataBlock slice (long offset ) throws IOException {
126
128
return slice (offset , this .size - offset );
127
129
}
128
130
129
131
/**
130
- * Return a new {@link FileChannelDataBlock } slice providing access to a subset of the
131
- * data. The caller is responsible for calling {@link #open()} and {@link #close()} on
132
- * the returned block.
132
+ * Return a new {@link FileDataBlock } slice providing access to a subset of the data.
133
+ * The caller is responsible for calling {@link #open()} and {@link #close()} on the
134
+ * returned block.
133
135
* @param offset the start offset for the slice relative to this block
134
136
* @param size the size of the new slice
135
- * @return a new {@link FileChannelDataBlock } instance
137
+ * @return a new {@link FileDataBlock } instance
136
138
*/
137
- FileChannelDataBlock slice (long offset , long size ) {
139
+ FileDataBlock slice (long offset , long size ) {
138
140
if (offset == 0 && size == this .size ) {
139
141
return this ;
140
142
}
@@ -144,14 +146,14 @@ FileChannelDataBlock slice(long offset, long size) {
144
146
if (size < 0 || offset + size > this .size ) {
145
147
throw new IllegalArgumentException ("Size must not be negative and must be within bounds" );
146
148
}
147
- debug .log ("Slicing %s at %s with size %s" , this .channel , offset , size );
148
- return new FileChannelDataBlock (this .channel , this .offset + offset , size );
149
+ debug .log ("Slicing %s at %s with size %s" , this .fileAccess , offset , size );
150
+ return new FileDataBlock (this .fileAccess , this .offset + offset , size );
149
151
}
150
152
151
153
/**
152
154
* Manages access to underlying {@link FileChannel}.
153
155
*/
154
- static class ManagedFileChannel {
156
+ static class FileAccess {
155
157
156
158
static final int BUFFER_SIZE = 1024 * 10 ;
157
159
@@ -161,6 +163,10 @@ static class ManagedFileChannel {
161
163
162
164
private FileChannel fileChannel ;
163
165
166
+ private boolean fileChannelInterrupted ;
167
+
168
+ private RandomAccessFile randomAccessFile ;
169
+
164
170
private ByteBuffer buffer ;
165
171
166
172
private long bufferPosition = -1 ;
@@ -169,7 +175,7 @@ static class ManagedFileChannel {
169
175
170
176
private final Object lock = new Object ();
171
177
172
- ManagedFileChannel (Path path ) {
178
+ FileAccess (Path path ) {
173
179
if (!Files .isRegularFile (path )) {
174
180
throw new IllegalArgumentException (path + " must be a regular file" );
175
181
}
@@ -193,34 +199,45 @@ int read(ByteBuffer dst, long position) throws IOException {
193
199
}
194
200
195
201
private void fillBuffer (long position ) throws IOException {
196
- for (int i = 0 ; i < 10 ; i ++) {
197
- boolean interrupted = (i != 0 ) ? Thread .interrupted () : false ;
198
- try {
199
- this .buffer .clear ();
200
- this .bufferSize = this .fileChannel .read (this .buffer , position );
201
- this .bufferPosition = position ;
202
- return ;
203
- }
204
- catch (ClosedByInterruptException ex ) {
202
+ if (Thread .currentThread ().isInterrupted ()) {
203
+ fillBufferUsingRandomAccessFile (position );
204
+ return ;
205
+ }
206
+ try {
207
+ if (this .fileChannelInterrupted ) {
205
208
repairFileChannel ();
209
+ this .fileChannelInterrupted = false ;
206
210
}
207
- finally {
208
- if (interrupted ) {
209
- Thread .currentThread ().interrupt ();
210
- }
211
- }
211
+ this .buffer .clear ();
212
+ this .bufferSize = this .fileChannel .read (this .buffer , position );
213
+ this .bufferPosition = position ;
214
+ }
215
+ catch (ClosedByInterruptException ex ) {
216
+ this .fileChannelInterrupted = true ;
217
+ fillBufferUsingRandomAccessFile (position );
212
218
}
213
- throw new ClosedByInterruptException ();
214
219
}
215
220
216
- private void repairFileChannel () throws IOException {
217
- if (tracker != null ) {
218
- tracker .closedFileChannel (this .path , this .fileChannel );
221
+ private void fillBufferUsingRandomAccessFile (long position ) throws IOException {
222
+ if (this .randomAccessFile == null ) {
223
+ this .randomAccessFile = new RandomAccessFile (this .path .toFile (), "r" );
224
+ tracker .openedFileChannel (this .path );
219
225
}
220
- this .fileChannel = FileChannel .open (this .path , StandardOpenOption .READ );
221
- if (tracker != null ) {
222
- tracker .openedFileChannel (this .path , this .fileChannel );
226
+ byte [] bytes = new byte [BUFFER_SIZE ];
227
+ this .randomAccessFile .seek (position );
228
+ int len = this .randomAccessFile .read (bytes );
229
+ this .buffer .clear ();
230
+ if (len > 0 ) {
231
+ this .buffer .put (bytes , 0 , len );
223
232
}
233
+ this .bufferSize = len ;
234
+ this .bufferPosition = position ;
235
+ }
236
+
237
+ private void repairFileChannel () throws IOException {
238
+ tracker .closedFileChannel (this .path );
239
+ this .fileChannel = FileChannel .open (this .path , StandardOpenOption .READ );
240
+ tracker .openedFileChannel (this .path );
224
241
}
225
242
226
243
void open () throws IOException {
@@ -229,9 +246,7 @@ void open() throws IOException {
229
246
debug .log ("Opening '%s'" , this .path );
230
247
this .fileChannel = FileChannel .open (this .path , StandardOpenOption .READ );
231
248
this .buffer = ByteBuffer .allocateDirect (BUFFER_SIZE );
232
- if (tracker != null ) {
233
- tracker .openedFileChannel (this .path , this .fileChannel );
234
- }
249
+ tracker .openedFileChannel (this .path );
235
250
}
236
251
this .referenceCount ++;
237
252
debug .log ("Reference count for '%s' incremented to %s" , this .path , this .referenceCount );
@@ -250,18 +265,21 @@ void close() throws IOException {
250
265
this .bufferPosition = -1 ;
251
266
this .bufferSize = 0 ;
252
267
this .fileChannel .close ();
253
- if (tracker != null ) {
254
- tracker .closedFileChannel (this .path , this .fileChannel );
255
- }
268
+ tracker .closedFileChannel (this .path );
256
269
this .fileChannel = null ;
270
+ if (this .randomAccessFile != null ) {
271
+ this .randomAccessFile .close ();
272
+ tracker .closedFileChannel (this .path );
273
+ this .randomAccessFile = null ;
274
+ }
257
275
}
258
276
debug .log ("Reference count for '%s' decremented to %s" , this .path , this .referenceCount );
259
277
}
260
278
}
261
279
262
280
<E extends Exception > void ensureOpen (Supplier <E > exceptionSupplier ) throws E {
263
281
synchronized (this .lock ) {
264
- if (this .referenceCount == 0 || ! this . fileChannel . isOpen () ) {
282
+ if (this .referenceCount == 0 ) {
265
283
throw exceptionSupplier .get ();
266
284
}
267
285
}
@@ -279,9 +297,21 @@ public String toString() {
279
297
*/
280
298
interface Tracker {
281
299
282
- void openedFileChannel (Path path , FileChannel fileChannel );
300
+ Tracker NONE = new Tracker () {
301
+
302
+ @ Override
303
+ public void openedFileChannel (Path path ) {
304
+ }
305
+
306
+ @ Override
307
+ public void closedFileChannel (Path path ) {
308
+ }
309
+
310
+ };
311
+
312
+ void openedFileChannel (Path path );
283
313
284
- void closedFileChannel (Path path , FileChannel fileChannel );
314
+ void closedFileChannel (Path path );
285
315
286
316
}
287
317
0 commit comments