0
# JVM Extensions
1
2
Okio provides extensive JVM-specific extensions for interoperability with Java I/O, NIO, security APIs, and other JVM libraries. These extensions make it easy to integrate Okio with existing Java codebases.
3
4
## Capabilities
5
6
### Java I/O Interoperability
7
8
Convert between Java I/O streams and Okio Sources/Sinks.
9
10
```kotlin { .api }
11
/**
12
* Converts an OutputStream to a Sink
13
* @return Sink that writes to this OutputStream
14
*/
15
fun OutputStream.sink(): Sink
16
17
/**
18
* Converts an InputStream to a Source
19
* @return Source that reads from this InputStream
20
*/
21
fun InputStream.source(): Source
22
23
/**
24
* Converts a Socket's OutputStream to a Sink with timeout support
25
* @return Sink that writes to the socket with timeout handling
26
*/
27
fun Socket.sink(): Sink
28
29
/**
30
* Converts a Socket's InputStream to a Source with timeout support
31
* @return Source that reads from the socket with timeout handling
32
*/
33
fun Socket.source(): Source
34
35
/**
36
* Converts a File to a Source for reading
37
* @return Source that reads from this file
38
* @throws FileNotFoundException if file doesn't exist
39
* @throws IOException if an I/O error occurs
40
*/
41
@Throws(IOException::class)
42
fun File.source(): Source
43
44
/**
45
* Converts a File to a Sink for writing
46
* @param append If true, append to existing file; if false, overwrite
47
* @return Sink that writes to this file
48
* @throws IOException if an I/O error occurs
49
*/
50
@Throws(IOException::class)
51
fun File.sink(append: Boolean = false): Sink
52
53
/**
54
* Converts a File to an appending Sink
55
* @return Sink that appends to this file
56
* @throws IOException if an I/O error occurs
57
*/
58
@Throws(IOException::class)
59
fun File.appendingSink(): Sink
60
```
61
62
**Usage Examples:**
63
64
```kotlin
65
import java.io.*
66
import java.net.Socket
67
68
// File I/O
69
val file = File("/tmp/example.txt")
70
val source = file.source().buffer()
71
val content = source.readUtf8()
72
source.close()
73
74
val sink = file.sink().buffer()
75
sink.writeUtf8("Hello from Okio!")
76
sink.close()
77
78
// Stream conversion
79
val byteArrayOutputStream = ByteArrayOutputStream()
80
val sink = byteArrayOutputStream.sink().buffer()
81
sink.writeUtf8("Data")
82
sink.close()
83
val bytes = byteArrayOutputStream.toByteArray()
84
85
val byteArrayInputStream = ByteArrayInputStream(bytes)
86
val source = byteArrayInputStream.source().buffer()
87
val data = source.readUtf8()
88
89
// Socket I/O with timeout
90
val socket = Socket("example.com", 80)
91
socket.soTimeout = 5000 // 5 second timeout
92
93
val socketSource = socket.source().buffer()
94
val socketSink = socket.sink().buffer()
95
96
socketSink.writeUtf8("GET / HTTP/1.1\r\nHost: example.com\r\n\r\n")
97
socketSink.flush()
98
99
val response = socketSource.readUtf8Line()
100
println(response)
101
socket.close()
102
```
103
104
### NIO Interoperability
105
106
Work with Java NIO Path and Channel APIs.
107
108
```kotlin { .api }
109
/**
110
* Converts a NIO Path to a Source for reading
111
* @param options OpenOption instances to control how file is opened
112
* @return Source that reads from the path
113
* @throws IOException if an I/O error occurs
114
*/
115
@Throws(IOException::class)
116
fun java.nio.file.Path.source(vararg options: OpenOption): Source
117
118
/**
119
* Converts a NIO Path to a Sink for writing
120
* @param options OpenOption instances to control how file is opened
121
* @return Sink that writes to the path
122
* @throws IOException if an I/O error occurs
123
*/
124
@Throws(IOException::class)
125
fun java.nio.file.Path.sink(vararg options: OpenOption): Sink
126
```
127
128
**Usage Examples:**
129
130
```kotlin
131
import java.nio.file.Paths
132
import java.nio.file.StandardOpenOption.*
133
134
val nioPath = Paths.get("/tmp/nio-example.txt")
135
136
// Write using NIO path
137
val sink = nioPath.sink(CREATE, WRITE, TRUNCATE_EXISTING).buffer()
138
sink.writeUtf8("NIO Path example")
139
sink.close()
140
141
// Read using NIO path
142
val source = nioPath.source(READ).buffer()
143
val content = source.readUtf8()
144
source.close()
145
println(content)
146
147
// Append to file
148
val appendSink = nioPath.sink(CREATE, WRITE, APPEND).buffer()
149
appendSink.writeUtf8("\nAppended line")
150
appendSink.close()
151
```
152
153
### Cryptographic Extensions
154
155
Integrate with Java security APIs for encryption and authentication.
156
157
```kotlin { .api }
158
/**
159
* Wraps a Sink with cipher encryption
160
* @param cipher Initialized Cipher instance for encryption
161
* @return CipherSink that encrypts data before writing
162
*/
163
fun Sink.cipherSink(cipher: Cipher): CipherSink
164
165
/**
166
* Wraps a Source with cipher decryption
167
* @param cipher Initialized Cipher instance for decryption
168
* @return CipherSource that decrypts data while reading
169
*/
170
fun Source.cipherSource(cipher: Cipher): CipherSource
171
172
/**
173
* Wraps a Sink with MAC computation
174
* @param mac Initialized Mac instance
175
* @return HashingSink that computes MAC while writing
176
*/
177
fun Sink.hashingSink(mac: Mac): HashingSink
178
179
/**
180
* Wraps a Source with MAC computation
181
* @param mac Initialized Mac instance
182
* @return HashingSource that computes MAC while reading
183
*/
184
fun Source.hashingSource(mac: Mac): HashingSource
185
186
/**
187
* Wraps a Sink with MessageDigest computation
188
* @param digest MessageDigest instance
189
* @return HashingSink that computes digest while writing
190
*/
191
fun Sink.hashingSink(digest: MessageDigest): HashingSink
192
193
/**
194
* Wraps a Source with MessageDigest computation
195
* @param digest MessageDigest instance
196
* @return HashingSource that computes digest while reading
197
*/
198
fun Source.hashingSource(digest: MessageDigest): HashingSource
199
```
200
201
**Usage Examples:**
202
203
```kotlin
204
import javax.crypto.Cipher
205
import javax.crypto.spec.SecretKeySpec
206
import javax.crypto.Mac
207
import javax.crypto.spec.SecretKeySpec
208
import java.security.MessageDigest
209
210
// AES encryption
211
val key = SecretKeySpec("MySecretKey12345".toByteArray(), "AES")
212
val encryptCipher = Cipher.getInstance("AES/ECB/PKCS5Padding")
213
encryptCipher.init(Cipher.ENCRYPT_MODE, key)
214
215
val file = File("/tmp/encrypted.dat")
216
val encryptedSink = file.sink().cipherSink(encryptCipher).buffer()
217
encryptedSink.writeUtf8("Secret message")
218
encryptedSink.close()
219
220
// AES decryption
221
val decryptCipher = Cipher.getInstance("AES/ECB/PKCS5Padding")
222
decryptCipher.init(Cipher.DECRYPT_MODE, key)
223
224
val decryptedSource = file.source().cipherSource(decryptCipher).buffer()
225
val decryptedMessage = decryptedSource.readUtf8()
226
decryptedSource.close()
227
println("Decrypted: $decryptedMessage")
228
229
// HMAC computation
230
val hmacKey = SecretKeySpec("hmac-secret".toByteArray(), "HmacSHA256")
231
val mac = Mac.getInstance("HmacSHA256")
232
mac.init(hmacKey)
233
234
val hmacSink = blackholeSink().hashingSink(mac)
235
hmacSink.write(Buffer().writeUtf8("data to authenticate"), 19)
236
hmacSink.flush()
237
println("HMAC: ${hmacSink.hash.hex()}")
238
239
// MessageDigest computation
240
val sha256 = MessageDigest.getInstance("SHA-256")
241
val digestSink = blackholeSink().hashingSink(sha256)
242
digestSink.write(Buffer().writeUtf8("data to hash"), 12)
243
digestSink.flush()
244
println("SHA-256: ${digestSink.hash.hex()}")
245
```
246
247
### Resource FileSystem
248
249
Access classpath resources as a FileSystem.
250
251
```kotlin { .api }
252
/**
253
* Creates a FileSystem that reads from classpath resources
254
* @return FileSystem for accessing classpath resources
255
*/
256
fun ClassLoader.asResourceFileSystem(): FileSystem
257
258
/**
259
* CipherSink encrypts data using a Cipher before writing to underlying sink
260
*/
261
class CipherSink(
262
private val sink: BufferedSink,
263
private val cipher: Cipher
264
) : Sink {
265
/**
266
* The cipher being used for encryption
267
*/
268
val cipher: Cipher
269
}
270
271
/**
272
* CipherSource decrypts data using a Cipher while reading from underlying source
273
*/
274
class CipherSource(
275
private val source: BufferedSource,
276
private val cipher: Cipher
277
) : Source {
278
/**
279
* The cipher being used for decryption
280
*/
281
val cipher: Cipher
282
}
283
```
284
285
**Usage Examples:**
286
287
```kotlin
288
// Access classpath resources
289
val classLoader = Thread.currentThread().contextClassLoader
290
val resourceFs = classLoader.asResourceFileSystem()
291
292
// Read a resource file
293
val configPath = "config/application.properties".toPath()
294
if (resourceFs.exists(configPath)) {
295
val config = resourceFs.read(configPath) {
296
readUtf8()
297
}
298
println("Config: $config")
299
}
300
301
// List resources in a directory
302
val resourceDir = "static/css".toPath()
303
resourceFs.listOrNull(resourceDir)?.forEach { path ->
304
println("Resource: $path")
305
}
306
```
307
308
### Async Timeout
309
310
Background thread timeout implementation for non-blocking operations.
311
312
```kotlin { .api }
313
/**
314
* Timeout implementation using a background thread
315
* Provides asynchronous timeout functionality
316
*/
317
class AsyncTimeout : Timeout() {
318
/**
319
* Enters this timeout, starting the timeout timer
320
*/
321
fun enter()
322
323
/**
324
* Exits this timeout, stopping the timeout timer
325
* @return true if the timeout was triggered, false otherwise
326
*/
327
fun exit(): Boolean
328
329
/**
330
* Cancels this timeout, preventing it from firing
331
*/
332
fun cancel()
333
334
/**
335
* Wraps a Sink with this timeout
336
* @param sink Sink to wrap with timeout
337
* @return Sink that respects this timeout
338
*/
339
fun sink(sink: Sink): Sink
340
341
/**
342
* Wraps a Source with this timeout
343
* @param source Source to wrap with timeout
344
* @return Source that respects this timeout
345
*/
346
fun source(source: Source): Source
347
348
/**
349
* Executes a block with this timeout applied
350
* @param block Block to execute with timeout
351
* @return Result of the block execution
352
* @throws IOException if timeout occurs
353
*/
354
@Throws(IOException::class)
355
fun <T> withTimeout(block: () -> T): T
356
357
/**
358
* Called when timeout occurs (override to customize behavior)
359
* Default implementation does nothing
360
*/
361
protected open fun timedOut()
362
363
/**
364
* Creates an exception for timeout scenarios
365
* @param cause Optional underlying cause
366
* @return IOException to throw on timeout
367
*/
368
protected open fun newTimeoutException(cause: IOException?): IOException
369
}
370
```
371
372
**Usage Examples:**
373
374
```kotlin
375
import java.util.concurrent.TimeUnit
376
377
// Create timeout for network operations
378
val timeout = AsyncTimeout()
379
timeout.timeout(30, TimeUnit.SECONDS) // 30 second timeout
380
381
try {
382
val result = timeout.withTimeout {
383
// Simulate long-running network operation
384
val socket = Socket("slow-server.com", 80)
385
val source = socket.source().buffer()
386
val sink = socket.sink().buffer()
387
388
sink.writeUtf8("GET / HTTP/1.1\r\nHost: slow-server.com\r\n\r\n")
389
sink.flush()
390
391
val response = source.readUtf8Line()
392
socket.close()
393
response
394
}
395
println("Response: $result")
396
} catch (e: IOException) {
397
println("Operation timed out or failed: ${e.message}")
398
}
399
400
// Custom timeout behavior
401
class CustomTimeout : AsyncTimeout() {
402
override fun timedOut() {
403
println("Custom timeout triggered!")
404
}
405
406
override fun newTimeoutException(cause: IOException?): IOException {
407
return IOException("Custom timeout exceeded", cause)
408
}
409
}
410
```
411
412
### Compression and Decompression
413
414
Work with compressed data streams.
415
416
```kotlin { .api }
417
/**
418
* Sink that compresses data using Deflater before writing
419
*/
420
class DeflaterSink(
421
private val sink: Sink,
422
private val deflater: Deflater
423
) : Sink {
424
/**
425
* The deflater used for compression
426
*/
427
val deflater: Deflater
428
}
429
430
/**
431
* Source that decompresses data using Inflater while reading
432
*/
433
class InflaterSource(
434
private val source: Source,
435
private val inflater: Inflater
436
) : Source {
437
/**
438
* The inflater used for decompression
439
*/
440
val inflater: Inflater
441
442
/**
443
* Attempts to read or inflate data
444
* @param sink Buffer to read into
445
* @param byteCount Number of bytes to attempt to read
446
* @return Number of bytes read, or -1 if exhausted
447
*/
448
fun readOrInflate(sink: Buffer, byteCount: Long): Long
449
450
/**
451
* Refills the inflater with more compressed data
452
* @return true if more data was available
453
*/
454
fun refill(): Boolean
455
}
456
457
/**
458
* Creates a pipe for connecting a source and sink with buffering
459
* @param maxBufferSize Maximum buffer size for the pipe
460
* @return Pipe instance with source and sink properties
461
*/
462
class Pipe(maxBufferSize: Long) {
463
/**
464
* Sink for writing data to the pipe
465
*/
466
val sink: Sink
467
468
/**
469
* Source for reading data from the pipe
470
*/
471
val source: Source
472
473
/**
474
* Folds the pipe buffer into the provided sink
475
* @param sink Sink to receive buffered data
476
*/
477
fun fold(sink: Sink)
478
479
/**
480
* Cancels the pipe, preventing further operations
481
*/
482
fun cancel()
483
}
484
```
485
486
**Usage Examples:**
487
488
```kotlin
489
import java.util.zip.Deflater
490
import java.util.zip.Inflater
491
492
// Compress data
493
val originalData = "This is data to compress. ".repeat(100)
494
val deflater = Deflater(Deflater.BEST_COMPRESSION)
495
val compressedBuffer = Buffer()
496
val deflaterSink = DeflaterSink(compressedBuffer, deflater)
497
val bufferedSink = deflaterSink.buffer()
498
499
bufferedSink.writeUtf8(originalData)
500
bufferedSink.close()
501
502
println("Original size: ${originalData.length}")
503
println("Compressed size: ${compressedBuffer.size}")
504
505
// Decompress data
506
val inflater = Inflater()
507
val inflaterSource = InflaterSource(compressedBuffer, inflater)
508
val bufferedSource = inflaterSource.buffer()
509
510
val decompressedData = bufferedSource.readUtf8()
511
bufferedSource.close()
512
513
println("Decompressed matches original: ${decompressedData == originalData}")
514
515
// Use Pipe for producer-consumer pattern
516
val pipe = Pipe(8192) // 8KB buffer
517
518
// Producer thread
519
Thread {
520
val sink = pipe.sink.buffer()
521
try {
522
for (i in 1..100) {
523
sink.writeUtf8("Message $i\n")
524
Thread.sleep(10) // Simulate work
525
}
526
} finally {
527
sink.close()
528
}
529
}.start()
530
531
// Consumer thread
532
Thread {
533
val source = pipe.source.buffer()
534
try {
535
while (!source.exhausted()) {
536
val message = source.readUtf8Line()
537
if (message != null) {
538
println("Received: $message")
539
}
540
}
541
} finally {
542
source.close()
543
}
544
}.start()
545
```